-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
get rid of the .
in tuples & anonymous struct literal syntax
#5039
Comments
This would be nicer to type, but the ambiguity with blocks could lead to some unintuitive compile errors. Consider this example: fn voidFn() void {}
export fn usefulError(foo: bool) void {
if (foo) {
voidFn() // forgot semicolon here
} else {
// ^ current compile error: expected token ';', found '}' on first token after forgotten semicolon
voidFn();
}
} Here I've forgotten the semicolon on the expression in the if statement, and the compiler gives an error at the first token after the missing semicolon. But with this proposal, the missing semicolon would cause the block to be interpreted as a tuple, equivalent to this code: fn voidFn() void {}
export fn weirdError(foo: bool) void {
if (foo) .{
// ^ error: incompatible types: 'struct:42:15' and 'void'
// ^ note: type 'struct:42:15' here
voidFn() // forgot semicolon here
} else {
// ^ note: type 'void' here
voidFn();
}
} For a forgotten semicolon, this is a very confusing error to get, especially for someone new to the language who hasn't learned anonymous struct syntax yet. This might be a solvable problem by identifying cases where this parsing is close to ambiguous and improving the error messages that result, but the two interpretations are different enough that it might be difficult to give an error message that is meaningful for both. |
Oh, I think there's also an ambiguous case. These two statements are both valid but mean very different things: fn voidErrorFn() error{MaybeError}!void {}
comptime {
var x = voidErrorFn() catch .{};
var y = voidErrorFn() catch {};
} |
Good point on the ambiguous case, and good explanation in the other comment. For the ambiguous case I'd be willing to change |
Mixing blocks and tuples&structs is the very reason I don't like this proposal. I often forgets the brackets |
nothing - this is pure syntax bikeshedding 🚴 |
Hmm, that would definitely work, but it also might have the side effect of unexpectedly converting fn voidErrorFn() error{MaybeError}!void {}
export fn foo() void {
voidErrorFn() catch {
//commentedOutCall();
};
} to fn voidErrorFn() error{MaybeError}!void {}
export fn foo() void {
voidErrorFn() catch .{};
} when the programmer tries to test commenting out that line. This could potentially be solved, but we would have to go down kind of a strange road. Zig currently makes a distinction for many types of blocks between expressions and statements. The if in |
Maybe we can change block syntax, #4412 |
OK, I'm convinced to close this in favor of status quo. |
Another option - maybe already discussed, I couldn't find any discussion about it yet - is to use |
Inspired by
|
A really cool but probably bad option is to define the single value of void as the empty struct. This would remove the ambiguity since it doesn't matter if |
I suggest to reconsider about this proposal. As @andrewrk noticed in 0.6.0 release note, 0.7.x maybe the last chance to make a bigger change in zig to polish this language more elegant, intuitive, simpler. There are a lot of proposals related to this: #4294 always require brackets, so it reduces use cases about block and resolves issue #1347. Also related to #5042, #1659 which can remove #4412 new keyword #4170 shows in order to keep consistency, anonymous funtion literal has a weird syntax. #5038 removes T{}, also related to #4847, array default initialization ayntax. #4661 remove bare IMO we should take all these things into account. Here's the ideally syntax what I prefer to: // named block
const x = block blk {
var a = 0;
break :blk a;
};
block outer while {
while {
continue :outer;
}
}
// unnamed block
block {
var a = 0;
assert(a+1 == 1);
}
// switch
const b = switch {
a > 0 => 1,
else => 0,
}
const b = switch var a = calc() {
a == 0 || a == 1 => 0,
else => 1,
};
switch var a = calc(); a {
.success => block {
warn("{}", {a});
},
else => @panic(""),
}
// while
var i: usize = 0;
while {
i += 1;
if i < 10 { continue; }
break;
}
assert(i == 10);
var i: usize = 1;
var j: usize = 1;
while i * j < 2000; i *= 2, j *= 3 {
const my_ij = i * j;
assert(my_ij < 2000);
}
const x = while var i = 0; i < end ; i += 1 {
if i == number { break true; }
} else { false }
while var i = 0; getOptions(i) => v; i += 1 {
sum += v;
} else {
warn("", {});
}
// for
for seq => v {
warn("{}", {v});
}
// if
if var a = 0; a != b {
assert(true);
} else if a == 9 {
unreachable;
} else {
unreachable;
}
const a = if b { c } else { d };
const x: [_]u8 = if a => value {
{value, 1, 2, 3, 4}
} else block blk {
warn("default init", {});
break :blk {0, 1, 2, 3, 4};
}
// error
const number = parseU64(str, 10) catch { unreachable };
const number = parseU64(str, 10) catch { 13 };
const number = parseU64(str, 10) catch block blk {
warn("", {});
break :blk 13;
};
const number = parseU64(str, 10) catch err switch err {
else => 13,
};
fn foo(str: []u8) !void {
const number = parseU64(str, 10) catch err { return err };
}
if parseU64(str, 10) => number {
doSomethingWithNumber(number);
} else err switch err {
error.Overflow => block {
// handle overflow...
},
error.InvalidChar => unreachable,
}
errdefer warn("got err", {});
errdefer err if errFormater => f {
warn("got err: {}", {f(err)});
}
// tuple & anonymous literals
var array: [_:0]u8 = {11, 22, 33, 44};
const mat4x4: [_][_]f32 = {
{ 1.0, 0.0, 0.0, 0.0 },
{ 0.0, 1.0, 0.0, 1.0 },
{ 0.0, 0.0, 1.0, 0.0 },
{ 0.0, 0.0, 0.0, 1.0 },
};
var obj: Object = {
.x = 13,
.y = 67,
.item = { 1001, 1002, 1003 },
.baseProp = { .hp = 100, .mp = 0 },
}; |
@SpexGuy Similar option: syntactically
@mogud I haven't read all those issues but the design looks quite messy...
|
@iology sorry for those type mistakes, I've edited the post. const x: [_]u8 = if a => value {
// disallow multiple statements?
// -> no, this is and only can be a single expression
{value, 1, 2, 3, 4}
} else block blk {
warn("default init", {});
// can it be just {0,1,2,3,4} (without semicolon)?
// -> no, only catch/if/else used as expression can have a single expression within `{}`.
break :blk {0, 1, 2, 3, 4};
} |
@mogud I truly do appreciate what you're doing here, but I don't think the syntax is going to go that direction. |
That's ok, zig itself is more important for me. :) |
As a newcomer to Zig I would also encourage not dropping this issue (syntax of tuples - or anonymous structs?). I have spent many hours being confused by the current syntax (it seems kind of unique to zig, and not in a good way) and trying to find out how to loop over |
Let us re-evaluate this in light of #14523. |
My opinion on this is that the status quo syntax should remain. While I'm very glad that ZON as a concept has made it in, I strongly believe the Zig syntax should be tailored first and foremost to the source language, provided it doesn't cause significant usability issues in ZON. In this case, I believe that holds; the leading Blocks and struct literals are fundamentally quite different, and it doesn't make sense to me to unify them. For instance, in self-hosted, we specifically removed support for Another problematic case is singleton tuples. These are used quite frequently, for instance in many uses of The only way I would be in support of a syntax change here would be if instead of simply dropping the leading |
I fully agree with @mlugg . In the pas few weeks, I have been "teaching" I mention that because we often have the comment that this syntax is weird and could make the language adoption harder, but it is not weird, it is uncommon, but I really think it is the right syntax. Also as it was pointed out, there are edge cases like tuples which could turn this into something complicated to implement. Finally, I'll add that I write quite a lot of elixir, where the map (which are used for struct) syntax is |
It seems that if you have experience in zig already then everything looks fine for you, but as a newcomer I need to say these random thoughts:
Phew, I'm sure this was already discussed million times but I just needed to say all this somewhere 🤣 |
My humble opinion is that the outermost dot and brackets |
agree with @shanoaice . although might be a little off topic, i wonder if we could get rid of the . in dereferencing and unbox optional as well. |
I heavily dislike that syntax idea; I happen to find the analogy with field access quite nice, especially for optionals where the payload more-or-less is a field. But more to the point, even without the issue of |
I actually like that idea. [_]T
// or
void However, without any type coercion, the type of stdout.writer().print(
"No ambiguity?\n",
{} // coerced to array
) catch {}; // void
fn func(array: [0]u8, x: void) void {
// …
}
const a: [_]u8 = {}; // coerced to array
const b: void = {}; // void
const c = {}; // void
// fn(array, void)
func(a, b);
func({}, b);
func(a, {});
func({}, {});
switch (a.len) {
0 => {}, // void
else => unreachable,
}
|
Alternatively, the big cases where
I think that covers about 99.9% of the cases. We could make |
The current syntax is a lot clearer to read. |
If we represent tuples as having at least one comma That representation also aligns with how Python defines its tuples; Currently, And @deflock 2nd point is quite valid. |
@OSuwaidi |
@RaphiSpoerri I'm sure Andrew initiated this issue in the first place for a reason. |
As someone who's trying to learn Zig coming from more than a decade of writing Go, I really like Zig as a language for its pragmatism, clarity, and relative simplicity, especially compared to Rust. But as with Rust, I find the syntax and the "ergonomics" to be somewhat weird. The leading dot thing just hurts my eyes every time I look at it :) I understand the problem, and the reasons for having it, and it's probably something you get used to over time, but it just goes against my personal sense of beauty. I could probably live with I also dislike the mandatory semicolons, and the mandatory parenthesis for This is of course very biased, but I wish new languages took more inspirations from Go (more than just |
It is very understandable and I held same opinion as you. It goes away after 10kloc. Afrer 10kloc of zig you will go to github issues and write the same reply which I am writting right now, defending the syntax. Though maybe same will happen after they remove it :D |
I disagree. I think it’s noisy and unpleasant to read, especially since it’s asymmetric. It’s usually clear from the context if something is a block or a tuple, so I would prefer writing .{.foo = bar} Here, I’m not a fan of the {foo = bar} |
I'm not sure if it's possible to do this, but here's an issue at least to document that we tried.
I believe the current problem is that when the parser sees
It still doesn't know if this is is a block or tuple. But the next token would either be
,
making it a tuple,;
making it a block, or}
making it a tuple. Maybe that's OK.Then tuples & anonymous struct literals will be less noisy syntactically and be easier to type.
The text was updated successfully, but these errors were encountered: