Error Handling
Oxide provides Swift-style error handling with throws, try, and do-catch blocks.
Throwing Functions
Mark functions that can fail with throws:
fn parseNumber(s: String) throws: Int {
if s.isEmpty() {
throw "Empty string"
}
s.parse().unwrap()
}
fn readConfig(path: String) throws: Config {
let content = try readFile(path)
try parseConfig(content)
}
Transpiles to:
fn parse_number(s: &str) -> Result<isize, Box<dyn std::error::Error>> {
if s.is_empty() {
return Err("Empty string".into());
}
Ok(s.parse().unwrap())
}
fn read_config(path: &str) -> Result<Config, Box<dyn std::error::Error>> {
let content = read_file(path)?;
parse_config(&content)
}
The throw Statement
Throw errors from throwing functions:
fn validateAge(age: Int) throws {
if age < 0 {
throw "Age cannot be negative"
}
if age > 150 {
throw "Age seems unrealistic"
}
}
fn divide(a: Int, b: Int) throws: Int {
if b == 0 {
throw "Division by zero"
}
a / b
}
Try Variants
try - Propagate Errors
Use in throwing functions or do-catch blocks:
fn processFile(path: String) throws: Data {
let content = try readFile(path) // Propagates error
let parsed = try parseData(content) // Propagates error
parsed
}
try? - Convert to Optional
Returns null on error:
let content = try? readFile("config.txt") // String?
if let data = try? parseJson(input) {
process(data)
} else {
useDefault()
}
// With null coalesce
let config = try? loadConfig() ?? Config.default()
Transpiles to:
let content = read_file("config.txt").ok();
if let Some(data) = parse_json(&input).ok() {
process(data);
} else {
use_default();
}
try! - Force Unwrap
Panics on error (use sparingly):
let content = try! readFile("required.txt") // Panics if error
Transpiles to:
let content = read_file("required.txt").unwrap();
warning
Only use try! when you're certain the operation will succeed, such as in tests or when failure indicates a bug.
Do-Catch Blocks
Handle errors with Swift-style do-catch:
do {
let config = try loadConfig()
let data = try fetchData(config.url)
process(data)
} catch {
println!("Error: $error")
}
Transpiles to:
match (|| -> Result<_, Box<dyn std::error::Error>> {
let config = load_config()?;
let data = fetch_data(&config.url)?;
process(data);
Ok(())
})() {
Ok(_) => {},
Err(error) => {
println!("Error: {}", error);
}
}
Catching Specific Error Types
do {
let data = try fetchData(url)
} catch NetworkError {
println!("Network error: $error.code")
} catch ParseError {
println!("Parse error: $error.message")
} catch {
println!("Unknown error: $error")
}
Transpiles to:
match fetch_data(&url) {
Ok(data) => { /* ... */ },
Err(error) => {
if let Some(e) = error.downcast_ref::<NetworkError>() {
println!("Network error: {}", e.code);
} else if let Some(e) = error.downcast_ref::<ParseError>() {
println!("Parse error: {}", e.message);
} else {
println!("Unknown error: {}", error);
}
}
}
The Implicit error Variable
In catch blocks, error is automatically available:
do {
try riskyOperation()
} catch {
// 'error' is available here
log(error)
return defaultValue
}
Combining with Optionals
Converting Result to Optional
// try? converts Result<T, E> to T?
let value: Int? = try? parseNumber(input)
// Chain with safe operators
let doubled = (try? parseNumber(input))?.mapped { it * 2 } ?? 0
Error or Null
Handle both missing values and errors:
fn getUser(id: Int) throws: User? {
if id <= 0 {
throw "Invalid ID"
}
database.findUser(id) // Returns User?
}
// Handle all cases
do {
if let user = try getUser(id) {
welcome(user)
} else {
println!("User not found")
}
} catch {
println!("Error: $error")
}
Custom Error Types
Define your own error types:
@[derive(Debug)]
enum ApiError {
case network(message: String)
case parse(line: Int, column: Int)
case notFound
}
fn fetchApi(url: String) throws: Response {
let response = try? http::get(url)
guard let r = response else {
throw ApiError.network(message = "Failed to connect")
}
if r.status == 404 {
throw ApiError.notFound
}
r
}
Result Type
For more explicit error handling, use Result directly:
fn divide(a: Int, b: Int): Result<Int, String> {
if b == 0 {
return Err("Division by zero")
}
Ok(a / b)
}
// Pattern match on result
match divide(10, 2) {
Ok(value) -> println!("Result: $value")
Err(e) -> println!("Error: $e")
}
// Use Result extension methods
let result = divide(10, 2)
.mapped { it * 2 }
.unwrapOr(0)
See Standard Library - Result for Result extension methods.
Best Practices
- Use
throwsfor recoverable errors - Network failures, parse errors, etc. - Use
try?with defaults -try? loadConfig() ?? defaultConfig - Reserve
try!for guarantees - When failure is a bug - Catch specific errors first - More specific catches before general
// Good: Specific error handling
do {
let data = try fetchData()
} catch NetworkError {
retryWithBackoff()
} catch {
logError(error)
}
// Good: Optional with default
let config = try? loadConfig() ?? Config.default()
// Avoid: Ignoring errors completely
// let _ = try? dangerousOperation()
See Also
- Null Safety - Optional type handling
- Standard Library - Result - Result extension methods
- Control Flow - Guard statements