Effective Sailfin

This guide covers patterns and practices for writing clear, safe, and performant Sailfin code. It’s similar in spirit to Effective Go.

Naming Conventions

  • Types, structs, enums, interfaces: CamelCaseUserProfile, HttpClient
  • Functions, variables, fields: snake_casefetch_data, user_count
  • Constants: UPPER_SNAKE_CASEMAX_RETRIES, DEFAULT_TIMEOUT
  • Model bindings: CamelCaseGpt4o, ClaudeSonnet
  • File names: snake_case.sfnhttp_client.sfn, effect_checker.sfn

Minimize Effect Scope

Declare the narrowest set of effects possible:

// Prefer: separate pure computation from effectful IO
fn compute(data: Array<Int>) -> Int {
    return data.reduce(|a, b| a + b);
}

fn process_and_log(data: Array<Int>) ![io] {
    let result = compute(data);  // pure — no effects
    print.info("Result: {{result}}");
}

Use Borrows When Possible

Prefer borrowing over ownership transfer:

// Good: borrows the data, caller retains ownership
fn analyze(data: &Array<Record>) -> Report {
    // ...
}

// Avoid: takes ownership unnecessarily
fn analyze_owned(data: Array<Record>) -> Report {
    // ...
}

Organize with Modules

Use mod.sfn files to define public APIs:

http_client/
├── mod.sfn          # Re-exports public API
├── client.sfn       # Implementation
├── request.sfn      # Request types
└── response.sfn     # Response types

Error Handling

Prefer Result<T, E> for expected failures, try/catch for exceptional conditions:

// Expected failure: return Result
fn parse_config(s: String) -> Result<Config, ParseError> { ... }

// Exceptional: use try/catch
fn main() ![io] {
    try {
        let config = parse_config(input).unwrap();
        run(config);
    } catch (e) {
        print.error("Fatal: {{e.message}}");
    }
}

Next Steps

This guide will expand as the language matures toward 1.0. For now, see: