Skip to content

Language Guide

Learn the implemented Atoll language from source text through concurrency and integrated SQL.

Updated View as Markdown

Atoll is a statically typed language that compiles to WebAssembly. It has nominal structs and enums, typed errors instead of exceptions, Option instead of null, static trait dispatch, structured concurrency, and SQL queries the compiler type-checks against your schema.

Here is a whole program:

struct LineItem {
    sku: string
    price: float
    qty: int
}

error OrderError {
    Empty
    BadQuantity { sku: string }
}

fn total(items: []LineItem): float ! OrderError {
    if items.is_empty() {
        error Empty
    }
    mut sum := 0.0
    for item in items {
        if item.qty <= 0 {
            error BadQuantity { sku: item.sku }
        }
        sum += item.price * (item.qty as float)
    }
    return sum
}

fn main(): void {
    mut cart: []LineItem = []
    cart.add(LineItem { sku: "AT-1", price: 12.5, qty: 2 })
    cart.add(LineItem { sku: "AT-9", price: 4.0, qty: 3 })

    match total(cart) {
        Ok(amount) => println("order total ${amount}")
        Err(Empty) => println("cart is empty")
        Err(BadQuantity { sku }) => println("bad quantity for ${sku}")
    }
}

Four things in that program are worth naming before you read further.

float ! OrderError is sugar for Result[float, OrderError]: the failure modes are in the signature, and match cannot forget one of them. error Empty leaves through the error channel — there is nothing to throw and nothing to catch by accident.

[]LineItem is sugar for List[LineItem], an owned growable list. add needs the mut binding because it takes a mutable receiver.

for is the only loop keyword. It iterates (for item in items), tests a condition (for i < n), or runs forever (for { }).

${...} interpolates through the Display trait, so any type that implements to_string can appear inside a string.

Absence, failure, and concurrency in one place

The three features that most change how you write code are Option, declared errors, and spawn. They compose without ceremony:

error FetchError { Timeout { host: string } }

struct Page {
    host: string
    bytes: int
}

fn fetch(host: string): Page ! FetchError {
    if host.is_empty() { error Timeout { host: host } }
    return Page { host: host, bytes: host.len() * 100 }
}

fn largest(pages: []Page): Page? {
    mut best: Page? = None
    for page in pages {
        if page.bytes > (best?.bytes ?? 0) {
            best = Some(page)
        }
    }
    return best
}

fn main(): void {
    left := spawn { fetch("example.com") }
    right := spawn { fetch("atoll.dev") }

    mut pages: []Page = []
    match left.await() {
        Ok(page) => pages.add(page)
        Err(Timeout { host }) => println("timed out: ${host}")
    }
    match right.await() {
        Ok(page) => pages.add(page)
        Err(Timeout { host }) => println("timed out: ${host}")
    }

    match largest(pages) {
        Some(page) => println("largest: ${page.host} (${page.bytes} bytes)")
        None => println("nothing fetched")
    }
}

Both spawn bodies start before the first await. Task[T] preserves the body’s static type, so awaiting a fallible task hands back an ordinary Result — concurrency changes scheduling, not the type or error model.

largest returns Page? because “no pages” is a legitimate answer, not an error. best?.bytes ?? 0 reaches through the Option and supplies a fallback in one expression. Spawning and awaiting also give the function the Spawn and Suspend effects — which the compiler infers for you; effect annotations are not something you write.

Reading path

Start with the Tour. It builds one small program from println to modules and concurrency, and every block on it compiles.

Then work through the chapters in order:

Chapter What it covers
Fundamentals Source text, literals, bindings, constants, declarations, functions, calls, closures, operators, decorators
Control flow Blocks, conditions, patterns, match, the for forms, transfer expressions, defer
Types Primitives, inference, conversions, generics, structs, enums, aliases, tuples, traits, references
Errors Option, Result, declared errors, ?, catch, error unions
Modules Module paths, imports, aliases, visibility, atoll.toml
Concurrency Effects, suspension, tasks, spawn, select, race, streams, cancellation
SQL Models, schemas, routing, queries, frames, writes, transactions
Standard Library Scalars, text, collections, time, I/O, networking, traits, intrinsics

Read Errors before I/O, concurrency, or queries: all three expose typed fallibility, and the rest of the language assumes you are comfortable with Option, Result, and ?.

Two reference pages sit outside that sequence. Diagnostics shows real compiler output next to the code that produced it. Feature Status records which prelude APIs actually lower to WebAssembly — a program can type-check and still fail to build, and that page tells you where the line is.

If you are coming from another language

Habit Atoll
null / nil T? — an Option. xs[0] returns T?, not T
exceptions T ! E — a Result with a declared error type
while (cond) for cond { }
let / var x := v and mut x := v
List<T>, Foo<T> []T, Foo[T] — brackets, not angle brackets
Int, String, Boolean int, string, bool — scalars are lowercase
interfaces with runtime dispatch traits with static dispatch; there is no dyn
camelCaseMethods snake_case functions, fields, and locals
throw / try / catch error V, ?, and catch — which converts errors rather than unwrapping them

Names follow one rule set throughout: snake_case for functions, methods, locals, parameters, and fields; PascalCase for nominal types and enum variants; SCREAMING_SNAKE_CASE for constants; lowercase for scalar builtins.

Three syntax details cause most first-day errors, so they are worth having in advance:

  • Struct declarations separate fields by newline; struct literals require commas.
  • error is a keyword and can never be an identifier. Bind an error payload as err or e.
  • ? propagates and needs a fallible enclosing function; ?? supplies an Option fallback; ?. reaches through an Option. They are three different operators.

Where the normative text lives

This guide teaches the language through working programs. For normative grammar and invariants, use the Language Specification. For how source becomes WebAssembly, continue to the Compiler, IR, and Runtime sections.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close