Error Handling
Mux uses explicit error handling through the result<T, E> and optional<T> types, avoiding exceptions entirely.
result<T, E>
The result type represents operations that can succeed with a value of type T or fail with an error of type E.
E must implement the Error interface:
string implements Error, so existing result<T, string> code continues to work.
Basic Usage
result Variants
result<T, E> is built in, not a user enum. It has two cases - ok carrying a
T, and err carrying an E - and you construct them with the ok and err
functions rather than through an enum name. Declaring your own type named
result is an error, because it used to overwrite the built-in silently.
The error type must implement Error, which means supplying message().
string does, so result<int, string> works out of the box; a class can too:
Creating result Values
result Methods
| Method | Returns | Description |
|---|---|---|
.is_ok() | bool | Returns true if the result is an ok variant |
.is_err() | bool | Returns true if the result is an err variant |
.to_string() | string | String representation |
Pattern Matching Results
Ignoring Error Details
Use _ when you don't need the error value:
optional<T>
The optional type represents values that may or may not exist.
Basic Usage
optional Methods
| Method | Returns | Description |
|---|---|---|
.is_some() | bool | Returns true if the optional contains a value |
.is_none() | bool | Returns true if the optional is empty |
.to_string() | string | String representation |
result Methods
optional<T> is built in and has two cases - some carrying a T, and
none. Like result, you build it with the some and none functions rather
than through an enum name, and you cannot declare a type named optional
yourself:
Creating optional Values
Safe Collection Access
Collections return optional<T> for safe access:
Map Lookups
Ignoring the Value
Use _ when you only care about presence/absence:
Combining result and optional
optional of result
result of optional
Error Propagation Patterns
Early Returns
Nested Matching
Fallible Type Conversions
String and char parsing return result because they can fail:
Technical Implementation
Memory Layout
Both types use a uniform runtime representation:
pub struct result<T, E> {
discriminant: i32, // 0 = ok, 1 = err
data: *mut T, // pointer to value
}
pub struct optional<T> {
discriminant: i32, // 0 = none, 1 = some
data: *mut T, // pointer to value
}Benefits:
- Single runtime representation: Collections can store either
- No enum overhead: No runtime enum tag beyond discriminant
- Easy error propagation: Simple with match statements
- Interop: optional and result can wrap the same types
Runtime ABI note
• Implementation detail: the runtime now represents both optional<T> and result<T, E> as boxed Value pointers (*mut Value) at the FFI boundary. This means runtime constructors and C-exported helpers return *mut Value for these types. Compiler-generated code and native extensions should treat optionals/results as boxed Value objects and use the provided discriminant helpers when matching variants.
Comparison with Other Languages
vs Rust
Very similar:
// Rust
fn divide(a: i32, b: i32) -> result<i32, String> {
if b == 0 {
err("division by zero".to_string())
} else {
ok(a / b)
}
}
// Mux
func divide(int a, int b) returns result<int, string> {
if b == 0 {
return err("division by zero")
}
return ok(a / b)
}Differences:
- Mux uses explicit
returnstatements - Rust has
?operator for error propagation (Mux doesn't)
vs Go
Similar philosophy, different syntax:
// Go
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Mux
func divide(int a, int b) returns result<int, string> {
if b == 0 {
return err("division by zero")
}
return ok(a / b)
}Mux advantage:
- Type system enforces error handling
- Cannot ignore errors without explicit match
vs Exceptions (Java/Python)
Different paradigm:
# Python
def divide(a, b):
if b == 0:
raise ValueError("division by zero")
return a / b
# Mux
func divide(int a, int b) returns result<int, string> {
if b == 0 {
return err("division by zero")
}
return ok(a / b)
}Mux advantages:
- Errors visible in function signature
- Cannot forget to handle errors
- No runtime exceptions
- No try/catch blocks
Best Practices
- Return result for fallible operations - Parse failures, I/O operations, validation
- Return optional for nullable values - Collection access, lookups, searches
- Match exhaustively - Handle both success and error cases
- Use descriptive error messages - Include context in error strings
- Early returns for errors - Reduces nesting
- Use
_for ignored values - Makes intent explicit - Don't overuse wildcards - Match specific cases when possible
- Document error conditions - What errors can a function return?
- Chain operations explicitly - No
?operator, use match - Prefer result over panicking - Explicit > implicit
Common Patterns
Validation
Lookup with Default
Transform result
See Also
- Enums - result and optional as tagged unions
- Control Flow - Pattern matching with match
- Types - Fallible type conversions
- Collections - Safe collection access with optional