What happenend to getting rid of the .{} syntax?

The related remove T{} syntax in favor of type coercion · Issue #5038 · ziglang/zig · GitHub is still open and now accepted, but get rid of the `.` in tuples & anonymous struct literal syntax · Issue #5039 · ziglang/zig · GitHub was apparently closed as not planned.

Does this simply mean that only what was originally proposed has been rejected (i.e. just removing the . in front of {}), or is there also no hope for the other proposals discussed there, especially replacing .{} with []?

1 Like

I don’t think .{} is going to be removed, but T{} might be. The dot in zig is pretty consistent when you think of it as “inherit the type from the result type”.

5 Likes

The accepted proposal was not for removing the dot, it was for removing the explicitly typed struct initializer form
const my_struct = MyStruct{};
and keeping type coercion from anonymous struct inits
const my_struct: MyStruct = .{};
const my_struct = @as(MyStruct, .{});
as the only way to initialize a struct.

The proposal for removing the dot from anonymous struct inits was rejected.

4 Likes

Yes, the dot could be thought of as representing a type that is omitted because it can be inferred. But it will make less sense when the T{} syntax is removed, because then it is not even a placeholder for anything anymore.

I don’t think it’s necessary to overthink the semantics of . in .{...}. .{...} is just a block used to express structure, and the dot in front is simply to distinguish it from code blocks without any markings. Any other symbol could be used to distinguish it from a code block; it’s just that the dot is the simplest, that’s all.

Edit: . here can be interpreted as the meaning of result type inference. This makes sense because in Zig these struct types themselves are namespaces, and we always use . as the namespace access operator. This is consistent with the perception of declaring literals.

Edit again: . can be understood as the magical character that triggers auto-completion in LSP lol, that’s really cool

5 Likes

pedantic but it is not coercion, it is type inference!

There used to be coercion from anonymous structs to concrete structs, but that was removed a while ago. IIRC they forgot to remove it when they planned so for a short time it was also a bug.

1 Like

This is why it is currently needed, but the .{} syntax is the ugliest possible way to achieve this. And when T{} disappears, the main justification for choosing the .{} syntax is gone; the other interpretations don’t make that much sense. It is essentially legacy cruft that will be even more confusing to people learning the language in the future without knowing where it came from.

But maybe it’s better to discuss this when T{} has actually been removed, this probably solves some ambiguities. I was just shocked when I saw that #5039 was simply closed.

I initially also thought [...] was better, but I no longer think so at all, because the experience of using . is now extremely consistent: pressing . basically triggers autocomplete (with the exception of a few cases like formatted strings where the experience is inconsistent), whether accessing struct members, accessing namespace declarations, or using result type inference. If someone told me now to not use . but to press [ to trigger autocomplete, I would actually find it inconsistent.

3 Likes

wow, the exact opposite of what I’d expect.

Can you explain further? I’ve just come to accept the ., but I don’t really get it. In my head, it’s just → this is what I must do to make the compiler happy. Not, oh, this makes sense.

1 Like

same here :slight_smile:

I do with that type inference was implemented such that we wouldn’t really have to explicitly specify the type like that all the time

same here :slight_smile:

First of all, I should mention that adding a snippet to expand .. to .{$0} basically fixed the issue for me. I no longer mind typing .{}, and I actually like reading .{}, that’s nice syntactic sign-post.

The reason why we need the leading . and don’t type just { .foo = bar } is syntactic ambiguity. Consider

     { while(false){} }
    .{ while(false){} }

The first one is a block expression with a while statement, the second is a tuple with void element. If we remove the leading ., then we’ll need some non-syntactic way to disambiguate the to.

Even { .foo = bar } is syntactically ambiguous, because the left hand size of = can be an expression, and .foo is an expression.

For completeness, {} vs .{} is another ambiguity here (instance of void type vs empty tuple), though arguably void can be an alias for struct {}.

These are not insurmountable difficulties, you can imagine adding enough heuristics to the parser to make it feel right basically 100% in practice, but that’s not the Zig way.

The reason for removing T{} is that:

  • It is redundant and doesn’t fit with the rest of the language
  • T[_]{} special case
  • RLS

Note how most languages have suffixes for integer literals (42ull, 92u64), but Zig doesn’t, and result type semantics (const x: u64 = 92) plays the equivalent role. .{} coheres better than T{} with results types, so, in Zig, if you want to keep only one syntax, you should prefer .{}.

The array length inference syntax, u32[_]{ 1, 2, 3 } is problematic because it’s a special case. u32[_] isn’t a type, it’s a separate grammatical category, which can be removed.

Finally, imo the strongest positive case for removal of T{} what that it neutralized RLS:

xs[0] = .{ .foo = 92 } constructs the struct in-place, xs[0] = Foo{ .foo = 92 } does a copy semantically. But now RLS is going away! I am immeasurably sad about that, but I also see how you can’t make that feature usable without something like a borrow checker.

11 Likes

The 1st line feels by far the cleanest to me, and the only one I ever use.
The 2nd line feels like I’m trying to override some default values from the struct with zero values.
The 3rd lines just looks like I’m trying to cast something into something it doesnt want to be, ugly indeed!

If we really have to mess with the syntax at this late stage, then why not simplify the default value case further and just have:

const my_struct: MyStruct;

Mostly I am a big fan of putting the type after the var or const (even in the simplest of cases). Maybe old-fashioned but very readable. I often see code like const x = .... and there is no clue what the type of x is at the callsite.

const my_struct: MyStruct = ...

Edit: Zig is kinda confusiing in that regard also. That strengthens my “always show the type”.

const x = @floatFromInt(42); // does not compile of course.

So what you mean is that if you have

const Foo = struct {
    a: u32,
    const b: Foo = .{ .a = 42 };
};

then when typing const foo: Foo = ., autocomplete offers both a and b?

Maybe it’s somewhat convenient but it does not really make much sense that it already offers a there, because it would not immediately follow the dot (it actually autocompletes to const foo: Foo = .{ .a = } instead). Arguably it is even annoying if there are a lot of functions/declarations in the struct, and now the fields are spammed in there too.

I think it would be fine if the first . would suggest only namespace declarations that would really follow the dot (b in this case), and if you want a struct literal you have to open it first and then type . to start autocompletion of the fields. And opening the struct literal would be even simpler if it was just [ instead of the awkward .{. It’s not the purpose of autocomplete to workaround bad language syntax.

1 Like

Fun fact. I’ve started to follow Zig project after seeing this feature: “typeless” int literals. I have so much pain and introduced bugs with forgotten “ull” in C/C++.

2 Likes

In the current ZLS implementation, its autocomplete does include a, but in the UI, the distinction between struct members and declared literals is very clear. However, this is just the ZLS implementation; in principle, LSP could also provide more complete autocompletion like .{ .a = }.

Edit:
I think your argument is strong. The autocomplete here is not a necessary moment (although I must admit that the autocomplete at this moment makes me feel very comfortable and happy). It is reasonable to manually express the intention to construct a struct.
Now I am back to the [...] camp.

I also feel regret about this, because I believe that as long as the two issues #476 and #1108 are resolved, RLS can be implemented safely.

Even if #1108 is not implemented, I think RLS can still have a safe space. E.g., for the initialization of constants and variables, RLS can be executed automatically and implicitly, whereas for the reassignment of variables, RLS needs to be executed explicitly.