Functions

Basic functions

tape
fn add(a: i64, b: i64) -> i64 {
    return a + b;
}

Default parameters

tape
fn greet(name: string, loud: bool = false) {
    if (loud) {
        io.println("HELLO, " + name + "!");
    } else {
        io.println("Hello, " + name);
    }
}

greet("world");         // uses default
greet("world", true);   // overrides

Default parameters must be trailing — once a parameter has a default, all subsequent parameters must also have defaults.

Named arguments

Named arguments allow you to skip middle defaults and improve readability at call sites:

tape
fn create_window(title: string, width: i64 = 800, height: i64 = 600, vsync: bool = true) -> Window { }

create_window("My App");                          // all defaults
create_window("My App", 1024, 768);              // positional
create_window("My App", height: 400);            // skip width, keep its default
create_window("My App", vsync: false);           // skip width and height
create_window("My App", height: 400, width: 1024); // named, any order

Rules:

  • Positional arguments come first. Once a named argument appears, all subsequent arguments must also be named.
  • Named arguments can be in any order. They are matched by name, not position.
  • Only parameters with defaults can be skipped. Required parameters must always be provided (positionally or by name).
  • Naming is never required. Pure positional calling always works.
  • Duplicate parameters are an error. Providing the same parameter both positionally and by name fails at compile time.
tape
fn connect(host: string, port: i64 = 443, timeout_ms: i64 = 5000, tls: bool = true) -> Connection or NetError { }

connect("api.example.com", tls: false, timeout_ms: 1000);  // host positional, rest named
connect(host: "localhost");                                  // all named, port/timeout/tls default
connect(port: 443);                                          // ERROR: host is required

Named arguments work with methods (self/*self is never named — it’s passed via dot notation):

tape
fn Buffer.resize(*self, new_cap: i64 = 4096, zero_fill: bool = true) { }

buf.resize();                         // both defaults
buf.resize(8192);                     // positional
buf.resize(zero_fill: false);         // named, skip new_cap

Named arguments are not available through function pointers — pointers erase parameter names.

Methods

Use Type.name syntax to declare a method on a struct:

tape
struct Vec2 { x: f64; y: f64; }

fn Vec2.length(*self) -> f64 {
    return math.sqrt(self.x * self.x + self.y * self.y);
}

let v = Vec2 { x: 3.0, y: 4.0 };
let len = v.length();  // 5.0

The first parameter is self (by value) or *self (by pointer).

Visibility

tape
pub fn api_function() -> i64 { return 42; }
fn internal_helper() { }

pub makes a function visible to importers. Without it, the function is module-private.

Multiple functions can be exported together with a pub {} block:

tape
pub {
    fn init() { ... }
    fn update() { ... }
    fn shutdown() { ... }
}

External functions (C FFI)

tape
@link("kernel32");
extern fn GetTickCount() -> u32;

Calls a C function using the platform ABI. The compiler emits an ABI trampoline automatically.

Exported functions

tape
@export
pub fn tape_init() -> i32 {
    return 0;
}

Makes a tape function callable from C. The function uses the platform calling convention at the boundary.

Operator overloading

Define methods named op_* to overload operators on structs:

tape
struct Vec2 { x: f64; y: f64; }

// Arithmetic
fn Vec2.op_add(self, other: Vec2) -> Vec2 { return Vec2 { x: self.x + other.x, y: self.y + other.y }; }
fn Vec2.op_sub(self, other: Vec2) -> Vec2 { return Vec2 { x: self.x - other.x, y: self.y - other.y }; }
fn Vec2.op_mul(self, other: Vec2) -> Vec2 { return Vec2 { x: self.x * other.x, y: self.y * other.y }; }
fn Vec2.op_div(self, other: Vec2) -> Vec2 { return Vec2 { x: self.x / other.x, y: self.y / other.y }; }
fn Vec2.op_mod(self, other: Vec2) -> Vec2 { return Vec2 { x: self.x % other.x, y: self.y % other.y }; }

// Unary negation
fn Vec2.op_neg(self) -> Vec2 { return Vec2 { x: -self.x, y: -self.y }; }

// Comparison
fn Vec2.op_eq(self, other: Vec2) -> bool { return self.x == other.x && self.y == other.y; }
fn Vec2.op_lt(self, other: Vec2) -> bool { return self.x < other.x; }
fn Vec2.op_lte(self, other: Vec2) -> bool { return self.x <= other.x; }
fn Vec2.op_gt(self, other: Vec2) -> bool { return self.x > other.x; }
fn Vec2.op_gte(self, other: Vec2) -> bool { return self.x >= other.x; }

Usage:

tape
let a = Vec2 { x: 1.0, y: 2.0 };
let b = Vec2 { x: 3.0, y: 4.0 };
let c = a + b;    // op_add
let d = -a;       // op_neg
let eq = a == b;  // op_eq
let ne = a != b;  // auto-derived from op_eq

!= is automatically derived from op_eq.

Last modified: