/ Specification
Playground Docs Performance GitHub
Chapter 6

Items

Items are top-level declarations that define named entities: functions, structs, enums, traits, implementation blocks, and test blocks. Items are hoisted — they are visible throughout the enclosing scope regardless of declaration order.

Functions

function    ::= "fn" identifier "(" [ param_list ] ")" [ "->" type_expr ]
                { contract } block
param_list  ::= param { "," param }
param       ::= ["..."] identifier ":" type_expr [ "=" expression ]
contract    ::= "require" expression [ "," string_literal ]
            |   "ensure" closure [ "," string_literal ]

Functions are named, callable entities with typed parameters, an optional return type, optional contracts, and a body block.

Parameters

Function parameters require type annotations. Parameters may have default values; once a parameter has a default, all following parameters must also have defaults. The last parameter may be variadic (prefixed with ...), in which case extra arguments are collected into an array.

fn greet(name: String, greeting: String = "Hello") -> String {
    return "${greeting}, ${name}!"
}

fn sum(...nums: Int) -> Int {
    return nums.reduce(|a, b| { a + b }, 0)
}

Contracts

Functions may declare require (precondition) and ensure (postcondition) contracts. Require clauses are checked before the function body executes. Ensure clauses receive the return value and are checked after the body completes. If a contract fails, a runtime error is raised with the optional message.

fn withdraw(balance: Int, amount: Int) -> Int
    require amount > 0, "amount must be positive"
    require amount <= balance, "insufficient funds"
    ensure |result| { result >= 0 }, "result must be non-negative"
{
    return balance - amount
}

Phase-Dependent Dispatch

When a function has phase-annotated parameters (~Type for fluid, *Type for crystal), the runtime checks the phase of the argument at call time. Multiple function overloads may exist with different phase constraints, and the correct one is dispatched based on the argument's phase.

Structs

struct_decl  ::= "struct" identifier "{" field_decl { "," field_decl } "}"
field_decl   ::= identifier ":" type_expr

Structs declare named record types with a fixed set of typed fields. Fields may hold any value type, including closures. When a closure field has self as its first parameter, it receives a deep clone of the struct instance when called through the struct.

struct Point {
    x: Float,
    y: Float
}

struct Counter {
    count: Int,
    increment: Fn,
    get_count: Fn
}

let c = Counter {
    count: 0,
    increment: |self| { self.count + 1 },
    get_count: |self| { self.count }
}
Important Struct method closures with self receive a deep clone. Mutations to self inside the closure do not propagate back to the original struct. Return the modified value to update the caller's copy.

Per-Field Phases

Each struct field carries its own phase tag. Individual fields can be frozen or thawed independently. When the entire struct is frozen, all fields are also frozen.

Enums

enum_decl      ::= "enum" identifier "{" variant { "," variant } "}"
variant        ::= identifier [ "(" type_list ")" ]
type_list      ::= type_expr { "," type_expr }

Enums declare tagged union types. Each variant has a name and optionally carries a payload of one or more typed values. Variants are constructed with the :: syntax and destructured in match expressions.

enum Option {
    Some(Int),
    None
}

enum Shape {
    Circle(Float),
    Rectangle(Float, Float),
    Point
}

let s = Shape::Circle(5.0)
let none = Option::None

Traits

trait_decl  ::= "trait" identifier "{" { trait_method } "}"
trait_method ::= "fn" identifier "(" [ param_list ] ")" [ "->" type_expr ]

Traits declare a set of method signatures that types can implement. Trait methods are declared without bodies — only their signatures are specified.

trait Printable {
    fn to_string(self) -> String
}

trait Comparable {
    fn compare(self, other: Int) -> Int
}

Impl Blocks

impl_block   ::= "impl" identifier "for" identifier "{" { function } "}"

Impl blocks provide method implementations for a trait on a specific type. The methods become callable on instances of that type.

impl Printable for Point {
    fn to_string(self) -> String {
        return "(${self.x}, ${self.y})"
    }
}

Test Blocks

test_block  ::= "test" string_literal block

Test blocks define named test cases. They are only executed when the program is run in test mode. The test name is a string literal for descriptive naming.

test "addition works" {
    let result = 2 + 2
    assert(result == 4, "expected 4")
}

test "string interpolation" {
    let name = "Lattice"
    assert("hi ${name}" == "hi Lattice")
}