When assigning to such a union, Zig lets the programmer choose to assign the tag, the value, or both at once. Unions support all possible combinations, but optionals have one case I can’t figure out:
Not exactly. Optionals of certain types, particularly pointers, are optimized to use special values for null instead of a tag. Pointers can simply be set to 0, like in C. I think this might be the root of your confusion.
I don’t think so. You cant guarantee that x != null in this case, since x could be anything. Of course, in debug mode undefined gets written to a set value, but according to the definition of undefined, this is not exactly correct.
As I understand it, you can’t set an optional value to “not null” without just giving it a value. If you don’t care about the value just use some sensible zero value.
In practice it works for Debug and ReleaseSafe (where undefined sets the value with 0xaa) but does not work for ReleaseSmall or ReleaseFast (it is null).
I wouldn’t say so. It’s a common pattern in Zig to get a pointer to uninitialized data and assign to it later (see Allocator.create(), etc). In fact, the langref explicitly acknowledges the usefulness of this pattern—it’s under “To change the active field of a union when a meaningful value for the field is not known, use undefined”
What you’re observing is Zig’s lack of true default values. That’s a deliberate design decision. By “true” I mean that the proposition “for all T, there is a default T of a specific state defined by the language”. In Zig, there isn’t.
In the case where T does have a complete default value, you can write it like this:
const a_value: ?T = .{};
The master branch recently added declaration literals, which going forward will be the preferred way to initialize something to a known-good starting state.
But Zig, unlike several other languages, does not have a well-defined default value for a type, unless all the fields are explicitly defined to have default values. So there isn’t an equivalent of what you’re looking for in all cases, only in some.
For some ?T where every combination of bits interpreted as a value of type T is a valid T, optionals could be thought of as something like struct { is_null: bool, payload: T }. Under this model, you could indeed initialize the is_null tag and the payload separately.
But for some ?T where some combination of bits interpreted as a a value of type T is not a valid T, such as the value 0 for a (non-allowzero) pointer type like *u8, optionals could be thought of something like enum(T) { null, ... }, where the fictional syntax ... enumerates every possible valid value for T. Under this model, there’s no tag, only a special sentinel value; the value is either a valid T and thus not null, or the invalid sentinel value selected to represent null.
Are you saying union(enum)s are not eligible for niche optimization? I’m aware the compiler doesn’t perform this optimization today, but I thought the plan was to fix that.
I still don’t believe there is a way to set a non-null value for any ?T without simply setting it to a value of T. I’m also not convinced that it is necessary, I would be grateful if you could provide a use case.
I was wrong, I did not know you could do this. However, .? is still only acting as an assertion, I don’t think it can be used to strictly access “just the value” of foo, because that’s not what it does semantically.
So we’ve established something here: This is a bad idea, and always run tests in ReleaseFast mode if trying to figure out what kind of weird stuff you can actually get away with.
All of this passes in both Debug and ReleaseFast mode.
I don’t think real code exists where doing things this way would not be strongly preferred. You can always assign a T to a ?T. So if you need to build a T up by parts, you don’t need to create it as a ?T, create it as a T and: return it, pass it, or assign it to a ?T.
I’ve broken off the conversation, as IMO it had veered away from answering the question and into more brainstorming discussion.
Long and Short of it is, As of right now, I don’t know if there is a good, stable, and guaranteed way to do what you are asking to do. Others are free to posit other solutions, or we can select one of the above answers as the “solution”.