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 it can be used as a Result's error type.

Propagating with use​

use extracts a successful value. If the operation fails, it returns the error from the enclosing function immediately. Sequential operations stay flat:

use_result.mux
Loading...

For an Optional, use extracts some(value) or returns none:

use_optional.mux
Loading...

The enclosing function must return the same wrapper kind. Result error types must be compatible; the success types may differ. An Optional does not need an error value. There is no automatic conversion between none and err.

use is a prefix expression with unary precedence. Calls and member access bind more tightly, so use text.to_int() extracts the call's result. Write (use text.to_int()).to_string() to call a method on the extracted integer. It also works in arguments, collection elements, assignments, conditions, and return expressions. A standalone use operation() handles a Result with a void success payload. Each operand runs once, and ordinary short-circuit rules still apply. Inside a lambda, failure returns from that lambda.

Functions returning result<void, E> use ok() for a successful completion; value-carrying Results continue to use ok(value).

Checking before extracting​

Use inspection methods when failure needs local handling:

checked_result.mux
Loading...

The compiler checks that value() or error() is valid on every path reaching the access. Printing an error and continuing does not make value() safe. Optional values follow the same rule with is_some(), is_none(), and value(). Checks can establish facts through branches and saved booleans, but changing the checked value invalidates those facts. Access is rejected when the compiler cannot prove the variant.

Basic Usage​

result_basic.mux
Loading...

Assigning from a match​

match can produce a value. Keep each arm readable across several lines: a successful arm yields the value, while a failure arm can explicitly return the current function's error.

assigning_from_match.mux
Loading...

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:

custom_error_type.mux
Loading...

Creating result Values​

creating_results.mux
Loading...

result Methods​

MethodReturnsDescription
.is_ok()boolTests for success; narrows checked branches
.is_err()boolTests for failure; narrows checked branches
.value()TExtracts the payload when success is proved
.error()EExtracts the payload when failure is proved
.to_string()stringString representation

Use match for pattern-specific behavior and transformations of both outcomes.

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()boolTests for a present value; narrows checked branches
.is_none()boolTests for absence; narrows checked branches
.value()TExtracts the payload when presence is proved
.to_string()stringString representation

Use use to propagate absence, inspection for local handling, and match when both cases need different behavior.

Optional variants​

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.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...

Flat Sequential Propagation​

flat_sequential_propagation.mux
Loading...

When each step should propagate its error unchanged, use keeps the happy path flat and makes the enclosing return type explicit. Use a match when an error needs local transformation or both outcomes require distinct work.

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: use keeps sequential operations flat
  • 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 the ? operator; Mux uses the explicit use prefix instead

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. Use use for propagation - Reduces nesting while preserving typed errors
  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. Flatten sequential fallible operations - Use use to propagate failure, or inspection methods and early returns for custom handling
  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