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.
