Skip to content

Result@(T, E)

← Back to Standard Library

Type-safe error handling with explicit success and error types.

Overview

Result@(T, E) is a generic enum that represents either success (Ok) containing a value of type T, or failure (Err) containing an error of type E.

All functions in Sushi implicitly return Result@(T, E) where: - T is the declared return type - E is the error type (defaults to StdError if not specified)

Type Syntax

Implicit Return with Default Error

fn add(i32 a, i32 b) i32:
    return Result.Ok(a + b)
# Actually returns Result@(i32, StdError)

Custom Error Type

enum MathError:
    DivisionByZero
    Overflow

fn divide(i32 a, i32 b) i32 | MathError:
    if (b == 0):
        return Result.Err(MathError.DivisionByZero)
    return Result.Ok(a / b)
# Returns Result@(i32, MathError)

Explicit Syntax

fn foo() Result@(i32, MyError):
    return Result.Ok(42)

Standard Error Enums

Sushi provides built-in error types. Each but StdError has a HOME module, and the import is what brings the bare name into a unit (docs/design/unit-namespaces.md, #574). A module whose calls answer an enum re-exports its home (#586), so the import a program already writes is the one that brings the name:

enum home also brought by
StdError none -- the implicit Result arm is in scope everywhere
MathError use <math>
FileMode use <io/fs>
IoError, FileError use <io/error> <io/contracts>, <io/fs>, <io/buf>, <io/files>, <net/tcp>
SeekFrom use <io/contracts> <io/fs>, <io/buf>, <net/tcp>
NetError use <net/error> <net/tcp>, <net/udp>, <net/dns>, <net/ip>
ProcessError use <sys/process>
EnvError use <sys/env>

StdError

Generic fallback for simple errors.

  • StdError.Error - Generic error condition

MathError

Mathematical operation errors.

  • MathError.DivisionByZero - Division by zero
  • MathError.Overflow - Arithmetic overflow
  • MathError.Underflow - Arithmetic underflow
  • MathError.InvalidInput - Invalid mathematical input

FileError

File system operation errors.

  • FileError.NotFound - File does not exist
  • FileError.PermissionDenied - Insufficient permissions
  • FileError.AlreadyExists - File already exists
  • FileError.IsDirectory - Path refers to a directory
  • FileError.DiskFull - No space left on device
  • FileError.TooManyOpen - Too many open files
  • FileError.InvalidPath - Invalid file path
  • FileError.IOError - Generic I/O error
  • FileError.Other - Any other error

IoError

I/O operation errors.

  • IoError.ReadError - Read operation failed
  • IoError.WriteError - Write operation failed
  • IoError.FlushError - Flush operation failed

ProcessError

Process management errors.

  • ProcessError.SpawnFailed - Failed to spawn process
  • ProcessError.ExitFailure - Process exited with error
  • ProcessError.SignalReceived - Process terminated by signal

EnvError

Environment variable errors.

  • EnvError.NotFound - Environment variable not found
  • EnvError.InvalidValue - Invalid environment variable value
  • EnvError.PermissionDenied - Insufficient permissions

Constructors

Result.Ok(value)

Create a success result containing a value.

fn get_answer() i32:
    return Result.Ok(42)

Result.Err(error)

Create an error result containing an error value.

fn divide(i32 a, i32 b) i32 | MathError:
    if (b == 0):
        return Result.Err(MathError.DivisionByZero)
    return Result.Ok(a / b)

Important: Result.Err() requires an error value. Calling it with zero arguments is a compile error (CE2050 — wrong argument count for the Err variant), not a deprecation warning.

Methods

.is_ok() -> bool

Check if the Result is an Ok variant.

let Result@(i32, MathError) result = divide(10, 2)
if (result.is_ok()):
    println("Success!")

.is_err() -> bool

Check if the Result is an Err variant.

let Result@(i32, MathError) result = divide(10, 0)
if (result.is_err()):
    println("Division failed")

.err() -> Maybe@(E)

Extract the error value if present, otherwise return Maybe.None().

let Result@(i32, MathError) result = divide(10, 0)
let Maybe@(MathError) error = result.err()

match error:
    Maybe.Some(e) ->
        println("Error occurred: {e}")
    Maybe.None() ->
        println("No error")

.expect(message: string) -> T

Unwrap the Ok value or panic with the given message if Err.

let Result@(i32, MathError) result = divide(10, 2)
let i32 value = result.expect("Division should not fail")
# Prints "ERROR: Division should not fail" and exits if Err

Warning: Use expect() sparingly. It will terminate the program if the Result is Err.

.realise(default: T) -> T

Extract the Ok value or return a default value if Err.

let Result@(i32, MathError) result = divide(10, 0)
let i32 value = result.realise(0)  # Returns 0 on error

Error Propagation with ??

The ?? operator unwraps a Result or propagates the error to the caller.

fn compute() i32 | MathError:
    let i32 x = divide(10, 2)??  # Unwraps or returns early
    let i32 y = divide(20, 5)??
    return Result.Ok(x + y)

Error Type Matching

The ?? operator requires error types to match exactly:

enum ErrorA:
    Error

enum ErrorB:
    Error

fn inner() i32 | ErrorA:
    return Result.Ok(42)

fn outer() i32 | ErrorB:
    let i32 x = inner()??  # ❌ Error: cannot propagate ErrorA to ErrorB
    return Result.Ok(x)

To use ??, the inner function's error type must match the outer function's error type:

fn outer() i32 | ErrorA:
    let i32 x = inner()??  # ✅ Both use ErrorA
    return Result.Ok(x)

Warning: Do NOT Use ?? in main()

Using ?? in the main() function generates a compiler warning and is highly discouraged:

fn main() i32:
    let i32 x = risky()??  # ⚠️ Warning CW2511
    return Result.Ok(0)

Instead, use explicit error handling:

fn main() i32:
    match risky():
        Result.Ok(x) ->
            println("Got: {x}")
            return Result.Ok(0)
        Result.Err(e) ->
            println("Failed")
            return Result.Ok(1)

Pattern Matching

Match on both success and error cases:

match divide(10, 2):
    Result.Ok(value) ->
        println("Result: {value}")
    Result.Err(MathError.DivisionByZero) ->
        println("Cannot divide by zero")
    Result.Err(e) ->
        println("Other error: {e}")

Usage in Conditionals

Result can be used directly in if statements (checks for Ok):

if (divide(10, 2)):
    println("Success!")
else:
    println("Failed")

Best Practices

  • Always handle errors explicitly - Don't ignore Result values
  • Use ?? for error propagation - In function chains with matching error types
  • Use .realise(default) for fallback values - When a default makes sense
  • Use pattern matching for detailed error handling - When you need different behavior per error variant
  • Avoid expect() in production code - It terminates the program on error
  • Avoid ?? in main() - Use explicit error handling instead
  • Keep error types consistent - Makes error propagation easier
  • Define custom error enums - For domain-specific error conditions

Examples

Basic Error Handling

enum ValidationError:
    TooShort
    TooLong
    InvalidCharacters

fn validate_username(string name) ~ | ValidationError:
    if (name.len() < 3):
        return Result.Err(ValidationError.TooShort)
    if (name.len() > 20):
        return Result.Err(ValidationError.TooLong)
    return Result.Ok(~)

Error Propagation Chain

fn read_config() string | FileError:
    let File f = open("config.txt", FileMode.Read())??
    let string content = f.read_all()??
    return Result.Ok(content)

Combining with Maybe

fn safe_divide(i32 a, i32 b) i32 | MathError:
    if (b == 0):
        return Result.Err(MathError.DivisionByZero)
    return Result.Ok(a / b)

fn process() i32 | MathError:
    let Result@(i32, MathError) result = safe_divide(10, 2)
    let Maybe@(MathError) error = result.err()

    if (error.is_some()):
        return Result.Err(error.realise(MathError.DivisionByZero))

    let i32 value = result.realise(0)
    return Result.Ok(value)