A literal builds a value directly from source text. Its type comes from the literal’s own form, and — where the form is incomplete — from the expected type at its position.
fn f(): void {
enabled := true // bool
attempts := 3 // int
ratio := 0.5 // float
initial := 'A' // char
label := "ready" // string
magic := b"\x89PNG" // []byte
scores := [90, 85, 77] // []int
pair := ("port", 8080) // (string, int)
origin := { x: 0, y: 0 } // { x: int, y: int }
println("${enabled} ${attempts} ${ratio} ${initial} ${label} ${magic.len()} ${scores.len()} ${pair.0} ${origin.x}")
}Booleans
true and false are the only bool literals.
fn f(): void {
enabled := true
visible: bool = false
println("${enabled && !visible}")
}Atoll has no truthiness. A condition must be a bool; an int, string,
List, or Option is not a condition on its own.
fn f(): int {
count := 3
if count {
return 1
}
return 0
}Write the comparison you mean — if count > 0 { ... }.
Integers
Integer literals come in four bases, and _ may separate digits anywhere.
fn f(): void {
decimal := 1_000_000
hex := 0xFF
binary := 0b1010_0011
octal := 0o755
println("${decimal} ${hex} ${binary} ${octal}")
}An unsuffixed integer adopts the expected numeric type when one exists, and
otherwise defaults to int.
fn f(): void {
ordinary := 200 // int
small: u8 = 200 // u8, from the annotation
wide: i64 = 200 // i64, from the annotation
println("${ordinary} ${small} ${wide}")
}A suffix fixes the type at the literal itself, which is what you want inside a
larger expression where no annotation is reachable. Every integer width has a
suffix: i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 isize
usize.
fn f(): void {
port := 8080u16
limit := 255u8
offset := 32isize
index := 9usize
huge := 340282366920938463463374607431768211455u128
masked := 0b1111_0000u8
println("${port} ${limit} ${offset} ${index} ${huge} ${masked}")
}Suffixes never truncate. A literal that does not fit its suffix or its contextual type is a compile-time error:
fn f(): void {
overflow: u8 = 999
println("${overflow}")
}The leading minus sign is a unary operator, not part of the token. That keeps overflow checking on the positive magnitude explicit, and it is why an extreme negative bound is written with a parenthesis or an annotation when the intended width is not obvious.
fn f(): void {
below_zero := -5
delta := 3 - -2
println("${below_zero} ${delta}")
}Floating point
A float literal has a decimal point, an exponent, or both, and defaults to
float. f32 and f64 select a fixed width.
fn f(): void {
ratio := 0.625
scientific := 6.022e23
tiny := 1.0e-3
grouped := 1_234.567_8
compact := 1.5f32
precise := 1.25f64
println("${ratio} ${scientific} ${tiny} ${grouped} ${compact} ${precise}")
}Integer and float literals do not silently cross categories, even when the mathematical values agree. Use a cast when a conversion is intended.
fn f(): float {
return 1
}fn f(): float {
n := 1
return n as float
}Characters
Single quotes build a char — one Unicode scalar value, not one byte.
fn f(): void {
letter := 'A'
newline := '\n'
tab := '\t'
quote := '\''
backslash := '\\'
heart := '\u{2764}'
println("${letter}${newline}${tab}${quote}${backslash}${heart}")
}Characters are comparable, which makes classification checks direct:
fn is_lower_ascii(c: char): bool {
return c >= 'a' && c <= 'z'
}
fn is_digit(c: char): bool {
return c >= '0' && c <= '9'
}Strings
Double quotes build a UTF-8 string. Escapes are processed and $name or
${expression} interpolates a value through its Display implementation.
fn f(): void {
name := "Ada"
sum := 2 + 3
greeting := "Hello, $name; 2 + 3 = ${sum}"
println(greeting)
}Escapes
The recognized escapes are \n, \t, \r, \0, \\, \", \', \$, and
\u{...} for a Unicode scalar.
fn f(): string {
return "tab:\t quote:\" backslash:\\ dollar:\$ return:\r nul:\0 emoji:\u{1F600}"
}\xHH is not a string escape — it belongs to byte strings only. Anything
else after a backslash is ATOLL1027: Invalid escape sequence:
fn f(): string {
return "reset:\e[0m"
}Interpolation
$name interpolates a bare identifier and stops there. It does not pick up a
following .field or call — that text stays literal. Use ${...} for anything
beyond one name.
struct Point { x: int, y: int }
fn f(): void {
p := Point { x: 3, y: 4 }
items := [1, 2, 3]
println("x=${p.x} count=${items.len()} first=${items[0] ?? 0}")
println("sum=${p.x + p.y}")
}A braced interpolation may hold a full expression, including calls, member
access, indexing, and operators. The interpolated type must implement Display:
struct Point { x: int, y: int }
impl Display for Point {
fn to_string(self): string => "(${self.x}, ${self.y})"
}
fn f(): void {
println("origin = ${Point { x: 0, y: 0 }}")
}Without that implementation the compiler reports ATOLL3100: type ... does not implement Display.
Multi-line and triple-quoted
An ordinary string may span lines, but the leading whitespace on each continuation line is part of the value. Triple quotes solve that: they strip the indentation shared with the closing delimiter, and they still process escapes and interpolation.
fn f(): void {
count := 3
flat := "line one
line two"
block := """
Report
indented detail
count: ${count}
"""
println(flat)
println(block)
}Raw strings
r"..." suppresses escape processing and interpolation. It is the right form
for regular expressions, Windows paths, and templates that contain $.
fn f(): void {
pattern := r"\d+\.\d+"
shell := r"$HOME/bin"
println("${pattern} ${shell}")
}“Raw” changes content processing, not delimiting: a raw string still ends at its
closing ", so it cannot contain an unescaped double quote.
The r prefix applies to the single-quoted form only. A triple-quoted string
always processes escapes and interpolation, whether or not you write r""":
fn f(): string {
return r"""
reset: \e[0m
"""
}To keep a backslash-heavy block literal, build it from raw single-quoted pieces:
fn f(): void {
lines := [
r"path: C:\bin\tools",
r"regex: \d+\.\d+",
]
println(lines.join("\n"))
}Slicing, iteration, and Unicode boundary rules are in Strings.
Byte strings
b"..." builds a []byte. Source bytes must be ASCII, and \xHH writes an
arbitrary byte.
fn f(): void {
png := b"\x89PNG\r\n\x1a\n"
tag: []byte = b"ATOLL"
println("${png.len()} ${tag.len()}")
}Non-ASCII source characters are rejected — one visible character need not be one byte, so the encoding must be explicit:
fn f(): void {
greeting := b"héllo"
println("${greeting.len()}")
}Encode Unicode text through the string API instead:
fn f(): void {
text := "héllo"
encoded := text.bytes()
println("${text.len()} chars, ${encoded.len()} bytes")
}Lists
Square brackets build a List. The element type comes from the elements, or
from the expected type when the literal is empty.
fn f(): void {
numbers := [1, 2, 3]
names: []string = []
grid := [[1, 2], [3, 4]]
println("${numbers.len()} ${names.len()} ${grid.len()}")
}An empty literal with nothing to infer from is an error, so annotate it:
fn f(): void {
values := []
println("${values.len()}")
}List literals nest and hold aggregates, which is how fixture data is usually written:
struct User { id: int, name: string, active: bool }
fn active_names(users: []User): []string {
return users.filter(u => u.active).map(u => u.name)
}
fn f(): void {
users := [
User { id: 1, name: "ada", active: true },
User { id: 2, name: "grace", active: false },
User { id: 3, name: "alan", active: true },
]
println("${active_names(users).len()} active")
}Fixed arrays
[N]T is a fixed-length array. It uses the same bracket syntax as a list and is
selected by the expected type.
fn f(): void {
magic: [4]byte = [0x7F, 0x45, 0x4C, 0x46]
weights: [3]int = [1, 2, 3]
mut total := 0
for w in weights {
total += w
}
println("${magic[0]} ${total}")
}A fixed array is indexed and iterated but does not carry the List method
surface — magic.len() is not available, because the length is in the type.
Maps and sets
Map and Set have no brace literal. Build them with the prelude constructors
and fill them in.
fn f(): void {
mut ports: Map[string, int] = Map.new()
ports.put("http", 80)
ports.put("https", 443)
mut tags: Set[string] = Set.new()
tags.add("web")
tags.add("web") // already present; the set stays at one entry
println("${ports.get("http") ?? 0} ${ports.len()} ${tags.len()}")
}Map.new and Set.new take an optional capacity, and the type arguments can be
supplied at the call instead of on the binding:
fn f(): void {
mut counts := Map.new[string, int](16)
for word in ["a", "b", "a"] {
counts.put(word, (counts.get(word) ?? 0) + 1)
}
for word, n in counts {
println("$word: $n")
}
}Map.get returns an Option, which is why ?? 0 appears above. Set membership
is contains; map membership is contains_key.
Tuples
A comma inside parentheses makes a tuple. () is the unit value.
fn f(): void {
pair := ("port", 8080)
triple := (1, true, "ready")
empty := ()
println("${pair.0}=${pair.1} ${triple.0} ${triple.1} ${triple.2}")
}Members are read positionally with .0, .1, and so on. Without a comma,
parentheses only group; a one-element tuple needs a trailing comma.
fn f(): void {
value := 3
grouped := (value) // int — just grouping
single: (int,) = (value,) // (int,) — a one-element tuple
println("${grouped}")
}Tuples are the usual way to return two values without declaring a type:
fn divide(n: int, d: int): (int, int) {
return (n / d, n % d)
}
fn f(): void {
(quotient, remainder) := divide(17, 5)
println("${quotient} rem ${remainder}")
}Records
Braces holding field: value pairs build an anonymous record. Its type is
structural — spelled out in full — and it needs no declaration.
fn f(): void {
origin := { x: 0, y: 0 }
annotated: { x: int, y: int } = { x: 1, y: 2 }
nested := { host: "localhost", port: 8080, tls: { enabled: true } }
println("${origin.x} ${annotated.y} ${nested.tls.enabled} ${nested.host}:${nested.port}")
}Records compose with lists and with inferred return types, which makes them convenient for ad-hoc result shapes:
fn summarize(values: []int) {
mut total := 0
mut worst := 0
for v in values {
total += v
if v > worst { worst = v }
}
return { total: total, worst: worst, count: values.len() }
}
fn f(): void {
s := summarize([3, 9, 4])
println("total=${s.total} worst=${s.worst} count=${s.count}")
}See Records for structural typing rules.
Structs and variants
A nominal type name followed by braces constructs a struct. Commas are required between fields in a literal, even when the declaration separated them with newlines.
struct Point {
x: int
y: int
}
fn f(): void {
p := Point { x: 10, y: 20 }
q := Point {
x: 1,
y: 2,
}
println("${p.x} ${q.y}")
}Newline-only separation inside a literal is a parse error:
struct Point { x: int, y: int }
fn f(): Point {
return Point {
x: 1
y: 2
}
}Field shorthand uses a visible binding with the same name:
struct Point { x: int, y: int }
fn f(): Point {
x := 10
y := 20
return Point { x, y }
}Enum variants use the enum name for the unit and tuple forms. A struct-shaped
variant is written unqualified, because Enum.Variant { ... } parses the
braces as a separate record:
enum Color { Red, Green, Blue }
enum Shape {
Circle { radius: float }
Square { side: float }
}
fn f(): void {
c := Color.Red
round := Circle { radius: 1.5 } // unqualified struct-shaped variant
boxed := Shape.Circle(2.0) // positional form of the same variant
match round {
Circle { radius } => println("circle ${radius}")
Square { side } => println("square ${side}")
}
println("${c == Color.Red} ${boxed == Shape.Circle(2.0)}")
}Error variants follow the same rule after error:
error ParseError {
InvalidDigit { position: int }
Empty
}
fn parse_digit(text: string): int ! ParseError {
if text.is_empty() { error Empty }
error InvalidDigit { position: 4 }
}Option and Result
Some, None, Ok, and Err are prelude constructors, not literal syntax,
but they behave like literals at a typed destination.
error LoadError { NotFound }
fn f(): void {
found: int? = Some(3)
missing: int? = None
good: Result[int, LoadError] = Ok(7)
bad: Result[int, LoadError] = Err(LoadError.NotFound)
println("${found ?? 0} ${missing ?? -1} ${good.unwrap_or(0)} ${bad.unwrap_or(0)}")
}A bare None with no surrounding Option[T] has nothing to infer T from and
must be annotated.
When a literal needs context
| Literal | Needs context when |
|---|---|
| Unsuffixed number | a non-default numeric width is intended |
[] |
no element supplies T |
None |
no surrounding Option[T] fixes T |
List syntax for [N]T |
a fixed array rather than a List is intended |
| Closure literal | parameter types are not inferable from the destination |
Context comes from a binding annotation, a parameter, a return type, a field type, a collection element, a branch join, or an explicit generic argument. It never comes from a value observed later at runtime.
Literal construction evaluates its embedded expressions left to right. String interpolations, list elements, tuple members, and field initializers may all have effects — being inside a literal does not make an expression constant. For what is folded at compile time, see Constants.
Composed example
struct Sample {
label: string
value: float
tags: []string
}
const WARN_THRESHOLD = 0.75
fn classify(sample: Sample): string {
return if sample.value >= WARN_THRESHOLD { "warn" } else { "ok" }
}
fn report(samples: []Sample): string {
mut out := ""
for sample in samples {
if out.len() > 0 { out = out + "\n" }
count := sample.tags.len()
out = out + "${sample.label}\t${sample.value}\t${classify(sample)}\t${count} tags"
}
return out
}
fn main(): void {
samples := [
Sample { label: "cpu", value: 0.91, tags: ["host", "core0"] },
Sample { label: "mem", value: 0.42, tags: [] },
Sample { label: "disk", value: 0.75, tags: ["ssd"] },
]
println(report(samples))
}