A block is a sequence of statements inside braces.
{
const x = 1;
const y = 2;
_ = x + y;
}Blocks are used for function bodies, if bodies, loop bodies, and local scopes. A name declared inside a block exists only inside that block.
const std = @import("std");
pub fn main() void {
{
const x = 10;
std.debug.print("{d}\n", .{x});
}
// x is not visible here
}This is ordinary scoping. The inner block gives x a place to live. When the block ends, the name is gone.
A block can also produce a value. This is one of the important rules in Zig. A block is not only a place to put statements. It can be used as an expression.
const value = blk: {
const a = 20;
const b = 22;
break :blk a + b;
};The value of value is 42.
The label blk names the block. The statement
break :blk a + b;leaves the block and gives it a value.
The label can have any valid name.
const result = answer: {
break :answer 100;
};Here result is 100.
This form is useful when a value needs more than one step to compute.
const std = @import("std");
pub fn main() void {
const n = 7;
const kind = classify: {
if (n == 0) break :classify "zero";
if (n < 0) break :classify "negative";
break :classify "positive";
};
std.debug.print("{s}\n", .{kind});
}The output is:
positiveA block expression has a type, just as every expression has a type. All exits that give a value must agree on the type.
const x = blk: {
if (true) break :blk 1;
break :blk 2;
};Both 1 and 2 are integer values, so this is fine.
This is wrong:
const x = blk: {
if (true) break :blk 1;
break :blk "one";
};One branch gives an integer. The other gives a string. Zig will reject it.
A block that does not give a value has type void.
const x = {};This is legal, but x has type void, which is rarely useful as a stored value.
A block can be used to limit the lifetime of temporary names.
const area = compute: {
const width = 8;
const height = 5;
break :compute width * height;
};After this block, width and height are no longer visible. Only area remains.
This keeps programs small and local. Names should live only where they are needed.
Blocks also work well with defer.
const std = @import("std");
pub fn main() void {
{
defer std.debug.print("leaving block\n", .{});
std.debug.print("inside block\n", .{});
}
std.debug.print("after block\n", .{});
}The output is:
inside block
leaving block
after blockA defer statement runs when control leaves the block. It does not wait until the whole function ends unless it is declared in the outer function block.
Blocks are simple, but they are used everywhere in Zig. They give scope. They group statements. They can compute values. They also decide when deferred work runs.
Exercise 3-1. Write a block expression that sets x to 10 + 20.
Exercise 3-2. Write a block expression that classifies an integer as "small" or "large".
Exercise 3-3. Put a defer inside an inner block and observe when it runs.
Exercise 3-4. Try to return two different value types from one block expression and read the compiler error.