Ruta graveolens  ·  notes from a language experiment  ·  cultivated since 2025

Functions

A function is defined using the fn keyword.

function = "fn" IDENT "(" [ params ] ")" [ "->" type ] "{" block "}" ;
params = param { "," param } ;
param = IDENT ":" type ;

Function Signature

Parameters MUST have explicit type annotations.

A function call evaluates to the value the function's body block evaluates to (see 4.5). Reaching the end of the body is not a distinct "implicit return": the body is an expression, and the call's value is that expression's value.

If a return type is specified, the body block MUST evaluate to a value of that type. If no return type is specified, the return type is (), and the body block evaluates to ().

fn add(x: i32, y: i32) -> i32 {
    x + y   // the body block evaluates to x + y, which is returned
}

fn do_nothing() {
    // the body block has no final expression, so it evaluates to ()
}

Inout Parameters

A parameter MAY be marked with the inout keyword to indicate that it is passed by reference and may be mutated by the callee. Changes made to an inout parameter are visible to the caller after the call returns.

param = [ param_mode ] IDENT ":" type ;
param_mode = "inout" | "borrow" ;

At the call site, an argument passed to an inout parameter MUST be marked with the inout keyword.

An argument to an inout parameter MUST be an lvalue (a variable, field access, or array index expression).

When a function is called with an inout argument:

  1. The address of the argument place is passed to the callee. For a field access or array index argument, this is the address of that field or element (an out-of-bounds index causes a runtime panic before the call); for a place rooted at a by-ref parameter of the caller, it is computed from the pointer the caller received
  2. The callee reads and writes to the argument through this address
  3. After the call returns, the original place holds the updated value, visible to the caller
fn increment(inout x: i32) {
    x = x + 1;
}

fn main() -> i32 {
    let mut n = 10;
    increment(inout n);
    n  // 11
}

A single function call MUST NOT pass the same variable to multiple inout parameters. This prevents aliasing of mutable references within a single call. The rule applies to the argument's root variable: two arguments that project different fields or elements of the same variable (such as o.a and o.b) are likewise rejected.

fn swap(inout a: i32, inout b: i32) {
    let tmp = a;
    a = b;
    b = tmp;
}

fn main() -> i32 {
    let mut x = 1;
    swap(inout x, inout x);  // error: cannot pass same variable to multiple inout parameters
    0
}

Borrow Parameters

A parameter MAY be marked with the borrow keyword to indicate that it is passed by reference for read-only access. The callee cannot mutate the borrowed value, and the value is not consumed (ownership is not transferred).

At the call site, an argument passed to a borrow parameter MUST be marked with the borrow keyword.

The body of a function MUST NOT mutate a borrow parameter. This includes:

  • Assignment to the parameter itself
  • Assignment to fields of the parameter
  • Assignment to array elements of the parameter
  • Passing the parameter, or any field or element of it, as an inout argument

The body of a function MUST NOT move out of a borrow parameter. A borrowed value cannot be returned, stored in a struct field, or passed to a function expecting an owned value.

When a function is called with a borrow argument:

  1. The address of the argument place is passed to the callee. For a field access or array index argument, this is the address of that field or element (an out-of-bounds index causes a runtime panic before the call); for a place rooted at a by-ref parameter of the caller, it is computed from the pointer the caller received
  2. The callee reads from the argument through this address
  3. After the call returns, the original variable is unchanged and still valid; a borrowed place is not moved out of its owner
struct Point { x: i32, y: i32 }

fn sum_coords(borrow p: Point) -> i32 {
    p.x + p.y
}

fn main() -> i32 {
    let p = Point { x: 10, y: 32 };
    let result = sum_coords(borrow p);
    result + p.x - p.x  // p is still valid after the borrow
}

Multiple borrow parameters MAY refer to the same variable. Unlike inout, borrows are shared read-only access.

fn sum_both(borrow a: i32, borrow b: i32) -> i32 {
    a + b
}

fn main() -> i32 {
    let x = 21;
    sum_both(borrow x, borrow x)  // OK: multiple borrows of same variable
}

A single function call MUST NOT pass the same variable to both a borrow parameter and an inout parameter. This enforces the law of exclusivity: either one inout or any number of borrow accesses, but never both simultaneously. As with rule 6.1:20, the rule applies to the argument's root variable: a borrow of one field and an inout of another field of the same variable are likewise rejected.

fn mixed(borrow a: i32, inout b: i32) {
    b = a + 1;
}

fn main() -> i32 {
    let mut x = 41;
    mixed(borrow x, inout x);  // error: cannot borrow and inout same variable
    0
}

Parameter Immutability

A parameter that is not marked inout is immutable within the function body. Assigning to such a parameter or modifying its fields is a compile-time error.

fn bad(x: i32) {
    x = 5;  // error: cannot assign to immutable parameter 'x'
}

struct Point { x: i32, y: i32 }

fn also_bad(p: Point) {
    p.x = 10;  // error: cannot assign to immutable parameter 'p'
}

Entry Point

A program MUST have a function named main.

The main function MUST return either i32 or (). When it returns i32, that value becomes the program's exit code. When it returns (), the exit code is 0.

fn main() -> i32 {
    42  // exit code is 42
}

Recursion

Functions MAY call themselves recursively.

fn factorial(n: i32) -> i32 {
    if n <= 1 { 1 }
    else { n * factorial(n - 1) }
}

fn main() -> i32 {
    factorial(5)  // 120
}

Function Visibility

Functions MAY call any function defined in the same module, regardless of definition order.

fn main() -> i32 {
    helper()  // can call function defined below
}

fn helper() -> i32 {
    42
}