The Effect System

Sailfin’s effect system is a compile-time capability mechanism. Functions declare what they’re allowed to do, and the compiler enforces it.

Canonical Effects

EffectCapabilityExamples
ioFilesystem, console, loggingprint.*, fs.*, console.*
netNetwork accesshttp.*, websocket.*, serve
modelAI model invocationprompt blocks, model .call()
gpuGPU/accelerator accessTensor operations, @gpu blocks
randRandom number generationrand.*
clockTime operationssleep, runtime.sleep

Declaring Effects

Effects are declared with the ![] syntax after the parameter list:

fn read_file(path: String) -> String ![io] {
    return fs.read(path);
}

fn fetch(url: String) -> Response ![net] {
    return http.get(url);
}

fn analyze(text: String) -> Analysis ![io, model] {
    let result = prompt gpt4o ![model] {
        system "Analyze the following text."
        user "{{text}}"
    };
    print.info("Analysis complete");
    return result;
}

Pure Functions

Functions without effect annotations are pure — they can’t perform IO, network calls, or any side effects:

fn add(a: Int, b: Int) -> Int {
    return a + b;  // Pure computation — no effects needed
}

Transitive Enforcement

If function A calls function B, then A must declare at least the effects that B declares:

fn helper() ![io, net] {
    let data = http.get("https://api.example.com");
    print.info("Fetched data");
}

// ERROR: missing ![net] — helper() requires it
fn bad_caller() ![io] {
    helper();
}

// OK: declares both effects
fn good_caller() ![io, net] {
    helper();
}

Effects in Tests

Tests also declare effects:

test "reads config file" ![io] {
    let config = read_file("test.toml");
    assert config.length > 0;
}

Diagnostics

The compiler produces detailed diagnostics when effects are violated, including source spans and suggested fixes:

error[E0301]: function `process` calls `fetch` which requires ![net],
              but `process` only declares ![io]
  --> src/main.sfn:12:5
   |
12 |     let data = fetch(url);
   |                ^^^^^ requires ![net]
   |
   = help: add `net` to the effect list: `fn process(url: String) ![io, net]`

Next Steps