Error Handling
Operations that can fail return Result values. Hot makes working with Results ergonomic through automatic wrapping, automatic unwrapping, and lazy argument evaluation.
The Result Type
A Result represents either success (Ok) or failure (Err):
Result enum {
Ok(Any),
Err(Any)
}
Return values are automatically wrapped in Result.Ok, so you typically only need err() to signal failures:
safe-divide fn (a: Int, b: Int): Int {
if(eq(b, 0), err("Division by zero"), div(a, b)) // div result auto-wrapped in Ok
}
Many core functions return Results implicitly—HTTP calls, file operations, parsing, and other fallible operations.
Automatic Unwrapping
When you use a Result value as a function argument or interpolate it in a template, Hot automatically handles it:
- Ok Result: Unwraps to the inner value
- Err Result: Immediately halts execution
// HTTP functions return Results automatically
response http-get("https://api.example.com/user/1")
name response.body.name // Auto-unwraps the Result
greeting `Hello, ${name}!`
If the HTTP call failed, execution halts at the point of use—you don't need explicit error handling on every line. Errors automatically propagate up.
Note: Function return type annotations specify the expected success type, not
Result. The Result wrapper is implicit for any operation that can fail.
Checking Results Explicitly
Use is-ok and is-err to inspect Results without triggering automatic unwrapping:
result-check safe-divide(10, 0)
message-check if(is-ok(result-check),
`Result: ${result-check}`,
"Cannot divide by zero") // This branch runs
safe-divide(10, 0) → Result.Err("Division by zero")
message-check → "Cannot divide by zero"
These functions receive the Result as a lazy argument, which prevents automatic unwrapping during the check.
You can also use match for pattern matching on Result variants:
result safe-divide(20, 4)
message match result {
Result.Ok => `Success: ${result}`
Result.Err => `Error: ${result}`
}
Lazy Arguments and Result Checking
When a function argument is marked lazy, it isn't evaluated until explicitly requested. This is how Hot enables safe Result inspection.
// The if function uses lazy arguments
if fn cond (pred: Any, lazy then: Any, lazy else: Any): Any {
pred => { do then }
=> { do else }
}
For lazy arguments, Result checking is suppressed during evaluation. This means:
- You can pass expressions that produce Results
- The Result won't auto-unwrap (or fail) until
doevaluates it - Functions like
is-okandis-errcan safely receive and inspect Results
// Safe division that returns a Result
safe-divide fn (a, b) {
if(eq(b, 0), err("Division by zero"), ok(div(a, b)))
}
// is-ok receives the Result without triggering auto-unwrap
result safe-divide(10, 0)
if(is-ok(result),
`Result: ${result}`,
"Cannot divide by zero") // This branch runs
Short-Circuit Evaluation
Lazy arguments also enable short-circuit evaluation for and and or:
// Short-circuit prevents errors in unevaluated branches
x-val 0
short-result if(eq(x-val, 0), "zero", div(10, x-val)) // div never called, no error
Error Handling Patterns
Pattern 1: Let It Fail
For many cases, just use Results directly. Errors propagate automatically:
main fn () {
user fetch-user(id) // Auto-unwraps or fails
posts fetch-posts(user.id) // Auto-unwraps or fails
render-page(user, posts) // Only runs if both succeeded
}
Pattern 2: Check and Handle
When you need to handle errors explicitly:
result fetch-user(id)
if(is-ok(result),
render-profile(result),
render-error-page(result))
Or use match for cleaner syntax:
result fetch-user(id)
match result {
Result.Ok => render-profile(result)
Result.Err => render-error-page(result)
}
Pattern 3: Default Values
Provide fallbacks for failures:
// Provide fallbacks for failures
config-result safe-divide(10, 0)
config if(is-ok(config-result), config-result, 99) // Fallback to 99
Pattern 4: Fail with Context
Use fail to halt execution with a custom error:
validate fn (data) {
if(is-empty(data.email),
fail("Email is required", {field: "email"}),
data)
}
Summary
- Use
Result.Ok(value)orok(value)andResult.Err(message)orerr(message)to create Results - Results auto-unwrap when passed to functions or used in templates
- Err Results automatically fail at point of use—no explicit handling needed
- Use
is-ok(result)andis-err(result)to check without triggering auto-unwrap - Use
matchfor pattern matching onResult.OkandResult.Errvariants - Dot access on Results automatically accesses fields within the payload:
result.name - Lazy arguments suppress Result checking, enabling safe inspection and short-circuit evaluation
- Most code can ignore error handling; errors propagate automatically