Browse the docs

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#

TEXT
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* '}'
  • Condition is 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 if is a statement: its branches produce no value. See if as a value below for the expression form.

  • The condition must have type bool (SEM0011). Integers are not truthy.

  • else if chains to any depth. else may start on the line after the closing brace.

  • An if whose then block and else branch both end with return (or a call returning never) ends the path. The function needs no further return.

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 in else or 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 move before 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 &expr or &mut expr borrows 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 let and plain if may be mixed in one else if chain.

if as a value#

  • An if or if let whose else chain ends in a plain else { ... } may appear wherever an expression is expected: a binding initializer, a return operand, 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 if produces the value of the branch that runs. Only that branch runs.

  • An if without a final else is not a value, because the path where every condition is false produces nothing. Using one as a value is PAR0009, which suggests adding else { ... }.

  • There is no conditional operator. c ? a : b is PAR0010: "Silk has no c ? a : b; write an if with a final else as the value, as in if 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 never adds 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 as u8.

  • An if in statement position stays a statement, even with a final else. A branch that ends in a non-unit expression there is an ignored result (SEM0087).

  • Static bodies do not evaluate if values.

Value blocks#

  • A block whose value is used, which is a branch of an if used as a value or an arm block of a match used as a value, runs its statements and then produces its last expression. The last expression counts when its type is neither () nor never.

    silk
    return match n {
      0 => 0
      _ => {
        let doubled = n * 2
        doubled + 1
      }
    }
  • A block without such a last expression produces (), or never when every path leaves it with return, break, continue or fail.

  • 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 { } and while let are the runtime loops. while tests the condition before each iteration.

  • There is no for, loop or do/while. A statement loop { } is PAR0006; write while true { }. loop is not reserved and may still name a binding. for outside static for is a parse error.

  • while let Pattern = e { } evaluates e before each iteration and runs the body while the value matches Pattern. The first mismatch ends the loop. It behaves as while true { if let Pattern = e { … } else { break } }.

  • Its subject follows the if let rules. A call result needs no move. A named non-Copy subject needs move, 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 = &e and while let P = &mut e borrow the subject on each iteration. A binding borrowed this way can't be moved out of the iteration (OWN0006).

  • A while true loop with no break that targets it never completes. It ends the path like a never call: fn f() -> never { while true { } } checks, and code after the loop is unreachable.

  • A break that leaves the loop, even in a nested if, makes it complete. A non-unit function that can then fall off its end is SEM0130. A break of an inner loop does not count.

  • Only the literal condition true (parentheses allowed) is recognized. while t { } with let t = true completes as usual.

  • To visit a slice or array, keep a usize index and compare it with .length. Indexes must be usize (see Structs, tuples and arrays).

break and continue#

  • break leaves the innermost enclosing while or while let. continue skips to its next condition test, or to the next evaluation of the while let subject.

  • Neither takes a label or a value. There are no loop labels. break 5 is break followed by the expression statement 5 (SEM0087).

  • Using either outside a loop is SEM0038.

  • break and continue may appear in a match arm block inside a loop, including a match used 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 break or return.

  • Moving a value inside a while condition and then using it after the loop is reported as use after move (OWN0001), even when the moving branch breaks.

return#

  • return Expression leaves the current function with that value. The value must match the declared result (SEM0129).

  • A bare return is allowed only directly before }; it returns (). Anything else after return is parsed as its value.

  • A function body with no final return gets an implicit return (). In a function whose result is not (), a path that reaches the end is SEM0130.

  • Statements after a return are accepted without a diagnostic.

never and diverging calls#

  • never is the result type of a computation that does not complete. A -> never function must not reach its end (SEM0130). Ending it with a while true loop that has no break satisfies this.

  • A call returning never may stand alone as a statement. It ends the path, so no return is needed after it.

  • return stop(code) is allowed in a function of any result type, and let x: i32 = stop(1) type-checks.

  • For failing with a typed error instead, see Typed failures.

Short-circuit evaluation#

  • a && b evaluates b only when a is true. a || b evaluates b only when a is false.

  • Both operands must be bool. 1 && true is 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#

silk
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
}
silk
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
  }
}
silk
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:

silk
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#

CodeMeaning
PAR0006A statement written loop { ... }; use while true { ... }
PAR0009An if used as a value without a final else
PAR0010The conditional operator c ? a : b; write if c { a } else { b }
SEM0006Unknown value, for example an if let binding used outside its block
SEM0011Condition is not bool
SEM0038break or continue outside a loop
SEM0087Expression statement whose result is not () or never
SEM0129return value does not match the declared result
SEM0130A reachable path falls off the end of a non-unit function
SEM0135Operator does not accept these operands, for example && on integers
SEM0226if let on a reference; match its referent with &r.*
OWN0001Use of a binding after it was moved
OWN0003A non-Copy value is consumed without move
OWN0005Loop body leaves owned values live differently than the next iteration expects
OWN0006A 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

View the source of this page