Skip to main content

Functions

Functions are data components, exactly like fields — attached to a node, reachable by dot-path, and first-class values you can store, pass around, and call from anywhere with access to them.

Declaring & Returning

The last expression in a function, without a trailing ;, is its return value — return also works, for an early exit:

declare.stof
fn add(a: float, b: float) -> float {
  a + b
}

fn greet(name: str = "world") -> str {
  return `Hello, ${name}!`;
}

#[main]
fn main() {
  pln(self.add(2, 3));
  pln(self.greet());
  pln(self.greet("Stof"));
}
Output

One gotcha worth knowing: a semicolon after that last expression changes its meaning. fn f() -> str { 42; } doesn't return 42 — the ; turns it into a statement, so the function returns nothing, and a declared -> str return type with nothing to return is an error. Drop the trailing ; on whatever line should be the return value or use a return statement.

Optional & Named Parameters

A ? suffix on a parameter's type makes it optional; calling with name = value passes arguments by name instead of position, in any order:

params.stof
fn create(id: str!, expires?: ms) {
  pln(id, expires);
}

fn total(a: int, b: int) -> int {
  a + b
}

#[main]
fn main() {
  self.create('abc123');
  self.create('abc123', 5000ms);

  pln(self.total(b = 30, a = 12));
}
Output

Arrow Functions

Shorthand for a function as an expression — as a local variable, or directly as a field's value:

arrows.stof
double: (x: int): int => x * 2

#[main]
fn main() {
  const add = (a: int, b: int): int => a + b;
  pln(add(5, 5));
  pln(self.double(21));
}
Output

Functions as Fields

A field can hold a function directly — call it the same way you'd call any other function:

as-field.stof
name: "Bob Smith"
message: (name: str = self.name): str => `Hi, ${name}`

#[main]
fn main() {
  pln(self.message());
  pln(self.message("Ada"));
}
Output

message's default parameter reads self.name at call time — so calling it with no arguments still resolves against whatever self is at that point.

Recursion with this

this refers to the function currently running — useful for recursion without hardcoding the function's own name:

recursion.stof
fn fibonacci(n: int) -> int {
  if (n <= 1) { n }
  else { this.call(n - 1) + this(n - 2) }
}

#[main]
fn main() {
  pln(self.fibonacci(10));
}
Output

What's Not Here Yet

Functions can also carry custom attributes (#[my_attr]), run asynchronously (async fn, #[async]), and be called on a prototype directly for static-like behavior via <TypeName>.func() — each of those gets its own dedicated page.