Closures

Closure types (function-shaped types in fields, params, returns) live in Type System / Closure Types. This page covers the expression form: the values you assign to those types.

Closures are pure, single-expression functions:

pub enum Event {
  textChanged(value: String),
  resized(width: I32, height: I32),
  submit
}

pub struct Form<E> {
  onChange: String -> E,
  onResize: I32, I32 -> E,
  onSubmit: () -> E,
  onScale: mut I32 -> E,
  onConsume: sink String -> E
}

impl Form {
  // Single parameter - no parens needed
  onChange: x -> .textChanged(value: x),

  // Multiple parameters - comma separated
  onResize: w, h -> .resized(width: w, height: h),

  // No parameters - empty parens required
  onSubmit: () -> .submit,

  // mut convention: caller must pass a mutable binding
  onScale: mut n -> .resized(width: n, height: n),

  // sink convention: caller's binding is consumed
  onConsume: sink s -> .textChanged(value: s)
}

Expression syntax:

ParametersSyntaxExample
None() -> expr() -> .submit
One (default)x -> exprx -> .changed(value: x)
One (mut)mut x -> exprmut n -> .resized(width: n, height: n)
One (sink)sink x -> exprsink s -> .text(value: s)
Multiplex, y -> exprx, y -> .point(x: x, y: y)
With typesx: T -> exprx: String -> .text(x: x)
Pipe syntax|x, y| expr|x, y| x + y

Rules:

  • Closures are pure: no side effects, single expression body
  • Single parameter does not need parentheses
  • Multiple parameters are comma-separated
  • Empty parameters require parentheses: () -> expr
  • Convention keywords (mut, sink) precede the parameter name
  • Type annotations optional when inferable
  • Convention on a closure param means the caller of the closure must satisfy it

Caller Constraints

When a closure type carries mut or sink, every caller is checked against that requirement at compile time:

let scale: mut I32 -> I32 = mut n -> n

let mut x: I32 = 10
let _r: I32 = scale(x)   // ok: x is mutable

let y: I32 = 5
let _s: I32 = scale(y)   // error: MutabilityMismatch: y is immutable

let consume: sink String -> String = sink s -> s

let label: String = "hello"
let _a: String = consume(label)  // ok: label is moved
let _b: String = label           // error: UseAfterSink: label was consumed