Error Handling

Errors in Aria are values, not exceptions. They're declared in function signatures, checked at compile time, and handled with minimal syntax. This is one of Aria's most significant advantages for AI code generation.

Declaring Errors

Errors are sum types. Functions declare their error type with !:

type IoError =
    | NotFound { path: str }
    | PermissionDenied { path: str, user: str }
    | Timeout { after: dur }

// This function returns str on success, IoError on failure
fn readFile(path: str) -> str ! IoError {
    ...
}

The Four Handling Patterns

1. Propagation with ?

The most common pattern. One token. Context is injected automatically (file, line, function).

fn loadConfig(path: str) -> Config ! IoError {
    content := io.readFile(path)?       // propagates on error
    json.decode[Config](content)?       // propagates on error
}

This is where Aria saves the most tokens. Compare to Go:

// Go: ~15 tokens PER error check
content, err := os.ReadFile(path)
if err != nil {
    return Config{}, fmt.Errorf("reading config: %w", err)
}

// Aria: 1 token
content := io.readFile(path)?

2. Inline Recovery with catch

Handle the error and provide a fallback value:

content := io.readFile("config.json") catch |err| {
    log.warn("Config not found, using defaults: {err}")
    yield "{}"
}

// With pattern matching on the error
result := fetchData(url) catch |err| {
    match err {
        Timeout{after} => yield cachedData
        _ => return err(err)
    }
}

3. Full Match

When you need to handle every error variant:

match io.readFile("config.json") {
    Ok(content) => process(content)
    Err(NotFound{path}) => createDefault(path)
    Err(PermissionDenied{path, user}) => {
        log.error("{user} cannot read {path}")
        exit(1)
    }
    Err(Timeout{after}) => retry()
}

4. Assert Success with !

For cases where failure is a programming error:

// Panics with a stack trace if the operation fails
config := io.readFile("known-good.txt")!

Composing Errors

type AppError =
    | Io(str)
    | Parse(str)
    | Database(str)
    | NotFound(str)

fn processRequest(req: Request) -> Response ! AppError {
    user := db.findUser(req.userId) catch |e| {
        return err(AppError.Database("{e}"))
    }
    data := io.readFile(user.configPath) catch |e| {
        return err(AppError.Io("{e}"))
    }
    config := json.decode[Config](data) catch |e| {
        return err(AppError.Parse("{e}"))
    }
    buildResponse(config)
}

Why This Matters for AI

LanguageTokens per error check5 fallible calls
Go~15~75
C++~10~50
Rust~3-8~40
Aria15

Fewer tokens means faster generation, lower cost, and fewer chances for the AI to introduce pattern-matching bugs in repetitive boilerplate.

Next Steps