Functions

Functions in Aria are pure by default, support full type inference, and use the last expression as the return value.

Basic Functions

// Multi-line function
fn add(a: i64, b: i64) -> i64 {
    a + b
}

// Single-expression (no braces, no return)
fn double(x: f64) -> f64 = x * 2.0

// No return value (returns unit)
fn log(msg: str) {
    println("[LOG] {msg}")
}

The last expression in a block is the return value — no return keyword needed.

Parameters

All parameters are immutable by default. Use mut for mutable parameters:

fn push(mut stack: Stack[i64], value: i64) {
    stack.items = stack.items.append(value)
}

Generic Functions

Type parameters use square brackets [T] — no angle bracket ambiguity:

fn first[T](items: [T]) -> T? {
    if items.len() == 0 { None } else { Some(items[0]) }
}

fn map[T, U](list: [T], f: fn(T) -> U) -> [U] =
    [f(x) for x in list]

Trait Bounds

Constrain type parameters with trait bounds:

fn largest[T: Ord](items: [T]) -> T? {
    ...
}

// Multiple bounds
fn sum[T: Ord + Numeric](values: [T]) -> T {
    mut total: T = 0
    for v in values { total = total + v }
    total
}

// Where clause for complex bounds
fn process[T] where T: Display + Clone {
    ...
}

Functions with Effects

Functions that perform side effects must declare them:

// Pure function (no effects) — safe to cache, parallelize, reorder
fn calculateTotal(items: [Item]) -> f64 =
    items.map(.price).sum()

// Function with I/O and filesystem effects
fn readConfig(path: str) -> Config ! IoError with [Io, Fs] {
    content := io.readFile(path)?
    json.decode[Config](content)?
}

// Function with network effects
fn fetchData(url: str) -> [byte] ! HttpError with [Net] {
    net.get(url)?.body
}

Error-Returning Functions

Functions declare their error types in the signature with !:

// Returns str on success, IoError on failure
fn readFile(path: str) -> str ! IoError {
    ...
}

// Multiple error types via sum type
type AppError =
    | Io(str)
    | Parse(str)
    | NotFound(str)

fn loadConfig(path: str) -> Config ! AppError {
    content := io.readFile(path) catch |e| {
        return err(AppError.Io("{e}"))
    }
    json.decode[Config](content) catch |e| {
        return err(AppError.Parse("{e}"))
    }
}

Closures

// Inline closure
nums.map(fn(x) => x * 2)

// Multi-line closure
nums.filter(fn(item) {
    item.isActive() && item.score > threshold
})

// Closures capture variables from their environment
multiplier := 3
scaled := nums.map(fn(x) => x * multiplier)

Visibility

Functions are visible within their module by default. Use pub to export for other modules. The compiler enforces this with E0704 errors for private symbol access across files.

Next Steps