Control flow.
if, if let, while and while let are statements. An if or if let whose else chain ends in a final else may also be used as a value. This page covers conditionals, if values and value blocks, loops, break, continue, return, the never type and short-circuit evaluation.
Syntax#
IfStatement = 'if' Condition Block ( 'else' ( IfStatement | IfLetStatement | Block ) )?
IfLetStatement = 'if' 'let' Pattern '=' Condition Block ( 'else' ( IfStatement | IfLetStatement | Block ) )?
WhileStatement = 'while' Condition Block
WhileLetStatement = 'while' 'let' Pattern '=' Condition Block
BreakStatement = 'break'
ContinueStatement = 'continue'
ReturnStatement = 'return' Expression?
IfExpression = ( 'if' Condition | 'if' 'let' Pattern '=' Condition ) Block
( 'else' ( 'if' Condition | 'if' 'let' Pattern '=' Condition ) Block )* 'else' Block
StaticIf = 'static' 'if' Condition Block ( 'else' Block )?
Condition = Expression
Block = '{' Statement* '}'Conditionis an ordinary expression, except that a bare record literal is not allowed in it. Wrap one in parentheses:if p.x == (Point { x: 1 }).x { }.Inside a call's argument list the restriction does not apply:
if f(Point { x: 1 }) { }parses.Parentheses around a condition are allowed but not needed.
Conditionals#
In statement position
ifis a statement: its branches produce no value. Seeifas a value below for the expression form.The condition must have type
bool(SEM0011). Integers are not truthy.else ifchains to any depth.elsemay start on the line after the closing brace.An
ifwhosethenblock andelsebranch both end withreturn(or a call returningnever) ends the path. The function needs no furtherreturn.
if let#
if let Pattern = expr { } else { }runs the first block when the value matches the pattern. Names bound by the pattern are visible only in that block, not inelseor after the statement (SEM0006).Any pattern from Patterns and match works:
Option.Some { value: at },Left { value },Shape.Circle { radius }.Matching a non-Copy value consumes it. A named variable needs
movebefore it:if let Option.Some { value } = move found { }. Without it the compiler reports OWN0003.A temporary, such as a call result or a constructed value, needs no
move:if let Option.Some { value } = find(x) { }.Matching
&expror&mut exprborrows instead. Bindings are then shared or exclusive views of the parts.A value of reference type cannot be matched directly (SEM0226). Match its referent:
if let Status.Bad { code } = &status.* { }.if letand plainifmay be mixed in oneelse ifchain.
if as a value#
An
iforif letwhoseelsechain ends in a plainelse { ... }may appear wherever an expression is expected: a binding initializer, areturnoperand, an argument, an array element, a match arm, or an operand of another expression.silk let sign = if n > 0 { 1 } else if n < 0 { -1 } else { 0 } return if let Option<i32>.Some { value } = move input { value } else { 0 }The
ifproduces the value of the branch that runs. Only that branch runs.An
ifwithout a finalelseis not a value, because the path where every condition is false produces nothing. Using one as a value is PAR0009, which suggests addingelse { ... }.There is no conditional operator.
c ? a : bis PAR0010: "Silk has noc ? a : b; write anifwith a finalelseas the value, as inif c { a } else { b }".Branch results join like match arms: equal types keep that type, distinct nominal types form a union, and a branch of type
neveradds nothing. Types that cannot join are SEM0049.An expected type, such as a binding annotation or a parameter type, reaches every branch, so
let small: u8 = if flag { 1 } else { 2 }types both literals asu8.An
ifin statement position stays a statement, even with a finalelse. A branch that ends in a non-unit expression there is an ignored result (SEM0087).Static bodies do not evaluate
ifvalues.
Value blocks#
A block whose value is used, which is a branch of an
ifused as a value or an arm block of amatchused as a value, runs its statements and then produces its last expression. The last expression counts when its type is neither()nornever.silk return match n { 0 => 0 _ => { let doubled = n * 2 doubled + 1 } }A block without such a last expression produces
(), orneverwhen every path leaves it withreturn,break,continueorfail.The value is computed before the block's locals are cleaned up, so it may move a local out but may not borrow one.
Blocks in statement position do not produce values.
Loops#
while Condition { }andwhile letare the runtime loops.whiletests the condition before each iteration.There is no
for,loopordo/while. A statementloop { }is PAR0006; writewhile true { }.loopis not reserved and may still name a binding.foroutsidestatic foris a parse error.while let Pattern = e { }evaluatesebefore each iteration and runs the body while the value matchesPattern. The first mismatch ends the loop. It behaves aswhile true { if let Pattern = e { … } else { break } }.Its subject follows the
if letrules. A call result needs nomove. A named non-Copy subject needsmove, and the body must give the binding a new value before the next iteration (OWN0005 otherwise). A mismatch consumes it too, so it is moved after the loop (OWN0001).while let P = &eandwhile let P = &mut eborrow the subject on each iteration. A binding borrowed this way can't be moved out of the iteration (OWN0006).A
while trueloop with nobreakthat targets it never completes. It ends the path like anevercall:fn f() -> never { while true { } }checks, and code after the loop is unreachable.A
breakthat leaves the loop, even in a nestedif, makes it complete. A non-unit function that can then fall off its end is SEM0130. Abreakof an inner loop does not count.Only the literal condition
true(parentheses allowed) is recognized.while t { }withlet t = truecompletes as usual.To visit a slice or array, keep a
usizeindex and compare it with.length. Indexes must beusize(see Structs, tuples and arrays).
break and continue#
breakleaves the innermost enclosingwhileorwhile let.continueskips to its next condition test, or to the next evaluation of thewhile letsubject.Neither takes a label or a value. There are no loop labels.
break 5isbreakfollowed by the expression statement5(SEM0087).Using either outside a loop is SEM0038.
breakandcontinuemay appear in amatcharm block inside a loop, including amatchused as a value:drop match i { 4 => { break } _ => 0 }.
Loops and moves#
Each iteration must start with the same set of live owned values. A body that moves an outer value and does not reassign it before the next iteration is rejected with OWN0005.
Reassigning the moved binding before the iteration ends fixes it. So does leaving the loop right after the move with
breakorreturn.Moving a value inside a
whilecondition and then using it after the loop is reported as use after move (OWN0001), even when the moving branch breaks.
return#
return Expressionleaves the current function with that value. The value must match the declared result (SEM0129).A bare
returnis allowed only directly before}; it returns(). Anything else afterreturnis parsed as its value.A function body with no final
returngets an implicitreturn (). In a function whose result is not(), a path that reaches the end is SEM0130.Statements after a
returnare accepted without a diagnostic.
never and diverging calls#
neveris the result type of a computation that does not complete. A-> neverfunction must not reach its end (SEM0130). Ending it with awhile trueloop that has nobreaksatisfies this.A call returning
nevermay stand alone as a statement. It ends the path, so noreturnis needed after it.return stop(code)is allowed in a function of any result type, andlet x: i32 = stop(1)type-checks.For failing with a typed error instead, see Typed failures.
Short-circuit evaluation#
a && bevaluatesbonly whenaistrue.a || bevaluatesbonly whenaisfalse.Both operands must be
bool.1 && trueis SEM0135.Ownership analysis follows the short circuit. A move in the right operand may not have happened (see Ownership and moves).
static if#
static if Condition { } else { }selects a branch during compilation. See Static control flow and reflection.The parser also accepts
static for Name in Expression { }; see Static control flow and reflection.
Examples#
fn classify(value: i32) -> i32 {
if value < 0 {
return -1
} else if value == 0 {
return 0
} else {
return 1
}
}
fn sumPositive(values: &[i32]) -> i32 {
let mut total = 0
let mut index: usize = 0
while index < values.length {
let value = values[index]
index = index + 1
if value < 0 {
continue
}
if value > 100 {
break
}
total = total + value
}
return total
}
pub fn main() -> i32 {
let values = [4, -2, 6, 500, 7]
if classify(-5) != -1 || classify(0) != 0 {
return 1
}
if sumPositive(&values) != 10 {
return 2
}
return 0
}import silk.core.option { Option }
fn find(values: &[i32], wanted: i32) -> Option<usize> {
let mut index: usize = 0
while index < values.length {
if values[index] == wanted {
return Option.Some { value: index }
}
index = index + 1
}
return Option.None
}
fn positionOf(values: &[i32], wanted: i32) -> usize {
if let Option.Some { value: at } = find(values, wanted) {
return at
}
return values.length
}
pub fn main() -> i32 {
let values = [4, 8, 15]
if positionOf(&values, 15) != 2 {
return 1
}
if let Option.Some { value } = find(&values, 99) {
return 2
} else {
return 0
}
}fn bump(counter: &mut [i32], answer: bool) -> bool {
counter[0] = counter[0] + 1
return answer
}
pub fn main() -> i32 {
let mut calls = [0]
if false && bump(&mut calls, true) {
return 1
}
if true || bump(&mut calls, true) {} else {
return 2
}
if calls[0] != 0 {
return 3
}
if true && bump(&mut calls, false) {
return 4
}
return calls[0] - 1
}A while let loop that stops at the first None:
import silk.core.option { Option }
fn next(at: usize, values: &[i32]) -> Option<i32> {
if at < values.length {
return Option<i32>.Some { value: values[at] }
}
return Option<i32>.None
}
pub fn main() -> i32 {
let values = [4, 5, 6]
let mut at: usize = 0
let mut sum = 0
while let Option.Some { value } = next(at, &values) {
sum = sum + value
at = at + 1
}
if sum != 15 {
return 1
}
return 0
}Diagnostics#
| Code | Meaning |
|---|---|
| PAR0006 | A statement written loop { ... }; use while true { ... } |
| PAR0009 | An if used as a value without a final else |
| PAR0010 | The conditional operator c ? a : b; write if c { a } else { b } |
| SEM0006 | Unknown value, for example an if let binding used outside its block |
| SEM0011 | Condition is not bool |
| SEM0038 | break or continue outside a loop |
| SEM0087 | Expression statement whose result is not () or never |
| SEM0129 | return value does not match the declared result |
| SEM0130 | A reachable path falls off the end of a non-unit function |
| SEM0135 | Operator does not accept these operands, for example && on integers |
| SEM0226 | if let on a reference; match its referent with &r.* |
| OWN0001 | Use of a binding after it was moved |
| OWN0003 | A non-Copy value is consumed without move |
| OWN0005 | Loop body leaves owned values live differently than the next iteration expects |
| OWN0006 | A binding borrowed from a while let or if let subject escapes its block |
See also#
Bindings and statements · Expressions and operators · Patterns and match · Ownership and moves · Program entry and termination · Effects and run · Typed failures · Services and providers · Static evaluation · silk/core/option