What is the rule for when a semicolon is needed?

In the two if-blocks shown below, the second needs a semicolon to compile.
I failed to find the rule for when a semicolon is needed.

pub fn main() void {
	var x: ?u8 = 5;
	
	if (x) |a|
		if (a > 0)
			std.debug.print("{}\n", .{a});

	if (x) |a|
		if (a > 0) {
			std.debug.print("{}\n", .{a});
		} // need a ; here
}
1 Like

One is an if expression, the other is an if statement.

Basically, if the “if” is followed by a block it does not need a semicolon, otherwise it does. The commented semicolon in your example terminates the outer if, which has no block. The inner if has a block and does not need the semicolon.

5 Likes

You mean the inner ones are expressions, and the outer ones are statements. Right? (Reasonable)

Play it more:

This compiles:

pub fn main() void {
	var x: ?u8 = 5;
	
	if (x) |a|
		if (a > 0)
			std.debug.print("{}\n", .{a})
		else
			std.debug.print("{}\n", .{a})
	;

	if (x) |a|
		if (a > 0) {
			std.debug.print("{}\n", .{a});
		} else {
			std.debug.print("{}\n", .{a});
		}
	;
}

This also compiles:

pub fn main() void {
	var x: ?u8 = 5;
	
	_ = if (x) |a|
		if (a > 0)
			std.debug.print("{}\n", .{a})
		else
			std.debug.print("{}\n", .{a})
	
	;

	_ = if (x) |a|
		if (a > 0) {
			std.debug.print("{}\n", .{a});
		} else {
			std.debug.print("{}\n", .{a});
		}
	;
}

This compiles too:

pub fn main() void {
	var x: ?u8 = 5;
	
	if (x) |a|
		_ = if (a > 0)
			std.debug.print("{}\n", .{a})
		else
			std.debug.print("{}\n", .{a})
		;

	if (x) |a|
		_ = if (a > 0) {
			std.debug.print("{}\n", .{a});
		} else {
			std.debug.print("{}\n", .{a});
		}
	;
}

But this doesn’t:

pub fn main() void {
	var x: ?u8 = 5;
	
	_ = if (x) |a|
		_ = if (a > 0)
			std.debug.print("{}\n", .{a})
		else
			std.debug.print("{}\n", .{a})
	
	;

	_ = if (x) |a|
		_ = if (a > 0) {
			std.debug.print("{}\n", .{a});
		} else {
			std.debug.print("{}\n", .{a});
		}
	;
}

So a if-block can be used both as expression and statement. Whether or not it is sued as expression/statement is by context. Reasonable.

I don’t know whether or not my understanding is correct. It looks to me that the similar situation is for {}. Sometimes, it is an expression, sometimes, it is a statement.

pub fn main() void {
	var x: ?u8 = 5;
	var y: void = {}; // here, {} is an expression
	
	if (x) |_| y;
	if (x) |_| {}             // here, {} is a statement
	_ = if (x) |_| {};        // here, {} is an expression
	if (x) |a| if (a > 0) {}; // here, {} is an expression
}

just want to add a link to related topic

1 Like

I think that in this case, the entire if is the statement, not needing ; because it has a block, albeit an empty one {} which is an expression that evaluates to void.

If {} is an expression here, then why does the previous line if (x) |_| y; need a semicolon, but the if (x) |_| {} line doesn’t need?

It seems that blocks do not allow semicolons after them.

{
    var x: u8 = 3;
    x += 1;
} // try to add a semicolon here, you'll get an error

{} // here too

blk: {
    break :blk;
} // here too

So blocks are expressions, but have special syntactical treatment when it comes to semicolons it seems. Also, the empty block {} which evaluates to void is the only expression you can ignore without error.

Just checked Zig documentation. It does mentions this speaking. It also mentions “statement” many times, but doesn’t give the word a definition. The fact that it mentions “statements” and “expressions” together many times indicates the two words have the same meaning as other languages.

My current understanding is the speaking “blocks are expressions” is not accurate. Instead, “blocks can be used as expressions” is more accurate. Much code shows that blocks are not always expressions. The following is another example:

	var y: void = {}; // as expression
	y; // legal only if y is void
	{} // as statement. Here, ; is not allowed.

The following is another case in which {...} is absolutely not an expression:

	var n:usize = 5;
	_ = switch (n) blk: { // error: expected '{', found 'an identifier'
		else => break: blk,
	};

After some research, here I try to make a clearer answer (than @neurocyte).

After adding the required ;, the quoted code can be re-written as

pub fn main() !void {
	var x: ?u8 = 5;
	
	if (x) |a|
		if (a > 0)
			std.debug.print("{}\n", .{a})
	;

	if (x) |a|
		if (a > 0) {
			std.debug.print("{}\n", .{a});
		}
	;
}

The two inner if-blocks block both act as expressions (of type void). In fact, by ignoring the output, the re-written code is equivalent to

pub fn main() !void {
	const y: void = {};
	
	var x: ?u8 = 5;
	
	if (x) |_|
		y
	;

	if (x) |_|
		y
	;
}

In fact, without the final semicolons, the two outer if-blocks are also both expressions. The above code can further re-written as

pub fn main() !void {
	const y: void = {};
	
	var x: ?u8 = 5;
	_ = x;
	
	y;

	y;
}

The final code compiles, but only if the type of y is void.


(I do still have some confusions on why sometimes a block can’t be followed by a semicolon.)