Skip to main content

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:

error_interface.mux
Loading...

string implements Error, so existing result<T, string> code continues to work.

Basic Usage

result_basic.mux
Loading...

result Variants

result_variants.mux
Loading...

Creating result Values

creating_results.mux
Loading...

result Methods

MethodReturnsDescription
.is_ok()boolReturns true if the result is an ok variant
.is_err()boolReturns true if the result is an err variant
.to_string()stringString representation
result_methods.mux
Loading...

Pattern Matching Results

pattern_matching_results.mux
Loading...

Ignoring Error Details

Use _ when you don't need the error value:

ignoring_error_details.mux
Loading...

optional<T>

The optional type represents values that may or may not exist.

Basic Usage

optional_basic.mux
Loading...

optional Methods

MethodReturnsDescription
.is_some()boolReturns true if the optional contains a value
.is_none()boolReturns true if the optional is empty
.to_string()stringString representation
optional_methods.mux
Loading...

result Methods

optional_variants.mux
Loading...

Creating optional Values

creating_optionals.mux
Loading...

Safe Collection Access

Collections return optional<T> for safe access:

safe_collection_access.mux
Loading...

Map Lookups

map_lookups.mux
Loading...

Ignoring the Value

Use _ when you only care about presence/absence:

ignoring_values.mux
Loading...

Combining result and optional

optional of result

optional_of_result.mux
Loading...

result of optional

result_of_optional.mux
Loading...

Error Propagation Patterns

Early Returns

early_returns.mux
Loading...

Nested Matching

nested_matching.mux
Loading...

Fallible Type Conversions

String and char parsing return result because they can fail:

fallible_conversions.mux
Loading...

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 return statements
  • 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

  1. Return result for fallible operations - Parse failures, I/O operations, validation
  2. Return optional for nullable values - Collection access, lookups, searches
  3. Match exhaustively - Handle both success and error cases
  4. Use descriptive error messages - Include context in error strings
  5. Early returns for errors - Reduces nesting
  6. Use _ for ignored values - Makes intent explicit
  7. Don't overuse wildcards - Match specific cases when possible
  8. Document error conditions - What errors can a function return?
  9. Chain operations explicitly - No ? operator, use match
  10. Prefer result over panicking - Explicit > implicit

Common Patterns

Validation

validation_pattern.mux
Loading...

Lookup with Default

lookup_default.mux
Loading...

Transform result

transform_result.mux
Loading...

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