Mojo Error Handling

Error handling gives your program a controlled way to respond when something goes wrong — a file is missing, a number is invalid, or a network call fails. Mojo provides the raises keyword and the Error type to propagate problems up the call stack without crashing the program silently.

The Error Propagation Concept

  read_file()     ← fails (file not found)
       │
       └─raises──→ parse_data()
                        │
                        └─raises──→ main()
                                        │
                                        └── handle error gracefully

  Without error handling:
    read_file() crashes the program immediately.

  With error handling:
    The error travels up until something handles it or the program ends cleanly.

Functions That Can Fail

Add raises to a function signature to declare that it can produce an error. Any function calling it must either handle the error or also declare raises.

fn divide(a: Float64, b: Float64) raises -> Float64:
    if b == 0.0:
        raise Error("Cannot divide by zero")
    return a / b

fn main() raises:
    var result = divide(10.0, 2.0)
    print(result)   # 5.0

    var bad = divide(5.0, 0.0)   # This will raise
    print(bad)                    # Never reached

Catching Errors with try/except

Wrap calls that might fail in a try block. When an error occurs, execution jumps to the matching except block. The program continues normally after the handler.

fn divide(a: Float64, b: Float64) raises -> Float64:
    if b == 0.0:
        raise Error("Division by zero")
    return a / b

fn safe_divide(a: Float64, b: Float64) -> Float64:
    try:
        return divide(a, b)
    except e:
        print("Error caught:", str(e))
        return 0.0   # safe fallback

fn main():
    print(safe_divide(10.0, 2.0))   # 5.0
    print(safe_divide(10.0, 0.0))   # Error caught: Division by zero \n 0.0
Flow diagram:
  safe_divide(10, 0)
       │
       ├── try: divide(10, 0)
       │         │
       │         └── raise Error("Division by zero")
       │
       └── except e:
               │
               ├── print error message
               └── return 0.0

The Error Type

The built-in Error type wraps an error message string. You create one by passing a descriptive string to Error(). Access the message with str(e) inside the except block.

fn validate_age(age: Int) raises:
    if age < 0:
        raise Error("Age cannot be negative: " + String(age))
    if age > 150:
        raise Error("Age " + String(age) + " seems unrealistic")

fn main():
    try:
        validate_age(-5)
    except e:
        print("Validation failed:", str(e))
        # Validation failed: Age cannot be negative: -5

    try:
        validate_age(25)
        print("Age 25 is valid")   # Age 25 is valid
    except e:
        print("This won't run")

Re-raising Errors

Sometimes you catch an error, add context, and re-raise it so higher-level code can also see it.

fn load_config(path: String) raises -> String:
    # Imagine reading a file here
    if path == "":
        raise Error("Empty path provided")
    return "config data"

fn start_app(config_path: String) raises:
    try:
        var config = load_config(config_path)
        print("App started with:", config)
    except e:
        raise Error("App startup failed: " + str(e))

fn main():
    try:
        start_app("")
    except e:
        print(str(e))
        # App startup failed: Empty path provided

finally Block

Code in a finally block always runs — whether an error occurred or not. Use it to release resources like file handles or connections.

fn process_data() raises:
    print("Opening resource")
    try:
        # Simulated work that might fail
        raise Error("Something went wrong mid-process")
    except e:
        print("Handling error:", str(e))
        raise e   # re-raise after logging
    finally:
        print("Closing resource")   # ALWAYS runs

fn main():
    try:
        process_data()
    except:
        print("process_data failed — handled in main")

Output:

Opening resource
Handling error: Something went wrong mid-process
Closing resource
process_data failed — handled in main

When to Use Error Handling

Use raises + try/except for:          Use Optional instead for:
  ✓ File not found                     ✓ "key may or may not exist"
  ✓ Network timeout                    ✓ "result may be absent"
  ✓ Invalid input from users           ✓ Simple missing-value signals
  ✓ Resource exhaustion (out of memory)
  ✓ Protocol violations

Key Takeaways

Add raises to a function that can fail. Use raise Error("message") to signal a problem. Wrap potentially failing calls in try blocks and handle errors in except blocks. The finally block always runs and is the right place to release resources. Re-raise errors with added context to help debugging at higher levels. Callers of a raises function must either handle the error or also declare raises.

Leave a Comment

Your email address will not be published. Required fields are marked *