# Field defaults

**URL:** <https://ziggit.dev/t/field-defaults/17755>\
**Category:** Explain\
**Tags:** language\
**Created:** [September 29, 2026, 2:55pm UTC](https://ziggit.dev/t/field-defaults/17755 "2026-09-29T14:55:26Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![matklad](https://ziggit.dev/user_avatar/ziggit.dev/matklad/32/2462_2.png) [@matklad](https://ziggit.dev/u/matklad)\
**Post date:** [September 29, 2026, 2:55pm UTC](https://ziggit.dev/t/field-defaults/17755/1 "2026-09-29T14:55:26Z")

</div>

`std.Io.Mutex` is intended to be initialized with a declaration literal:

```zig
my_mutex: Mutex = .init;

```

An alternative design would be to make the following work:

```zig
my_mutex: Mutex = .{};

```

And this is how it used to work.

What are the consequences of these different design choices? Why should I prefer one over the other?

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [September 29, 2026, 3:06pm UTC](https://ziggit.dev/t/field-defaults/17755/2 "2026-09-29T15:06:20Z")

</div>

Default fields make sense when you can change fields independently of each other.  
But if doing so can cause an invalid state, a declaration is preferred as it prevents that.

Declarations also provide a clear name to the state, there can be multiple, and can coexist with default fields.

---

<div class="post-metadata">

**Author:** ![matklad](https://ziggit.dev/user_avatar/ziggit.dev/matklad/32/2462_2.png) [@matklad](https://ziggit.dev/u/matklad)\
**Post date:** [September 29, 2026, 3:09pm UTC](https://ziggit.dev/t/field-defaults/17755/3 "2026-09-29T15:09:22Z")

</div>

But I guess in this case these two don’t really apply?

There’s just `.init`, and it is called `.init`, rather than `.unlocked`. And Mutex has just a single field internally.

---

<div class="post-metadata">

**Author:** ![ScottRedig](https://ziggit.dev/user_avatar/ziggit.dev/scottredig/32/5880_2.png) [@ScottRedig](https://ziggit.dev/u/ScottRedig)\
**Post date:** [September 29, 2026, 5:40pm UTC](https://ziggit.dev/t/field-defaults/17755/4 "2026-09-29T17:40:42Z")

</div>

For that specific question, it shouldn’t be `.unlocked` because a user shouldn’t do, for example:

```zig
my_lock.lock();
defer my_lock = .unlocked;

```

That is, `.init` should only be used for initialization, so it is named as such.

In general, it seems the consensus of the core team is that default fields are only the way to do things in a few specific cases, and initialization of variables tends to not be one of them. The main “rule” is that you should not use default fields if setting one of the fields but not another has the potential for the struct to be initialized in an invalid state. This has cascaded in a general style change of using decl literals for “default” initialization across the std, which includes the mutex. Another advantage is that this gives an explicit API surface point of “this is how you initialize this” instead of just knowing that an empty initialization (`.{}`) is a usable construct.

As far as I understand it, the only reason default field values are still in the language at all is because they’re really useful for options structs.

---

<div class="post-metadata">

**Author:** ![pasta](https://ziggit.dev/user_avatar/ziggit.dev/pasta/32/6273_2.png) [@pasta](https://ziggit.dev/u/pasta)\
**Post date:** [September 29, 2026, 7:02pm UTC](https://ziggit.dev/t/field-defaults/17755/5 "2026-09-29T19:02:44Z")

</div>

Something that might remove the need for defaults is if you could do `.default with .{ .foo = 10 }`. I’m not sure the caveats and footguns of a system like that, but in the very least it’s trivially implementable in userspace.

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [September 30, 2026, 1:12am UTC](https://ziggit.dev/t/field-defaults/17755/6 "2026-09-30T01:12:11Z")

</div>

For uniformity sake, can we always make `init` a function? Making it an expression sometimes just increases what people have to remember.

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [September 30, 2026, 3:26am UTC](https://ziggit.dev/t/field-defaults/17755/7 "2026-09-30T03:26:38Z")

</div>

I would argue a function adds more cognitive overhead as functions imply running logic. If it just returns constant data why should it be a function instead of a constant?

---

<div class="post-metadata">

**Author:** ![ForeverZer0](https://ziggit.dev/user_avatar/ziggit.dev/foreverzer0/32/2443_2.png) [@ForeverZer0](https://ziggit.dev/u/ForeverZer0)\
**Post date:** [September 30, 2026, 3:39am UTC](https://ziggit.dev/t/field-defaults/17755/8 "2026-09-30T03:39:39Z")

</div>

They would be better to clarify than I, but I understood it as keeping the convention of the name `init` for a function, and use another name if it is a constant.

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [September 30, 2026, 3:59am UTC](https://ziggit.dev/t/field-defaults/17755/9 "2026-09-30T03:59:59Z")

</div>

Why? You can clearly tell if something is or isn’t a function call, and if it’s a declaration literal it is not allowed to be a function unless you are calling it (and it returns the type it is in).

---

<div class="post-metadata">

**Author:** ![tholmes](https://ziggit.dev/user_avatar/ziggit.dev/tholmes/32/5641_2.png) [@tholmes](https://ziggit.dev/u/tholmes)\
**Post date:** [September 30, 2026, 4:47am UTC](https://ziggit.dev/t/field-defaults/17755/10 "2026-09-30T04:47:45Z")

</div>

I think the issue here is where you’re working with something like a library you’re not super familiar with, and you want to know whether you can do `= .init;` without having to see compiler output.  
Obviously, it’s a more pronounced issue if you don’t have an IDE with autocompletion.

---

<div class="post-metadata">

**Author:** ![pasta](https://ziggit.dev/user_avatar/ziggit.dev/pasta/32/6273_2.png) [@pasta](https://ziggit.dev/u/pasta)\
**Post date:** [September 30, 2026, 10:23am UTC](https://ziggit.dev/t/field-defaults/17755/11 "2026-09-30T10:23:50Z")

</div>

Agreed. The same issue exists with `deinit` too, often the only clue you need to `.deinit(...)` is that the function exists, and sometimes you only need to call it if you’ve not used an arena, plus it’s plausible the struct’s internals may add or change that requirement in the future. This is, unfortunately, an evil of `RAII` and is best avoided if possible.

When not avoidable I’ve been experimenting with conventions to hint towards correct usage based on how parts of std are doing it, along the lines of:

- `.init(...)` indicates no sane default state exists without runtime logic, and that `.deinit(...)` exists but is only required if some other action has taken place on the struct that can have taken resource ownership
- `.empty`/`.default`/`.init` similarly indicate an optional `.deinit(...)` exists. The exact keyword is chosen for readability (`std.Io.Mutex` wouldn’t really make sense being “empty” or “default”)
- `.new(...)` or `.create(...)` indicate that `.deinit(...)` is _required_ (e.g. it closes a file)
- None of the above existing indicates that `.{}` is allowed and no `deinit` exists

---

<div class="post-metadata">

**Author:** ![pierrelgol](https://ziggit.dev/user_avatar/ziggit.dev/pierrelgol/32/10233_2.png) [@pierrelgol](https://ziggit.dev/u/pierrelgol)\
**Post date:** [September 30, 2026, 12:56pm UTC](https://ziggit.dev/t/field-defaults/17755/12 "2026-09-30T12:56:01Z")

</div>

agree, init is usually suppose to be “idempotent” and used to initialize something, I don’t think a function is necessary if it’s in the form of `Foo.init()` then it’s better to do `const foo : Foo = .init;` the function call is pointless since there’s no value to tweak the init with. I don’t think it should confuse anyone.

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [September 30, 2026, 4:22pm UTC](https://ziggit.dev/t/field-defaults/17755/13 "2026-09-30T16:22:02Z")

</div>

Expression contains logic too. The only difference is that an expression is required to be comptime known. Most of the times, this fact is irrelevant on the consumption side. Consider the following:

```zig
fn foo() void {
    const bar: Bar = .init();
    // ...
}

```

Why should `foo` care whether the result from `init` is always the same? The function should just work with whatever it receives. What is matter here is whether `bar` is invariant within the scope of the function. What happens beyond that, what `init` returns at a different time or a different place is totally irrelevant.

Forcing code to become dependent on incidental implementation details creates problems down the line. Suppose we need to change the initialization of `Bar` such that it’s slightly different when an env variable is set. If we had gone with an expression we would to make changes everywhere where it’s used.

---

<div class="post-metadata">

**Author:** ![pierrelgol](https://ziggit.dev/user_avatar/ziggit.dev/pierrelgol/32/10233_2.png) [@pierrelgol](https://ziggit.dev/u/pierrelgol)\
**Post date:** [September 30, 2026, 4:30pm UTC](https://ziggit.dev/t/field-defaults/17755/14 "2026-09-30T16:30:22Z")

</div>

that’s a fair point, when I think of a `const bar : Bar = .init;` I’m thinking this initialization is basically just the only reasonable value this can be. For example if you make a special counter, the only reasonable value is 0. or some neutral context fitting value. Same for many thing and in those cases, I think an init decl, makes more sense than a init function. But having said that there’s an argument, to name decls something else than `init` like `.empty` or `.default` which is a fair argument.

---

<div class="post-metadata">

**Author:** ![IceGuye](https://ziggit.dev/letter_avatar_proxy/v4/letter/i/b782af/32.png) [@IceGuye](https://ziggit.dev/u/IceGuye)\
**Post date:** [September 30, 2026, 8:19pm UTC](https://ziggit.dev/t/field-defaults/17755/15 "2026-09-30T20:19:57Z")

</div>

I think in this specific Mutex example, the type of the state field is not a primitive data or some sorts of simple data. It is an atomic Value with an enum type, which is a type function, and it also needs to init. You are not suppose to manually mutate the state atomic value. Even if you do, you will need an atomic operation. So for consistency, the Mutex may also want to have an init. Init also implies that you are going to call it at the beginning, and only once.

It’s also worth to mention that, literally, Zig allows you to do anything, but also Zig’s missions includes educating, reusable codes, and improving safety, so it’s good to have the standard libraries including some information or implication to guild the users to do the right thing. Let’s say if there is no init value or init() function, the users may do `var my_mutex: Mutex = .{ .state = .init(.locked) };`… This may cause problems, and this doesn’t look very right, right?

Simpler examples may use default, and it implies the users may directly change the fields by using any sorts of normal operation:

```zig
pub const Monster = struct { hp: i64 = 100, mp: i64 = 100};

```

For those simple things and I want to mutate to values whenever in whatever ways I want, then I would probably go with default.

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [September 30, 2026, 8:46pm UTC](https://ziggit.dev/t/field-defaults/17755/16 "2026-09-30T20:46:46Z")

</div>

`init` should be always a function because `deinit` is always a function. Maintaining that symmetry makes std simpler. If you want to use an expression, use a different word. `init` is taken and it’s short for `initialize`. What does `init` stand for when you do `const bar: Bar = .init`? You say `initializers`, but then what the heck does “bar is its initializers” mean?

---

<div class="post-metadata">

**Author:** ![pzittlau](https://ziggit.dev/letter_avatar_proxy/v4/letter/p/ecae2f/32.png) [@pzittlau](https://ziggit.dev/u/pzittlau)\
**Post date:** [September 30, 2026, 9:20pm UTC](https://ziggit.dev/t/field-defaults/17755/17 "2026-09-30T21:20:15Z")

</div>

I like `const foo = Foo.init` more than `const foo = Foo.init()` for a very simple reason: It makes clear that the initialization data is `comptime` known. This has a few advantages:

1. It makes the job of the compiler easier because it doesn’t need to that many optimization passes on it and just puts the resulting thing into `.rodata` and is done with it. It may even be able to do better optimizations, in matter of execution speed, by inlining this into `.text` directly near where it is used(though I personally don’t like putting data in `.text`) or realizing that it’s just a bunch of zeroes.
2. It makes the compilers job especially easier when `Foo` is generic. Because the compiler can more easily realize that all the instances of `.init` across the different monomorphizations of `Foo` are the same and just collapse them into one while doing semantic analysis(maybe even earlier I don’t quite know how Zig does it).
3. It makes the job of the programmer easier because they know that the initialization value doesn’t depend on any runtime global state\[1\].
4. It makes the job of the programmer easier because they can’t inadvertently introduce bugs by changing the initialization to depend on runtime global state. Because if they did the compiler would tell them so.

* * *

1. Of course assuming no `mmap`ing read-only things as read-write and changing it or stuff like that.

---

<div class="post-metadata">

**Author:** ![spiffyk](https://ziggit.dev/user_avatar/ziggit.dev/spiffyk/32/7202_2.png) [@spiffyk](https://ziggit.dev/u/spiffyk)\
**Post date:** [September 30, 2026, 9:34pm UTC](https://ziggit.dev/t/field-defaults/17755/18 "2026-09-30T21:34:16Z")

</div>

> [@chung-leong](#):
>
> What does `init` stand for when you do `const bar: Bar = .init`?

I read it as _initialized_, as in _an initialized value of `Bar`_. 🤷

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [October 2, 2026, 8:07pm UTC](https://ziggit.dev/t/field-defaults/17755/19 "2026-10-02T20:07:38Z")

</div>

> [@spiffyk](#):
>
> I read it as _initialized_, as in _an initialized value of `Bar`_. 🤷

Okay, let us goes with that. So `init` stands for `initialized`. Now, let us replace the abbreviation with the full word. In theory, that’d make our code clearer, right?

```zig
fn foo() void {
    const bar: Bar = .initialized;
    // ...
}

```

Is programmer’s intention in the above code clear? The answer is no, because the past-participate is often used for bit flags or enum. The reader is more likely to think that `Bar` is something like:

```zig
const Bar = enum { uninitialized, initialized, deinitialized };

```

We only know `.init` is not actually an enum literal because `init` exists as a function in other contexts (where arguments are required, for instance). It’s a construct that relies on its hypothetical alternative. It’s convoluted and stupid.

---

<div class="post-metadata">

**Author:** ![ForeverZer0](https://ziggit.dev/user_avatar/ziggit.dev/foreverzer0/32/2443_2.png) [@ForeverZer0](https://ziggit.dev/u/ForeverZer0)\
**Post date:** [October 2, 2026, 8:58pm UTC](https://ziggit.dev/t/field-defaults/17755/20 "2026-10-02T20:58:15Z")

</div>

I don’t feel strongly about it, but I do tend to agree, and use names such as `default`, `empty`, or whatever makes sense in the context, but avoid `init`.  
I just feel it clashes with the `init/deinit` and `create/destroy` conventions, and there seems to always be a better/equal alternative. There is probably some situation out there where that isn’t the case, but I have yet to encounter it.

[Next page](https://ziggit.dev/t/field-defaults/17755.md?page=2)
