Language / if / else

if / else

The if / else construct provides branching based on a boolean condition.

if is available both as a statement and as a value expression. Both forms evaluate the condition once and execute only the selected branch.

Surface Syntax#

Minimal form:

if <condition> {
  ...
}

With an else:

if <condition> {
  ...
} else {
  ...
}

Value expression#

A value-position conditional has an expression at the end of each branch:

let label = if ready {
  "ready"
} else {
  "not ready"
};

An expression form requires else, and both branch values must have one compatible type. The selected value becomes the result; the other expression is not evaluated. This contract applies to strings, scalars, optionals, results, and the other value categories accepted by the checker and hosted lowerer.

Lowering a value-position conditional is type-directed before it emits either branch. Type classification must not evaluate the condition or leave partial control flow behind. The condition is therefore evaluated exactly once even when the result binding omits an explicit type annotation, and values borrowed by the condition (including string views) remain valid after the conditional.

if let (Pattern-Destructuring Statement Form)#

Silk also supports an if let statement form for refutable pattern matching without introducing a separate match expression:

if let <pattern> = <scrutinee> {
  ...
} else {
  ...
}

if let mut <pattern> = <scrutinee> {
  ...
}

Notes:

  • The scrutinee expression is evaluated exactly once.
  • The pattern binders (for example Some(v) binds v) are in scope only in the then block.
  • if let mut marks binders introduced by the pattern as mutable in that scope, so they may be reassigned like ordinary let mut locals.
  • else is optional (when omitted, a non-matching scrutinee executes no block).
  • else if let ... chains are supported and parse as nesting in the same way as else if ....
  • else let ... is supported as shorthand for else if let ...; else let mut has the same binder mutability as else if let mut.
  • if let move ..., else if let move ..., and else let move ... consume the scrutinee for ownership-tracked values. The consumed source binding is not available in the then block, the else block, or after the if.

if let chains (&& let)#

The if let statement form supports a short-circuiting && chain that mixes refutable let clauses and ordinary boolean clauses:

if let Some(x) = get_x() &&
   x > 0 &&
   let mut Ok(v) = get_value(x) {
  // `x` and `v` are in scope here.
  v = v + 1;
  return v;
} else {
  // `x` and `v` are NOT in scope here.
  return 0;
}

Semantics:

  • Clauses are evaluated left-to-right and short-circuit like &&.
  • A let <pattern> = <expr> clause evaluates <expr> exactly once:
  • if the pattern matches, its binders are introduced and evaluation continues,
  • otherwise the entire condition is false.
  • A let mut <pattern> = <expr> clause introduces mutable binders for the remaining clauses and the then block.
  • A let move <pattern> = <expr> clause consumes the clause scrutinee for ownership-tracked values. The moved source binding is unavailable in later clauses, the then block, and the else block.
  • A non-let clause must have type bool; false short-circuits.
  • Binders introduced by let clauses are in scope for:
  • subsequent clauses in the chain, and
  • the then block. They are not in scope in the else block, and they do not escape the if.

Parsing note (Supported forms):

  • && at the top level is parsed as a clause separator. Use parentheses if a clause needs its own && / || / ?? expression at the top level.

Example (else let shorthand):

fn main () -> int {
  let a: int? = None;
  let b: int? = Some(3);

  if let Some(v) = a {
    return v;
  } else let Some(v) = b {
    return v;
  } else {
    return 0;
  }
}

Supported patterns in the Supported forms (same as match expressions; see flow match):

  • optionals: None, Some(name), Some(_)
  • recoverable results: Ok(name), Err(name) (and _ binders)
  • enums: Variant(...) / E::Variant(...) / qualified variants
  • type unions: name: Type / _: Type

Example (optional):

fn main () -> int {
  let maybe: int? = Some(7);

  if let Some(v) = maybe {
    return v;
  }
  return 0;
}

Example (recoverable Result):

import std::result;

fn main () -> int {
  let r: std::result::Result(int, string) = Ok(42);
  if let Ok(v) = r {
    return v;
  }
  return 0;
}

Notes:

  • <condition> is an expression; parentheses are optional because the normal expression grammar already includes parenthesized expressions.
  • Bodies are blocks. else may be followed by either:
  • a block (else { ... }), or
  • another if (else if ... { ... }) to form an “else-if” chain.

Surface Syntax (Expression Form)#

Silk also supports if / else as an expression form that yields a value:

let v: int = if cond { 123 } else { 456 };

Notes:

  • if expressions require an else branch so the expression yields a value on all paths.

  • The else if ... chain form is supported in expression position:

    let v: int = if a { 1 } else if b { 2 } else { 3 };
    
  • Restriction: the { ... } bodies of if expressions contain a single expression (not a full statement block).

Semantics#

  • The condition expression is evaluated exactly once.
  • If the condition is true, the if block executes and the else block (if present) does not execute.
  • If the condition is false, the else block executes if present; otherwise the if statement does nothing.

Blocks create scopes:

  • Declarations inside the if body are not visible outside that body.
  • Declarations inside the else body are not visible outside that body.

Type Checking Rules#

  • The condition must have type bool. If it does not, the checker reports a type mismatch (diagnostics, E2001).

For if expressions:

  • The then and else branches must produce compatible value types.
  • The expression’s result type is the shared branch type (or the expected type when the expression is type-directed).

else if Chains#

The language supports chained conditions (“else-if chains”). The compiler parses else if as sugar for nesting an if inside the else block:

fn main () -> int {
  let x: int = 1;

  if x == 0 {
    return 0;
  } else {
    if x == 1 {
      return 1;
    } else {
      return 2;
    }
  }
}

The equivalent direct surface form is:

fn main () -> int {
  let x: int = 1;
  if x == 0 {
    return 0;
  } else if x == 1 {
    return 1;
  } else {
    return 2;
  }
}

Examples#

Minimal if / else#

fn main () -> int {
  if true {
    return 0;
  } else {
    return 1;
  }
}

Boolean expressions in conditions#

fn main () -> int {
  let x: int = 1;
  let y: int = 2;

  if x < y && y < 10 {
    return 3;
  } else {
    return 4;
  }
}

Control flow inside branches#

fn main () -> int {
  let x: int = 1;
  let y: int = 2;

  if x < y {
    while false {
      continue;
    }
    return 3;
  } else {
    return 4;
  }
}

Notes#

Supported forms:

  • if <expr> { ... } and if <expr> { ... } else { ... } statement forms.
  • if let <pattern> = <expr> { ... } statement form:
  • else if let / else let chains, and
  • && let-chains in the if let condition.
  • Boolean type-checking for conditions.
  • if expressions of the form if <cond> { <expr> } else { <expr> }.

Limitations:

  • General block expressions ({ stmt* <expr> }) outside the specific if expression form.

examples:

Source repository · Edit this page · View Markdown