Error handling patterns
Common patterns for handling errors effectively.
Pattern 1: default value
Return a default value when an error occurs:
fn divide(a:i64 b:i64 -- result:i64)! {
dup 0 == if {
drop2
"division by zero" -1 panic
}
/
}
fn safe_divide(a:i64 b:i64 default:i64 -- result:i64) {
-> default // bind parameter
divide if {
// Success - return result
} else {
default // Return default on error
}
}
fn main() {
10 0 -1 safe_divide print nl // -1
10 2 -1 safe_divide print nl // 5
}
Pattern 2: try multiple options
Try alternatives when first option fails:
fn try_parse(s:str -- value:i64)! {
// ... parsing logic
}
fn parse_with_fallback(primary:str fallback:str -- value:i64) {
-> fallback -> primary // bind parameters
primary try_parse if {
// Primary succeeded
} else {
fallback try_parse if {
// Fallback succeeded
} else {
0 // Both failed, use default
}
}
}
Pattern 3: collect results
Continue despite errors, collect successes:
fn divide(a:i64 b:i64 -- result:i64)! {
dup 0 == if {
drop2
"division by zero" -1 panic
}
/
}
fn try_all( -- success_count:i64) {
0 -> count
10 2 divide if {
drop // Discard result
count inc -> count
}
10 5 divide if {
drop
count inc -> count
}
10 0 divide if {
drop
count inc -> count
}
count
}
fn main() {
try_all print " operations succeeded" print nl // 2
}
Pattern 4: error chain
Propagate errors through multiple operations:
fn step1(x:i64 -- y:i64)! {
x 0 < if {
"negative input" 1 panic
}
x 2 *
}
fn step2(x:i64 -- y:i64)! {
x 100 > if {
"overflow" 2 panic
}
x 10 +
}
fn pipeline(x:i64 -- result:i64)! {
-> x // bind parameter
x step1 if {
step2 if {
// Both succeeded
} else {
"step2 failed" 2 panic
}
} else {
"step1 failed" 1 panic
}
}
Pattern 5: cleanup with defer
Use defer for guaranteed cleanup:
use io
use mem
fn read_file(path:str -- content:str)! {
-> path // bind parameter
path io::ReadOnly io::open if {
-> file // bind file handle
defer {
// Always runs
file io::close
}
4096 -> size
size mem::alloc! -> buf
defer {
// Always runs
buf mem::free
}
file buf size io::read if {
-> bytes_read
buf bytes_read mem::to_string
} else {
"read failed" 1 panic
}
} else {
"open failed" 1 panic
}
}
Defers execute in LIFO order (last in, first out).
Pattern 6: resource wrapper
Wrap resource operations:
struct File {
handle:i64
path:str
}
fn file_open(path:str -- f:ptr)! {
-> path // bind parameter
path io::Read io::open if {
-> handle // bind handle
File {
handle = handle
path = path
}
} else {
"open failed" 1 panic
}
}
fn file_close(f:ptr -- ) {
-> f // bind parameter
f @handle io::close
}
fn with_file(path:str -- ) {
-> path // bind parameter
path file_open if {
-> f // bind file
defer {
f file_close
}
// Use file...
}
}
Pattern 7: validation
Validate before processing:
fn validate_input(x:i64 -- )! {
-> x // bind parameter
x 0 < if {
"negative not allowed" 1 panic
}
x 1000 > if {
"too large" 2 panic
}
}
fn process(x:i64 -- result:i64)! {
-> x // bind parameter
x validate_input if {
x dup * // Safe to process
} else {
"validation failed" 1 panic
}
}
Pattern 8: retry logic
Retry on transient failures:
fn unreliable_op( -- result:i64)! {
// Might fail sometimes
}
fn retry(max_attempts:i64 -- result:i64)! {
-> max_attempts // bind parameter
0 -> attempts
0 -> success
0 -> last_result
success 0 == attempts max_attempts < and while {
unreliable_op if {
-> last_result
1 -> success
}
attempts inc -> attempts
success 0 == attempts max_attempts < and
}
success if {
last_result
} else {
"all attempts failed" 1 panic
}
}
Pattern 9: error context
Add context to errors:
fn parse_config(path:str -- cfg:ptr)! {
-> path // bind parameter
path read_file if {
-> content // bind content
content parse_json if {
// Success
} else {
"invalid JSON in config" 2 panic
}
} else {
"failed to read config file" 1 panic
}
}
Pattern 10: batch processing
Process items, log failures:
fn process_batch(items:ptr -- processed:i64 failed:i64) {
-> items // bind parameter
0 -> processed
0 -> failed
0 items len 1 for i {
items i nth process_item if {
drop
processed inc -> processed
} else {
"Failed: " print items i nth print nl
failed inc -> failed
}
}
processed failed
}
Best practices
- Always handle errors - Don't just
dropwithout logging - Use meaningful messages - Help debugging
- Clean up resources - Use
deferfor guaranteed cleanup - Propagate when appropriate - Use
!to bubble up errors - Fail fast - Validate early, fail early
- Consider recovery - Not every error should crash
What's next?
Learn about Advanced Topics like defer, context, and memory management.