Result¶
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 six built-in error types:
StdError¶
Generic fallback for simple errors.
StdError.Error- Generic error condition
MathError¶
Mathematical operation errors.
MathError.DivisionByZero- Division by zeroMathError.Overflow- Arithmetic overflowMathError.Underflow- Arithmetic underflowMathError.InvalidInput- Invalid mathematical input
FileError¶
File system operation errors.
FileError.NotFound- File does not existFileError.PermissionDenied- Insufficient permissionsFileError.AlreadyExists- File already existsFileError.InvalidPath- Invalid file pathFileError.IoError- Generic I/O error
IoError¶
I/O operation errors.
IoError.Read- Read operation failedIoError.Write- Write operation failedIoError.Flush- Flush operation failed
ProcessError¶
Process management errors.
ProcessError.Spawn- Failed to spawn processProcessError.Exit- Process exited with errorProcessError.Signal- Process terminated by signal
EnvError¶
Environment variable errors.
EnvError.NotFound- Environment variable not foundEnvError.InvalidValue- Invalid environment variable valueEnvError.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() must now include an error value. The old syntax without error data is deprecated.
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()??
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)