Blocks and Let Bindings
Blocks are sequences of statements enclosed in braces that evaluate to a value. They provide scoped computation with intermediate bindings, making complex expressions readable and composable.
Block Syntax
A block contains zero or more let bindings followed by a final expression.
The block evaluates to the value of the final expression:
{
let a = 10;
let b = 20;
a + b
}
This block evaluates to 30.
Let Bindings
The let keyword introduces a named value within a block. Each binding is
terminated by a semicolon:
{
let price = m_item_price;
let quantity = m_item_count;
let subtotal = price * quantity;
let tax = subtotal * 8 / 100;
subtotal + tax
}
Bindings are immutable – once assigned, a let variable cannot be
reassigned.
Types of Bindings
let bindings take no type annotation; the type is inferred from the
right-hand side. Use a cast when you need a particular width:
let count = 42; // inferred as integer
let name = "hello"; // String
let bal = 0 as U256; // explicit width via cast
Blocks as Expressions
Because blocks are expressions, they can appear anywhere a value is expected:
In Let Bindings
let fee = {
let rate = if vip { 1 } else { 3 };
amount * rate / 100
};
In Member Transforms
Blocks are especially useful in member transforms for multi-step state computations:
m_balances: HashMap<address, U256> {
in transfer(to, amount) => {
let sender = msg::sender;
let sender_bal = m_balances[sender];
let to_bal = m_balances[to];
m_balances
.insert(sender, sender_bal - amount)
.insert(to, to_bal + amount)
}
}
In Pure Functions
Pure function bodies are blocks:
pure fn compute_fee(amount: U256, rate: u64) -> U256 {
let basis_points = rate as U256;
let fee = amount * basis_points / 10000;
fee
}
In If/Else Branches
Each branch of an if/else is a block:
let result = if complex_condition {
let x = compute_a();
let y = compute_b();
x + y
} else {
let fallback = get_default();
fallback * 2
};
Scope
Variables declared with let are visible only within their enclosing block:
let x = 10;
let result = {
let y = 20;
x + y // evaluates to 30
};
// y is not visible here
A later let may reuse a name that is already in scope. The new binding
shadows the old one from that point on; the earlier value is not modified:
{
let x = amount + 1;
let x = x * 2;
x
}
Inside a nested block used as a value (let r = { … };), use a fresh name:
re-binding an outer name there does not yet compile on EVM.
Blocks in Route Actions
Inside route action lists, let bindings can be used to compute intermediate
values:
routes {
swap(amount: U256) => [
let fee = amount * 3 / 100;
let net = amount - fee;
Transfer(net) ~> m_recipient
Transfer(fee) ~> m_treasury
]
}
let bindings (like var captures and emit) end with a semicolon. Other
actions, such as sends, are simply written one after another with no
separator.