Skip to content

Bindings

Declare inferred or annotated values, opt into mutation, destructure values, and shadow earlier names.

Updated View as Markdown

A binding gives a value a local name. Atoll uses := when the compiler infers the type and : plus = when the source states the type.

name := "Ada"
attempts: int = 3

Bindings are immutable unless they begin with mut.

Declaration forms

Form Type Initial value Reassignable
name := value Inferred Required No
mut name := value Inferred Required Yes
name: T = value Written as T Explicit No
mut name: T = value Written as T Explicit Yes
name: T Written as T Type-defined zero/default No
mut name: T Written as T Type-defined zero/default Yes

:= introduces an inferred binding. = either initializes an annotated binding or assigns to an existing mutable place. Keeping those spellings distinct lets the parser and the reader see whether a name is new.

count := 0
mut total: int = 0

total = total + count

Writing count = 0 for a new inferred binding is an error. Writing count: int := 0 is also an error; annotated bindings use =.

Immutable by default

An immutable binding cannot be assigned a second value.

name := "Ada"
name = "Grace" // error: `name` is immutable

Immutability belongs to the binding. Whether an operation can mutate the value also depends on the value’s type and the receiver or parameter through which it is accessed.

Mutable bindings

Use mut when an algorithm must update a local value.

fn sum(values: []int): int {
    mut total := 0
    for value in values {
        total += value
    }
    return total
}

The mut marker is part of the binding or parameter declaration:

fn increment(mut value: int): int {
    value += 1
    return value
}

Type inference and annotations

An initializer normally supplies enough information:

enabled := true
port := 8080
message := "ready"
coordinates := (12, 34)

Use an annotation when the intended type cannot be recovered from the value or when it is part of the contract you want readers to see.

port: u16 = 8080
names: []string = []
result: Option[int] = None

Atoll’s scalar types use lowercase names such as int, bool, and string. Nominal composite types use PascalCase, such as Option, Result, List, and user-defined structs and enums.

Default initialization

An annotated binding may omit = value:

mut total: int
mut names: []string

The type determines the initial zero/default value. This form requires an explicit type; an inferred binding always requires := value. Prefer an explicit initializer when it communicates intent better than the type’s default.

Destructuring

A pattern can introduce more than one binding.

(left, right) := (10, 20)
first, second := (1, 2)

Patterns also appear in for, match, and refutable-binding else forms. Tuple, struct, variant, and slice patterns share the same pattern language; the patterns specification will define irrefutability and failure behavior in one place.

Refutable bindings with else

When a pattern might not match, an else block handles failure. The block must diverge, for example by returning from the function.

Some(value) := maybe_value else {
    return 0
}

return value

Bindings introduced by the pattern remain available after the else.

Shadowing and scope

A later declaration may shadow an earlier name. Shadowing creates a new binding; it does not mutate the old one.

input := "42"
input := parse_int(input)

Each binding is visible from its declaration to the end of its block, except where a nested or later binding shadows it. Atoll does not hoist local bindings.

Naming

Local variables and parameters use snake_case.

retry_count := 0

fn reserve(seat_count: int): bool {
    return seat_count > 0
}

See the naming conventions for the complete, current naming table.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close