-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
The combination of implicit semicolons after certain statements and the implicit return at the end of a block (e.g. a function body) causes ambiguity.
Currently these are all equivalent:
fn abs(x: f32) -> f32 {
if (x < 0.0) {
return -x;
} else {
return x;
}
}
fn abs(x: f32) -> f32 {
return if (x < 0.0 {
-x
} else {
x
};
}
fn abs(x: f32) -> f32 {
if (x < 0.0) {
-x
} else {
x
}
}
fn abs(x: f32) -> f32 {
return if (x < 0.0) -x else x;
}
fn abs(x: f32) -> f32 {
if (x < 0.0) -x else x
}However, this is problematic because of the ambiguity that comes from an implicit semicolon after some statement-level constructs.
fn main() {
if (a) {
foo();
} else {
bar();
} // no semicolon needed here
if (b) {
bar();
} // or here
}The implicit semicolon is valuable because C has so thoroughly conditioned us to expect compound statements that use {} to end at the }. But where does the implicit semicolon really go? What about these examples:
fn main() {
if (a) { foo() } else { bar() }
if (b) { bar() }
if (a) foo() else bar()
if (b) bar()
if (a) foo else bar
(); // does this call bar()?
if (b) bar
{} // does this struct-initialize bar{}?
( bar )();
{ bar }(); // what does this mean?
if (b) { bar }(); // what does this mean?
// this should probably be a syntax error, but it currently isn't:
comptime 1 1;
}Proposal:
When one of the following "compound statement"-like constructs appears at block scope, it can optionally qualify for an implicit semicolon if it uses {} around its "body"-like component (details below):
switch: already always uses{}, so always qualifies for implicit semicolon.{...}: a nested block at block scope always qualifies for implicit semicolon.while,for: putting{}around the body qualifies for implicit semicolon.comptime: putting{}around the expression qualifies for implicit semicolon.if,try: putting{}around the "else" body, if any, otherwise around the "then" body qualifies for implicit semicolon.
If a statement qualifies for an implicit semicolon, the statement ends at the } and cannot be its containing-block's return expression. In other words, the implicit semicolon is always effectively present, not optionally present if necessary.
Here are some examples of this proposal in action:
fn abs(x: f32) -> f32 {
// no {}, so no implicit semicolon, so this is the return expression:
if (x < 0.0) -x else x
}
fn abs(x: f32) -> f32 {
// this `if` is not at statement level, so these {} don't earn any implicit semicolons:
return if (x < 0.0) {
-x
} else {
x
};
}
fn abs(x: f32) -> f32 {
// this statement gets a semicolon, and so does nothing
if (x < 0.0) {
-x
} else {
x
}
// error for not returning an f22
}
fn main() {
{ bar }(); // syntax error for empty `()`
{ bar };(); // equivalent to the above
if (b) { bar }(); // syntax error for empty `()`
if (b) { bar };(); // equivalent to the above
comptime 1 1; // syntax error
comptime {1} 1; // ok
}
fn getKindName(kind: Kind) -> []const u8 {
// need the return here because switch uses {}
return switch (kind) {
KindA => "A",
KindB => "B",
};
}A separate idea to consider is to require that the "then" and "else" bodies if if and try agree on use of {}. That would simplify this proposal slightly, but is really a separate idea.