// The two are less consistent.
const v = T{.x = 1, .y = true}; // why not T.{.x = 1, .y = true} ?
const v: T = .{.x = 1, .y = true};
// The two are more consistent.
const v = T.declOrTag;
const v: T = .declOrTag;
My understanding:
This is assigning a T struct to v.
const v = T{.x = 1, .y = true};
This is an anonymous struct being assigned to v, which gets coerced to a T.
const v: T = .{.x = 1, .y = true};
So it wouldn’t really make sense to have T.{.x = 1, .y = true}
The dot is a stand-in for the type. It’s <thing>{ .x = 1 }, and <thing> can be either a type name, or a dot.
No it does not get coerced, rather the anonymous expression infers the type based on where you put it
If there is no type to infer then zig generates a type that matches the expression, this type is distinct, it does not coerce to other structs/tuples.
It did coerce previously, but that was a bug
The syntax is consistent, other syntax that infers the type also uses a .: .foo, .init()
not to mention T{} and .{} do have different semantics; T{} makes a new temporary instance that is then written to the variable/parameter, however, .{} writes directly to the variable/parameter.
This does affect program behaviour, for example
const S = struct { x: u8, y: u8 };
// initial value doesnt matter which syntax you use
var a: S = .{ .x = 4, .y = 10 };
a = S{ .x = a.y, .y = a.x }; // swaps as you'd expect
a = .{ .x = a.y, .y = a.x }; // does not swap
// the above is equivalent to this:
a.x = a.y;
a.y = a.x; // a.x already overwritten with a.y
I was admittedly not aware of this nuance. I always favor the const thing: Thing = .{ ... } way, as it was my impression it was the preferred style, I didn’t realize there was semantic differences too.
I dont think it is decided yet, but it is worth mentioning there has been talk of removing the T{} syntax.
for initialising a variable the semantic difference does not matter, and the optimiser likely produces the same or similar machine code regardless.
remove T{} syntax in favor of type coercion · Issue #5038 · ziglang/zig
This proposal has been accepted.
I always saw the . in front of { as placeholder for an inferred type. From that perspective the syntax makes sense.
It’s still incosistent of course, because then const v: T = .declOrTag; should be const v: T = ..declOrTag; … which I guess is sort of an argument hat .{ should just be { heh.
Huh, I wasn’t aware of that either. That would indeed be unexpected and weird, in my mind, both are a merged init-assignment and both should generate the exact same code (which they probably do).
in most cases yes, but in cases where the semantic difference matters, they will ofc generate different code.
This proposal being accepted means that there is no way to implement swapping (or anything that assigns while using, via an internal temporary instance) without a more verbose way.
// Before
a = S{ .x = a.y, .y = a.x };
// After
const b :S = .{ .x = a.y, .y = a.x };
a = b;
Then I realized Zig doesn’t support (a, b) = (b, a) either so eliminating S{...} is actually another point of consistency:
// Instead of
( a, b ) = ( b, a )
// Either
const tmp = a;
a = b;
b = tmp;
// Or
std.mem.swap( u8, &a, &b );
std.mem.swap literally does the tmp dance (or a align-aware version of it).
Apparently a bunch of tickets relating to this behavior: Design flaw: Swapping struct fields yields unexpected value · Issue #12064 · ziglang/zig · GitHub.
This is why zig should add constructors, so that we could have a default ctor and a copy ctor… and maybe a move ctor? That of course would bring in the need for assignment operators, and operator overloading… Wait.
</joke> – in case it wasn’t obvious.
Yeah, this doesn’t seem to work like one would hope.
pub fn main() !void {
var a, var b = .{ @as(u8, 1), @as(u8, 2) };
std.debug.print("a={}, b={}\n", .{ a, b });
a, b = .{ b, a };
std.debug.print("a={}, b={}\n", .{ a, b });
}
a=1, b=2
a=2, b=2
Phew, you had me for a second there…
It was too good an opportunity to pass by ![]()
I understand this is one of the main reasons zig ultimately decided to remove RLS, but honestly, even though I know languages like Python support doing this, for a low-level language, allowing variables to be swapped this way feels really counterintuitive to me. I think it’s totally reasonable not to support swapping in this way.
Adding to this, I also think that swapping variables doesn’t happen so often, that there is the need to support this as a special case. It’s easily implemented in user-space in a small function or in just 3 lines of code that are self-explanatory, while, I admit, being a bit of nuisance to type when coming from higher level languages.
I think I’ve swapped a and b a total of 0 times in my lifetime. Maybe I’m doing programming wrong… ![]()
But people do seem to come to Zig and write that line expecting it to do a swap, and getting surprised it doesn’t act as they expected.
So it feels Zig should either:
- Support it (probably the easiest option)
- Make this an error (it would have to either catch in the general case without false positives, which seems to me it would be difficult, or have a really weird special case)
- Make it a warning (except Zig doesn’t do those)
- Accept the surprising behavior here (probably the worst option)
Maybe if zls gave a warning about this option 4 could be acceptable.
- Detect and provide a suitable error saying “this isn’t supported - use
std.mem.swapinstead.”