Reference / Statements
Statements
A statement is one step of a block: a declaration, an assignment, a loop, a jump or an expression. The statements of a block run in order, one after the other.
fn main() {
let names = ["Aino", "Eero", "Ilona"]
let mut total = 0
for name in names {
total += name.len()
}
print(total) // 13
}
A line break ends a statement, and a ; separates two statements on one line (Lexical structure). An expression can also stand as a statement (Expressions).
let
let declares a binding: a name for a value. The binding cannot change. The name is in scope from the statement after the let to the end of its block.
fn main() {
let width = 6
let height: i64 = 7
print(width * height) // 42
}
The rules:
- A
letalways has a value. Without a type name, the binding gets the type of its value (Types). - With a type name, the value must have that type (E0032). When the type name is a
T?, aTvalue becomes aT?(Types). - The value must be an expression that has a value. Expressions lists the expressions without one (E0043).
- The value cannot read the name that the
letdeclares. That name starts after thelet(E0033). - An assignment to a binding of a
letis an error (E0001). To change it, declare it withlet mut.
fn main() {
let count = 1
count = 2 // error[E0001]
}
E0001: what this error means
let mut
let mut declares a binding that an assignment can change.
fn main() {
let mut count = 0
count = count + 1
count += 1
print(count) // 2
}
A let mut binding must change somewhere in its scope. When no assignment changes it or one of its elements, the program builds with a warning (W0002).
A binding in live code that no expression reads builds with a warning (W0014). For a let mut that nothing reads, this warning takes the place of W0002. A name that starts with _ gets no warning.
let _
let _ = value calculates the value and then drops it. It declares no binding. Use it to drop a value on purpose, with no warning (W0004).
fn tell(n: i64) -> i64 {
print("told {n}")
return n
}
fn main() {
let _ = tell(3) // told 3
}
The rules:
_is not a name, solet mut _is an error (E0078)._is not a value. Reading it, or an assignment_ = value, is an error (E0079).- As for any
let, the value must be an expression that has a value (E0043).
Shadowing
A let can take the name of a binding or a constant that is already in scope. The new binding hides the old one until the end of its block. It can have a different type.
fn main() {
let n = 5
let n = n * 2
print(n) // 10
if n > 5 {
let n = "large"
print(n) // large
}
print(n) // 10
}
A binding can also take the name of a function, such as print, or of a class. A call still finds the function or the class, because a binding is never called.
const LIMIT = 3
fn main() {
print(LIMIT) // 3
let LIMIT = 4
let print = LIMIT * 2
print(print) // 8
}
The rules:
- The value of the new
letreads the old binding, because the new name starts after thelet. - A
letcan shadow a parameter. - A
let mutmust change before a laterletin the same block shadows it (W0002). - Each pass of a loop body runs its
lets again, so each pass gets new bindings. - No binding or parameter can take the name
Option,Result,Some,OkorErr(E0026).Noneis a keyword. - No binding or parameter can take the name of an enum (E0116).
Assignment
An assignment place = value stores a value in a place. These are the places:
- a binding of a
let mut - a
mutfield of an object (Classes) - an element of an array in one of these places, such as
xs[i]orgrid[i][j](Arrays)
class Counter {
mut hits: i64
}
fn main() {
let mut xs = [1, 2, 3]
xs[0] = 10
let c = Counter(hits: 0)
c.hits = 5
print(xs[0] + c.hits) // 15
}
The rules:
- The value must have the type of the place (E0032). Where the place is a
T?, aTbecomes aT?. - As for a
let, the value must be an expression that has a value. Sox = panic("stop")is an error (E0043). - A binding without
mut, such as one of aletor afor, cannot change. Neither can a parameter, a field withoutmutor a constant (E0001). - To write a
mutfield, the binding that holds the object does not needmut. To write an element, the array must be in a place that can change. - A place cannot go through
?.(E0022). An expression that is not a place, such asa + 1, is an error (E0021). - A function is not a place (E0074).
- An assignment is a statement. It has no value, so it cannot be part of an expression, and
a = b = 1is an error.
The program calculates the place first, then the value. It checks an index against the length of the array after it calculates the value.
fn tell(n: i64) -> i64 {
print("value {n}")
return n
}
fn main() {
let mut xs = [0, 0]
xs[5] = tell(3) // value 3
print(xs[0])
}
sisu: panic at example.sisu:8:8: index 5 out of bounds for length 2
Compound assignment
A compound assignment place op= value is place = place op value. The operators are +=, -=, *=, /= and %=.
fn main() {
let mut n = 10
n += 5
n *= 2
n %= 7
print(n) // 2
let mut text = "sau"
text += "na"
print(text) // sauna
}
The rules:
- The operator follows the rules of its arithmetic operator.
+=on astringjoins two strings. - The program calculates the place once. So in
xs[next()] += 1,nextruns once. - It reads the place before it calculates the value. So an index that is out of bounds panics before the value is calculated.
- An integer overflow or an integer division by zero stops the program with a panic, as the operator does (Operators).
- The place cannot be an optional, such as an
i64?(E0032). - A compound assignment to a
charis an error, because arithmetic does not apply to achar(E0094).
fn main() {
let mut letter = 'a'
letter += 1 // error[E0094]
}
E0094: what this error means
Constants
A const declares a constant: a name for a value that sisu calculates when the program builds. A constant is declared at the top level, next to the functions.
const WIDTH = 6
const HEIGHT: i64 = 7
const AREA = WIDTH * HEIGHT
const GREETING = "Hello, " + "sauna"
fn main() {
print(AREA) // 42
print(GREETING) // Hello, sauna
}
The rules:
- A constant is an
i64, au8, anf64, abool, acharor astring(E0085). - Its value can use literals, operators, the conversions that cannot fail, the constants of
f64and the constants above it (E0086). - A call, a binding, an array, an
if, a block or an interpolation in the value is an error (E0086). This applies also to a side of&&or||that the program would skip. - A constant can use only the constants that are declared above it, and not itself (E0087).
- A value that would panic, such as an integer overflow, is an error when the program builds (Operators). So is a conversion that can fail (E0100).
- A function can use a constant that is declared below it.
- Functions, classes, enums and constants share one set of names (E0034).
- A constant cannot take the name of a built-in type, or
Option,Result,Some,OkorErr(E0026). - A constant cannot take the name of a built-in function, such as
print(E0034). It cannot be named_(E0080). The sections dbg and Files are in development, and they change this rule. - A constant cannot change (E0001), and a
constin a function is an error (E0084). Useletthere.
const TOTAL = PART + 1 // error[E0087]
const PART = 2
fn main() {
print(TOTAL)
}
E0087: what this error means
while
A while loop runs its body again and again while its condition is true. The program tests the condition before each pass, also before the first.
fn main() {
let mut n = 1
while n < 100 {
n *= 3
}
print(n) // 243
}
A while let loop tests a pattern in place of a condition (Enums and patterns).
The rules:
- The condition must be a
bool(E0032). - A
whilehas no value. Abreakthat ends awhilecannot have a value (E0141). - A
whilecan warn when its condition reads locals that the loop does not change (W0011). The diagnostic page tells when it warns. - When W0011 warns about a
let mutthat never changes, W0002 does not warn about it. - A
whilewith the conditiontruebuilds with a warning (W0009). Writeloopfor an endless loop.
fn main() {
let mut n = 0
while true { // warning[W0009]
n += 1
if n == 3 {
break
}
}
print(n) // 3
}
W0009: what this warning means
fn main() {
let mut i = 0
let n = 3
while i < n { // warning[W0011]
print(i)
}
}
W0011: what this warning means
for
A for loop runs its body once for each element of an array, or for each integer of a range. On each pass, its name holds the next element.
fn main() {
for name in ["Aino", "Eero"] {
print(name)
}
for i in 0..3 {
print(i)
}
for i in 1..=2 {
print(i * 10)
}
}
Aino Eero 0 1 2 10 20
A range start..end holds the integers from start up to end, without end. A range start..=end holds end too.
The rules:
- The source of a
foris an array or a range (E0035). An optional array must be unwrapped first (E0062). A range is valid only in aforand in a pattern (E0023). The section Loops over iterables is in development, and it changes the rules for the source and for a range. - The two ends of a range are
i64(E0032). Whenstartis not less thanend,start..endis empty. Whenstartis more thanend,start..=endis empty. start..=endalso works whenendis the largesti64.- The program calculates the source once, before the first pass. For a range, it calculates
start, thenend. - The loop visits the elements that the array has when the loop starts. A change to the array in the body does not change them.
- The name is a binding without
mut, so an assignment to it is an error (E0001). Its scope is the body. for _ inruns the body and binds no name.- A
forhas no value. Abreakthat ends aforcannot have a value (E0141).
fn main() {
let mut xs = [1, 2, 3]
for x in xs {
xs[0] = 100
print(x)
}
let mut n = 0
for _ in 0..n + 2 {
n += 1
}
print(xs[0] + n)
}
1 2 3 102
Loops over iterables In development
A for loop also takes a value that conforms to Iterable, such as the result of an iterator method (Iterators). A range is a value, so a binding can hold it.
fn main() {
let r = 0..3
for i in r {
print(i) // 0, 1, 2
}
for x in [1, 2, 3].map(x => x * 2) {
print(x) // 2, 4, 6
}
}
loop
A loop runs its body again and again, until a break ends it. A loop is also an expression: a break with a value gives the loop that value (Expressions).
fn main() {
let mut n = 0
loop {
n += 1
if n == 3 {
break
}
}
print(n) // 3
}
A loop that no break ends runs again and again. Only a return, a panic, or a jump to a loop around it can leave it. Only a break that ends the loop gives it a value.
fn main() {
loop {
print("again")
}
}
break and continue
break ends the loop around it. continue ends the current pass, and the loop goes on with its next pass. In a while, the next pass tests the condition first.
fn main() {
for i in 0..10 {
if i % 2 == 0 {
continue
}
if i > 6 {
break
}
print(i)
}
}
1 3 5
The rules:
- Without a loop label,
breakandcontinueact on the innermost loop around them. breakandcontinueoutside a loop are an error (E0038). They never leave a function.- The header of a loop is the condition of a
whileor the source of afor. A jump in the header cannot act on that loop or on a loop around it (E0082). - A loop inside a header has a body of its own, and its jumps can act on it.
- Only a
breakthat ends aloopcan have a value (E0141).continuenever has a value.
Loop labels
A loop label is a name and a : before a loop. break name and continue name act on the loop with that label, also from an inner loop.
fn main() {
rows: for row in 0..3 {
for col in 0..3 {
if col > row {
continue rows
}
if row == 2 {
break rows
}
print("{row} {col}")
}
}
}
0 0 1 0 1 1
The rules:
- A loop label can be before
loop,whileorfor, and on a line of its own. Before anif, amatchor a block, it is an error (E0145). So is the loop label_. - A loop cannot take the loop label of a loop around it (E0143). Two loops one after the other can have the same loop label.
- At a jump that names a loop label, no binding, function, constant, class, enum or built-in type in scope can have that name (E0142).
- A
continuethat names no loop label around it is an error (E0144). - A loop label that no
breakorcontinuenames builds with a warning (W0008).
After break, a name is a loop label when a loop around the break has that loop label. Then the rest of the statement is the value. Otherwise, the name starts the value.
After a loop label, the value cannot start with a token that only continues an expression, such as + or .. A ( or [ right after the loop label needs a space before it, as in break outer (x) (E0142).
fn main() {
let a = outer: loop {
break outer -1
}
let outer = 10
let b = loop {
break outer -1
}
print(a) // -1
print(b) // 9
}
return
return value ends the function and gives its value to the caller. return alone ends a function that has no return type. A return leaves all the loops around it.
fn find(xs: [i64], wanted: i64) -> i64 {
for i in 0..xs.len() {
if xs[i] == wanted {
return i
}
}
return -1
}
fn main() {
print(find([4, 8, 15], 8)) // 1
print(find([4, 8, 15], 9)) // -1
}
The rules:
- In a function with a return type, each
returnhas a value of that type (E0036, E0032). Where the type is aT?, aTbecomes aT?. - In a function without a return type, a
returnhas no value (E0037). - The value must be an expression that has a value, so
return panic("stop")is an error (E0043). - A
returnfrom the first call ofmainends the program. Another call ofmainreturns to its caller (Functions).
Reachability
Some statements never finish, so the code after them cannot run. A statement never finishes when its type is never, and Types lists these expressions. These are the statements of the type never:
return,breakandcontinue- an expression of the type
never, such as a call ofpanicortodo(Functions), or aloopthat nobreakends - a
whilewhose condition is the literaltrue, with nobreakthat ends it
A block with such a statement never finishes either. A statement after it cannot run, and the program builds with a warning at the first one (W0001).
fn main() {
print(1) // 1
return
print(2) // warning[W0001]
}
W0001: what this warning means
The end of a function
A function with a return type must not reach the end of its body without a value. Each path through the body must end in a value or in a statement that never finishes (E0031).
fn first_even(xs: [i64]) -> i64 {
for x in xs {
if x % 2 == 0 {
return x
}
}
} // error[E0031]
fn main() {
print(first_even([3, 8]))
}
E0031: what this error means
The rules:
- A
for, and awhilewith a condition that is nottrue, can always finish. This applies also when its body always returns. - An
ifwithout anelsecan always finish. - The literal
trueis the only condition that makes awhilenever finish, also in parentheses.while 1 == 1and a constant that istruecan finish. - A
breakin an inner loop ends only that loop, so it does not end the outerlooporwhile true. Abreakwith the outer loop's label does. - Each
breakcounts, also one that cannot run, such as abreakafter areturn. - A function without a return type can reach the end of its body. Then it returns to its caller.
fn first_even(xs: [i64]) -> i64 {
let mut i = 0
loop {
if xs[i] % 2 == 0 {
return xs[i]
}
i += 1
}
}
fn main() {
print(first_even([3, 8, 5])) // 8
}
Sisu