Tour of Sailfin

This tour walks through Sailfin’s distinctive features. By the end, you’ll understand what makes Sailfin different and how its features work together.

Variables and Types

Sailfin uses let for immutable bindings and let mut for mutable ones:

let name: String = "Sailfin";
let mut counter: Int = 0;
counter = counter + 1;

Type inference works for most declarations:

let x = 42;           // Int
let pi = 3.14;        // Float
let active = true;    // Bool

Functions and Effects

Every function declares what it can do:

fn greet(name: String) ![io] {
    print.info("Hello, {{name}}!");
}

fn add(a: Int, b: Int) -> Int {
    return a + b;  // No effects needed — pure function
}

The ![io] annotation is an effect type. The compiler tracks effects transitively — if foo calls greet, then foo must also declare ![io].

Structs and Interfaces

struct User {
    name: String,
    email: String,
    age: Int,
}

interface Printable {
    fn display(&self) -> String;
}

impl Printable for User {
    fn display(&self) -> String {
        return "{{self.name}} <{{self.email}}>";
    }
}

Enums and Pattern Matching

enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn handle(result: Result<Int, String>) ![io] {
    match result {
        Ok(value) => print.info("Got: {{value}}"),
        Err(msg) => print.error("Failed: {{msg}}"),
    }
}

The Effect System

Effects are Sailfin’s capability system. The six canonical effects are:

EffectGrants access to
ioFilesystem, console, logging
netHTTP, WebSocket, network
modelAI model invocation
gpuGPU/accelerator access
randRandom number generation
clockTimers, sleep, wall-clock time
fn fetch_and_log(url: String) -> String ![io, net] {
    let response = http.get(url);
    print.info("Fetched {{response.status}}");
    return response.body;
}

Ownership and Borrowing

Sailfin uses move-by-default semantics:

let data = Vec.new();
let moved = data;       // data is moved — can't use data anymore
// print.info(data);    // ERROR: use after move

fn read_only(items: &Vec<Int>) {      // shared borrow
    print.info("Length: {{items.len()}}");
}

fn mutate(items: &mut Vec<Int>) {     // exclusive borrow
    items.push(42);
}

AI Constructs

Models, prompts, and pipelines are first-class:

model gpt4o = openai:"gpt-4o";

fn summarize(text: String) -> String ![model] {
    let result = prompt gpt4o ![model] {
        system "You are a concise summarizer."
        user "Summarize: {{text}}"
    };
    return result;
}

Error Handling

fn parse_config(path: String) -> Config ![io] {
    try {
        let content = fs.read(path);
        return Config.parse(content);
    } catch (e: IoError) {
        print.error("Failed to read config: {{e.message}}");
        throw e;
    }
}

Testing

Tests are built into the language:

test "addition works" {
    let result = add(2, 3);
    assert result == 5;
}

test "greet produces output" ![io] {
    greet("World");  // Effects required in tests too
}

What’s Next?