Some languages (e.g. TypeScript, Haskell) support a shorthand syntax to avoid repeating the field name. I am surprised to find no discussion on this topic, but is there any reason Zig couldn’t support something like this?
The only reason I can think of “why not” is that it is redundant. It would mean there would be two ways to do something, which zig is trying to avoid.
It’s not a compelling argument, but what is equally important is why should zig implement this?
it is just syntax sugar. It would be most useful in the pattern you use as an example, constructors.
But zig discourages this pattern in favour of using the literal syntax, in such cases it’s arguably more likely that variables with the same name as the fields do not exist, at least in simple cases.
It’s actually a Javascript feature, not limited to Typescript
Personally I would love to see many more convenience features from JS/TS when working with structs in Zig (especially “proper” JS-like destructuring and spreading). I think most of those JS syntax features would also work fine in a statically typed language like Zig or even C.
For instance I’d like to be able to do things like with structs:
const a: Bla = .{ .x = 1, .y = 2, .z = 3 };
const b: Bla = .{ ...a, .y = 4 }; // assign content of 'a' with some 'overrides'
const { x, y, z } = b; // destructure b.x, b.y, b.z into x, y, z
…or for arrays and slices:
const a: [_]u8 = .{ 1, 2, 3, 4, 5, 6, 7 };
const [x, y, ...z] = a; // x=1, y=2, z is a slice [3..]
…especially the struct spreading would go nicely with the ‘new’ .init pattern, the only problem is the awkward syntax if ... is used as the spread operator:
// use default .init values with some non-default overrides:
const a: Bla = .{ ... .init, .x = 123, .y = 234 };
…in general I would put much more ‘syntax focus’ on working with structured data, since programming is all about transforming data from one shape into another shape
I think I’d rather have spread as a builtin (with RLS being used to infer the type of init when possible) rather than introducing another operator, something like:
@merge(init: T, overrides: anytype) T
const a: Bla = @merge(.init, .{ .x = 123, .y = 234 });
Yes, this could be done in userspace right now but you lose the ergonomics and optimisations afforded by RLS.
That would work too, but personally I think there’s already way too much @ noise in the language.
Some of the proposed syntax sugar could also be implemented in comptime functions, but this smells too much like the C++ stdlib philosophy to me (e.g. trying to move language features into the stdlib).
Of course it’s fair to say to not ‘waste’ syntax for what can be implemented in the language itself (which is especially true for Zig’s comptime features - see the ‘interface’ discussion), this philosophy at least keeps the language surface minimal. The downside though is often code that doesn’t look ‘aesthetic’ (which of course is also highly subjective).
Personally, I feel that relying on syntactic sugar with the same symbol names is a bit fragile. I don’t really expect struct field names to match the input symbol names, although it sometimes happens, but depending on it makes me a bit uneasy.
That’s fair. I recall your post about writing emulators in Zig from a while back. Depending on what I’m writing, I often lean that way as well. Right now anything that mixes numerical types is a pain. Hopefully ranged integers and implicit upcasting to floats (e.g. a u32 doesn’t lose any information by being cast to an f64, so no explicit cast needed) will land at some point.
My problem with this being an operator is that it’s not clear where it should go and how it should behave. Let’s use # as the operator for the sake of this discussion, because it doesn’t have any meaning in Zig at the moment and is easy to type. Your example would translate to:
const a: Bla = .{ #.init, .x = 123, .y = 123 };
To me, it’s weird for #.init to be within the brackets, as it’s feels like a very different kind of construct to .x = 123 to .y = 123. Furthermore should RLS propagate to .init? What if .init is a function, not a decl literal? Should try be allowed?
I think this is pretty reasonable looking, but it still has problems. It needs the result type to propagate through #, and that’s not the case for any other operator at the moment other than try. And that’s not even getting into precedence rules for a new operator.
IMO, a builtin that infers the return type is going to be more consistent with the rest of the language, and try would just work without having to add a special case for a new operator.
It’s been pointed out that the specific idea is already syntactically defined as a tuple of enum literals, so that isn’t available.
As for something like it, there are a few precepts from the Zen which apply:
Communicate intent precisely.
Favor reading code over writing code.
Only one obvious way to do things.
Zig is not completely averse to syntactic shorthand, the ‘decl literals’ which are a relatively recent addition are well-liked. They mean that if you have a Foo with a declaration Foo.empty, Zig now accepts this:
.foo = .empty,
As well as
.foo = Foo.empty,
While that’s two ways to do things, the first one is intended to be the obvious way, and this does spare repetition, much like the shorthand you’re referring to.
There’s an open issue (yes, I opened it) for spreading arrays in switch prongs, and clearly some interest in implementing it. This one actually lets us do a new thing which is untenable in status quo Zig. It doesn’t seem impossible that whatever is decided for spreading would uhhhhhhh spread to other parts of the syntax. The awkwardness you mention, of ... .init, is avoided with my most-preferred syntax, .init[...].
LHS destructure-by-field has come up before, the general sentiment was that keeping track of memory and other resources is too important to allow fields to be elided, that’s just asking for trouble. Doing it for all fields is less attractive, and there’s also the temptation to dereference a returned pointer straight into fields:
Which leaks (it would be less obvious than this in reality). It works for tuples because those are almost never used for anything other than multiple return values. It would be pretty nice to be able to destructure a tuple inside |a, capture|, but that one also can get confusing when combined with the existing facility using | and ,: I’ve written Julia and Python code which does this kind of thing, you end up counting commas and thinking really hard about what is going into which.
In general it’s hard to find syntactic shorthands which are explicit both when writing it, and when reading it later. I’m glad Zig emphasizes the latter, because once you learn it, reading it is never more confusing than what the code does, and that property is extremely rare in a language.