Patterns

Formatting rules for compiler-recognized patterns: blocks, try, match, recurse, parallel, spawn, timeout, cache, with, for, and nursery.

Blocks and try

Block Expressions

Block expressions use { } with newline-separated expressions. The last expression is the block’s value. Multi-line blocks are always stacked regardless of width.

// ALWAYS this format for multi-line blocks
let result = {
    let x = compute()
    let y = transform(x)
    x + y
}

// Short blocks may use comma-separated one-liner
let result = { let x = 1, x + 1 }

Contracts on Functions

Contracts (pre()/post()) appear on the function declaration, between the signature and =:

@divide (a: int, b: int) -> int
    pre(b != 0) = a / b

@divide (a: int, b: int) -> int
    pre(b != 0 | "divisor cannot be zero") = a / b

@compute (n: int) -> int
    post(result -> result >= 0) = {
    let value = compute_inner(n)
    value
}

@compute_sqrt (n: float) -> float
    pre(n >= 0.0 | "cannot take sqrt of negative")
    post(result -> result >= 0.0) = {
    let result = sqrt(n)
    result
}

Long Conditions Break

Binary expressions in contracts break before operators:

@process (data: Data) -> Output
    pre(data.is_valid()
        && data.size() > 0
        && data.size() < max_size) = process_inner(data)

try Blocks

try uses block syntax with ? propagation:

let result = try {
    let data = fetch(url: endpoint)?
    let parsed = parse(input: data)?
    Ok(parsed)
}

match

Scrutinee Before Block, Arms Below

Multi-line match has arms stacked with trailing commas. Short matches (all simple expressions, no guards, fits in line width) may be single-line:

// Single-line (short, simple)
let label = match b { true -> "yes", false -> "no" };

// Multi-line
let label = match status {
    Pending -> "waiting",
    Running -> "in progress",
    Complete -> "done",
    Failed -> "error",
}

Arms with Longer Bodies

let message = match event {
    Click(x, y) -> format(template: "Clicked at ({}, {})", x, y),
    KeyPress(key, mods) -> format(template: "Key: {} with {:?}", key, mods),
    _ -> "unknown event",
}

Arms with Long Calls

When an arm body has a long function call, break the call arguments (not after ->):

let result = match event {
    Click(x, y) -> handle_click_with_long_name(
        x: x,
        y: y,
        options: defaults,
    ),
    KeyPress(key) -> handle_key(key),
}

Arms with Always-Stacked Bodies

Break after -> only when the body is an always-stacked pattern (block, try, match):

let result = match input {
    Some(data) -> {
        let validated = validate(data);
        transform(validated)
    },
    None -> default_value,
}

Guards

let category = match n {
    x if x < 0 -> "negative",
    0 -> "zero",
    x if x < 10 -> "small",
    _ -> "large",
}

recurse

Always Stacked

recurse is always stacked with named parameters:

@factorial (n: int) -> int = recurse(
    condition: n <= 1,
    base: 1,
    step: n * self(n - 1),
)

@fib (n: int) -> int = recurse(
    condition: n <= 1,
    base: n,
    step: self(n - 1) + self(n - 2),
    memo: true,
)

@parallel_fib (n: int) -> int = recurse(
    condition: n <= 20,
    base: sequential_fib(n),
    step: self(n - 1) + self(n - 2),
    parallel: 20,
)

parallel and spawn

Always Stacked

let results = parallel(
    tasks: [fetch(url: "/a"), fetch(url: "/b"), fetch(url: "/c")],
)

let results = parallel(
    tasks: [fetch(url: "/a"), fetch(url: "/b"), fetch(url: "/c")],
    max_concurrent: 2,
    timeout: 30s,
)

Task List Follows List Rules

Short task list stays inline; long list wraps:

let results = parallel(
    tasks: [
        fetch_user(id: 1),
        fetch_user(id: 2),
        fetch_user(id: 3),
        fetch_user(id: 4),
        fetch_user(id: 5),
    ],
    max_concurrent: 3,
)

spawn

Fire-and-forget, same formatting:

spawn(
    tasks: [send_email(to: user), log_event(event: action)],
)

timeout and cache

Stacked When Multiple Params

let result = timeout(
    op: fetch(url: slow_endpoint),
    after: 5s,
)

let user = cache(
    key: `user:{user_id}`,
    op: fetch_user(id: user_id),
    ttl: 5m,
)

Inline Only If Very Short (Rare)

let value = cache(key: "k", op: get(), ttl: 1m)

with Expressions

Inline If Short

let result = with Http = mock_http in fetch(url: "/api")

Break at in

When exceeding 100 characters:

let result =
    with Http = MockHttp { responses: default_responses }
    in fetch_user_data(user_id: current_user)

Multiple Capabilities

let result =
    with Http = mock_http,
         Logger = mock_logger
    in perform_operation(input: data)

Nested with

let result =
    with Http = mock_http in
        with Cache = mock_cache in
            fetch_cached(url: endpoint)

for Loops

Inline If Short

for x in items do print(msg: x)

let doubled = for x in items yield x * 2

let positives = for x in items if x > 0 yield x

Break for Long Body

for user in users do
    process_user(user: user, options: default_options)

let results = for item in items yield
    transform(input: item, config: default_config)

Nested for

for x in 0..10 do
    for y in 0..10 do
        plot(x: x, y: y)

Labeled Loops

loop:outer {
    loop:inner {
        if condition then break:outer result
        else continue:inner
    }
}

nursery

Always Stacked

let results = nursery(
    body: n -> {
        n.spawn(task: fetch(url: "/a"))
        n.spawn(task: fetch(url: "/b"))
        n.spawn(task: fetch(url: "/c"))
    },
    on_error: CancelRemaining,
    timeout: 30s,
)

Complex Body

When the body lambda is complex, break after arrow:

let results = nursery(
    body: n -> {
        let urls = generate_urls(count: 10)
        for url in urls do n.spawn(task: fetch(url: url))
    },
    on_error: CollectAll,
)

Channel Constructors

Typically Inline

Channel constructors are typically short:

let (tx, rx) = channel<int>(buffer: 10)
let (tx, rx) = channel_in<Message>(buffer: 100)
let (tx, rx) = channel_out<Event>(buffer: 50)
let (tx, rx) = channel_all<Data>(buffer: 25)

catch

Stacked Format

let result = catch(
    expr: potentially_panicking_operation(),
)