Error handling
Built-in operations for error handling.
Overview
| Instruction | Signature | Description |
|---|---|---|
panic |
(msg code --) |
Signal a panic (error) |
err |
(-- msg code) |
Get error code from last fallible call |
Ok |
(-- 1) |
Builtin constant for success |
Err |
(-- 0) |
Builtin constant for generic error |
Result constants
Quadrate provides two builtin constants for result handling:
Ok= 1 (success)Err= 0 (generic error)
Each module defines specific error codes starting at 2. For example:
- io::ErrNotFound = 2
- io::ErrPermission = 3
- os::ErrNotFound = 2
- str::ErrOutOfBounds = 2
Signaling panics
panic
Signals a panic with a message and code. Used in fallible functions.
Signature: (msg code --)
"invalid input" 1 panic
Fallible functions
Functions that can fail are marked with ! after the signature:
fn divide(a:i64 b:i64 -- result:i64)! {
dup 0 == if {
drop2
"division by zero" Err panic
}
/
}
Handling errors
With switch (recommended)
Use switch to match on specific error codes:
"/tmp/config.txt" io::Read io::open switch {
Ok {
-> file
"File opened!" print nl
file io::close
}
io::ErrNotFound {
drop
"File not found" print nl
}
io::ErrPermission {
drop
"Permission denied" print nl
}
_ {
drop
"Unknown error" print nl
}
}
With if-else
Handle success/failure without matching specific errors:
10 2 divide if {
// Success: result on stack
print nl
} else {
"Division failed" print nl
}
Calling fallible functions
With switch (matching error codes)
Handle specific errors:
fn read_config( -- data:str)! {
"/etc/app/config.txt" io::Read io::open switch {
Ok {
-> file
// Read file contents...
file io::close
}
io::ErrNotFound {
drop
"config not found" io::ErrNotFound panic
}
_ {
-> code
"config read failed" code panic
}
}
}
With if-else
Handle errors without matching specific codes:
fn compute(x:i64 -- result:i64)! {
-> x
x 2 divide if {
// Success
} else {
drop
"compute failed" Err panic
}
}
With ! (abort on error)
Abort the program if an error occurs:
fn compute(x:i64 -- result:i64)! {
-> x
x 2 divide! // Aborts program if divide fails
10 +
}
Warning: Using ! terminates the entire program if the function fails. Only use when crashing is acceptable (e.g., during initialization or in scripts).
Propagating errors
To propagate errors to the caller, handle with if/else and call panic:
fn compute(x:i64 -- result:i64)! {
-> x
x 2 divide if {
10 +
} else {
"compute failed" 1 panic
}
}
Error codes
By convention:
Ok(1) = SuccessErr(0) = Generic error- Module-specific errors start at 2
Each module defines its own error codes:
// io module
io::ErrNotFound // 2 - File not found
io::ErrPermission // 3 - Permission denied
io::ErrInvalidHandle // 4 - Invalid file handle
// os module
os::ErrNotFound // 2 - No such file or directory
os::ErrPermission // 3 - Permission denied
os::ErrExists // 4 - File already exists
// str module
str::ErrOutOfBounds // 2 - Index out of bounds
str::ErrAlloc // 3 - Memory allocation failed
Best practices
- Use switch for specific errors - Match module error codes
- Use if-else for simple cases - When you don't need specific codes
- Use meaningful messages - Help with debugging
- Clean up with defer - Resources released on error
fn process(path:str -- result:i64)! {
-> path
path io::Read io::open switch {
Ok {
-> file
defer {
file io::close
}
// Process file...
42
}
io::ErrNotFound {
drop
"file not found" io::ErrNotFound panic
}
io::ErrPermission {
drop
"permission denied" io::ErrPermission panic
}
_ {
-> code
"open failed" code panic
}
}
}