Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Define functions with clear interfaces

Every parameter and return type must be declared:

def double(value: i64) -> Result[i64, OverflowError[i64]]:
    """Return twice the supplied value."""
    Ok(value * 2)

def main() -> Result[(), IoError | OverflowError[i64]]:
    print(double(21)?)?
    Ok(())
42

value: i64 declares the parameter type. The return type Result[i64, OverflowError[i64]] describes either a doubled integer or overflow. Ok(value * 2) supplies success; the multiplication returns an error from double if it cannot fit. You can also write return Ok(value * 2). The caller uses double(21)? to obtain the successful integer or pass the error onward. Parameters are immutable. See recoverable errors for handling either outcome explicitly.

Indent statements in the same block equally, using spaces. Tabs are rejected.

A string at the beginning of a function is its documentation. Triple quotes allow a docstring to span lines. When a function should immediately return a string, write return "text" so it cannot be mistaken for documentation.

Functions are known throughout the file, so one function may call a function declared later. Name arguments when that makes their meaning clearer:

def rectangle(width: i64, height: i64 = 2) -> Result[i64, OverflowError[i64]]:
    Ok(width * height)

def main() -> Result[(), IoError | OverflowError[i64]]:
    print(rectangle(height=3, width=4)?)?
    print(rectangle(4)?)?
    Ok(())
12
8

Positional arguments come first. After the first named argument, all remaining arguments must be named; their order need not match the declaration. Supplied expressions run in the order you write them. Required parameters come before parameters with defaults.

Defaults run afresh for each omitted argument. Mutable defaults never share storage between calls:

def count(values: &mut list[i64] = []?) -> Result[i64, AllocError]:
    values.append(7)?
    Ok(len(values))

def main() -> Result[(), AllocError | IoError]:
    print(count()?)?
    print(count()?)?
    mut saved = [1, 2]?
    print(count(values=&mut saved)?)?
    print(len(saved))?
    Ok(())
1
1
3
3

An omitted borrowed argument gets temporary storage that lasts for the call. Pass your own buffer to keep its changes. Defaults run after supplied arguments, in declaration order; they cannot refer to another parameter. Allocation still needs ? and a compatible Result return type. If a default fails, that call returns the error without entering the body. Callable values and builtins keep positional arguments; splat unpacking is not supported.