* Documentation

Examples

Copy-paste programs showcasing structs, enums, generics, FFI, error handling, and more.

All examples below compile as-is against Turf v1.0.0. Save any of them to a .tr file, then turf file.tr -o file && ./file.

Structs, constructors, instance methods, and static methods

extern fn strlen(s: string): int;

struct User { age: int, name: string }

fn User.new(age: int, name: string): void { self.age = age; self.name = name; }
fn User.grow(): void { self.age = self.age + 1; }
fn User.rename(new_name: string): void { self.name = new_name; }
fn User.describe() static: string { return "a user"; }

fn main(): int {
    mut u: User = User::new(0, "unnamed");
    u.grow();
    assert(u.age == 1, "");

    u.rename("Bob");
    assert(User::describe() == "a user", "");
    return 0;
}

Enums and modules

import io;

enum Status { Pending, Active, Closed }

fn describe(s: Status): string {
    compare s {
        matches Status::Pending:
            return "pending";
        done
        matches Status::Active:
            return "active";
        done
        otherwise:
            return "closed";
    }
}

fn main(): int {
    immut s: Status = Status::Active;
    io::printline(describe(s));
    assert(Status::COUNT == 3, "");
    return 0;
}

Arrays of structs

extern fn strlen(s: string): int;

struct User {
    age: int,
    name: string
}

fn User.new(age: int, name: string): void {
    self.age = age;
    self.name = name;
}

fn main(): int {
    mut users: User[5] = [
        User::default(),
        User::default(),
        User::default(),
        User::default(),
        User::default()
    ];

    users[0] = User::new(25, "Alice");
    users[1] = User::new(30, "Bob");
    users[2] = User::new(35, "Charlie");

    assert(users[0].age == 25, "");
    assert(strlen(users[0].name) == 5, "");

    assert(users[1].age == 30, "");
    assert(strlen(users[1].name) == 3, "");

    assert(users[2].age == 35, "");
    assert(strlen(users[2].name) == 7, "");

    return 0;
}

Casting and type conversion

fn main(): int {
    mut a: double = 42 to double;
    assert(a > 41.9 && a < 42.1, "");

    mut b: int = 3.75 to int;
    assert(b == 3, ""); // truncates the decimal part

    mut c: int = "100" to int;
    assert(c == 100, "");

    mut x: int = 10;
    mut s9: string = x to string;

    return 0;
}

Loops, break, continue, range-for

fn main(): int {
    mut i: int = 0;
    mut sum: int = 0;

    while i < 10 {
        i = i + 1;
        if i % 2 == 0 {
            continue;
        }
        sum = sum + i;
    }
    assert(sum == 25, "");

    mut for_sum: int = 0;
    for j in 1..10 step j += 1 {
        if j % 2 == 0 {
            continue;
        }
        for_sum = for_sum + j;
    }
    assert(for_sum == 25, "");

    return 0;
}

Error handling with recover

import io;

error MathError {
    DivisionByZero
}

fn divide(a: int, b: int): !int {
    if b == 0 {
        return MathError::DivisionByZero;
    }
    return a / b;
}

fn tryDivide(a: int, b: int): int {
    return divide(a, b) recover as err run {
        if err == MathError::DivisionByZero {
            io::printline("division by zero");
        }
        return -1;
    };
}

fn main(): int {
    mut x: int = divide(10, 0) recover produce 0;
    assert(x == 0, "");
    assert(tryDivide(10, 0) == -1, "");
    assert(tryDivide(10, 2) == 5, "");
    return 0;
}

Generics: a user-defined Box<T> and the stdlib’s List<T>

import io;
import list::List;

struct Box<T> {
    val: T
}

fn Box.new(v: T): void {
    self.val = v;
}

fn Box.get(): T {
    return self.val;
}

fn main(): int {
    mut b: Box<int> = Box::new(42);
    assert(b.get() == 42, "");

    mut nums: List<int> = List::default();
    nums.push(10);
    nums.push(20);
    assert(nums.length() == 2, "");

    return 0;
}

State machines

import io;

machine TrafficLight {
    Red -> Green,
    Green -> Yellow,
    Yellow -> Red
}

fn main(): int {
    mut light: TrafficLight = TrafficLight::Red;

    compare light {
        matches TrafficLight::Red:
            light.transition(TrafficLight::Green);   // compile-time-proven safe
        done
        otherwise:
            io::printline("not red");
    }

    assert(light == TrafficLight::Green, "");
    return 0;
}

FFI — pointers and memory

extern fn malloc(size: int): ptr;
extern fn free(p: ptr): void;
extern fn memset(p: ptr, c: int, n: int): ptr;
extern fn memcpy(dst: ptr, src: ptr, n: int): ptr;
extern fn strlen(s: string): int;

// An ordinary function may never take or return a raw `ptr`/`T*` (even
// inside `unsafe { }`) -- only `extern fn` bindings and lib/std can, so
// buffer allocation stays inlined in main() below instead of its own helper.

fn main(): int {
    unsafe {
        mut b: ptr = malloc(32);
        memset(b, 0, 32);
        free(b);
    }

    mut len: int = strlen("verify");
    assert(len == 6, "");

    return 0;
}

A larger FFI showcase — file I/O, libm, environment variables, sockets, variadic printf via extern fn printf(fmt: string, ...): int;, and linking against arbitrary system libraries via extern from "libname" fn — lives in the test suite alongside the rest of the examples above.

Real-world-shaped programs

The showcase/ directory has larger programs combining multiple stdlib modules — no extern fn/ptr in the showcase files themselves:

See that directory’s own files for details on running each one.