Skip to content

Values Bindings And Blocks

Chris Michael edited this page Sep 2, 2026 · 2 revisions

Pudu

Values, Bindings, And Blocks

Pudu distinguishes the name of a value from a place whose value may change. let introduces the former; var introduces the latter. The distinction is lexical and visible at the declaration.

let origin = 0
var position = origin
position = position + 1

The inferred type of both names is Int. Reassigning origin is invalid. Assignment changes an existing mutable binding and produces (); it does not create a new name.

Bindings and annotations

Local bindings normally infer their type from the initializer. An annotation constrains that inference and records an intended boundary.

let port: UInt16 = 8080
var retries: Int = 3

Integer literals are held without loss while inference is active. 8080 can therefore be checked as UInt16; a value outside that type's range is rejected instead of truncated. Pudu does not use an annotation as a request for a lossy conversion.

A name is visible after its declaration in the containing block. An inner block may shadow it. The inner binding ends with the block and does not alter the outer one.

fn adjusted(value: Int) -> Int {
  let offset = 2
  let result = {
    let offset = 5
    value + offset
  }
  result + offset
}

adjusted(10) evaluates to 17: the inner block produces 15, then the outer offset adds two.

Blocks are expressions

A block evaluates its statements in source order. If the last item is an expression, that value is the value of the block. A block with no trailing expression produces ().

let magnitude = {
  let squared = value * value
  squared
}

The absence of a terminator is meaningful here: squared is the result, not a discarded statement. This rule is shared by function bodies, conditional branches, match arms written as blocks, loop bodies, and macro block arguments.

Mutation stays explicit

Only a var binding can be assigned. Mutation of a collection held by a var is expressed by an assignment or a mutating built-in method supported for that value.

fn sum(values: Array[Int]) -> Int {
  var total = 0
  for value in values { total = total + value }
  total
}

Pudu does not infer mutability from later use. Changing total from var to let changes the program's static meaning and is diagnosed at the assignment.

Scope at conditional boundaries

Names introduced inside an if, match arm, or loop body do not escape that region. Pattern bindings follow the same rule. let … else is the exception with an explicit purpose: after its fallback diverges, the successful pattern bindings remain available in the containing block.

fn require(value: Option[Int]) -> Int {
  let Some(found) = value else { return 0 }
  found
}

The fallback cannot fall through, so every path reaching found has proved the Some pattern.

Related

Clone this wiki locally