Skip to content

Error Handling with ard/result

The ard/result module provides functions for working with results that can be either a success value or an error. The Result type represents the outcome of an operation that may fail.

The result module provides:

  • Success values with Result::ok()
  • Error values with Result::err()
  • Type safety for error handling without exceptions
  • Result syntax sugar with the ! operator (e.g., Int!Str is equivalent to Result<Int, Str>)
use ard/result
use ard/io
fn divide(a: Int, b: Int) Int!Str {
if b == 0 {
Result::err("Cannot divide by zero")
} else {
Result::ok(a / b)
}
}
fn main() {
match divide(10, 2) {
ok(result) => io::print(result.to_str()),
err(error) => io::print("Error: {error}")
}
}

Create a successful Result containing the given value.

use ard/result
let result: Int!Str = Result::ok(42)

Create a failed Result containing the given error.

use ard/result
let result: Int!Str = Result::err("Something went wrong")

All Result types have the following methods:

Check if the Result is a success.

use ard/result
let result: Int!Str = Result::ok(42)
if result.is_ok() {
// success
}

Check if the Result is an error.

use ard/result
let result: Int!Str = Result::err("failed")
if result.is_err() {
// error
}

Get the success value, or return a default if the Result is an error.

use ard/result
let result: Int!Str = Result::err("failed")
let value = result.or(0) // 0

Get the success value, or panic with a message if the Result is an error.

use ard/result
let result: Int!Str = Result::ok(42)
let value = result.expect("Expected success") // 42

Use match expressions to handle both success and error cases:

use ard/result
use ard/io
fn main() {
let result: Int!Str = Result::ok(42)
match result {
ok(value) => io::print("Success: {value.to_str()}"),
err(error) => io::print("Error: {error}")
}
}

Results can be written in two equivalent ways:

// Verbose form
fn divide_verbose(a: Int, b: Int) Result<Int, Str> { ... }
// Concise form using ! syntax
fn divide_concise(a: Int, b: Int) Int!Str { ... }

Both forms are identical. The ! syntax is more readable for simple result types.

Use the try keyword to propagate errors to the caller:

use ard/result
fn divide(a: Int, b: Int) Int!Str {
if b == 0 {
Result::err("Cannot divide by zero")
} else {
Result::ok(a / b)
}
}
fn add_and_divide(a: Int, b: Int, divisor: Int) Int!Str {
let sum = a + b
let result = try divide(sum, divisor)
Result::ok(result + 10)
}
fn main() {
match add_and_divide(5, 5, 2) {
ok(value) => io::print(value.to_str()),
err(error) => io::print(error)
}
}

The try keyword unwraps the Result. If it’s an error, the entire function returns that error immediately.

use ard/result
use ard/io
fn main() {
let result: Int!Str = Result::ok(42)
match result {
ok(value) => io::print(value.to_str()),
err(_) => io::print("Operation failed")
}
}
use ard/result
fn parse_age(age_str: Str) Int!Str {
match Int::from_str(age_str) {
age => {
if age < 0 {
Result::err("Age cannot be negative")
} else {
Result::ok(age)
}
},
_ => Result::err("Invalid integer")
}
}
fn validate_age(age: Int) Void!Str {
if age < 18 {
Result::err("Must be 18 or older")
} else {
Result::ok(Void)
}
}
fn process_user(age_str: Str) Void!Str {
let age = try parse_age(age_str)
try validate_age(age)
Result::ok(Void)
}
use ard/result
fn main() {
let result: Int!Str = Result::err("parsing failed")
let value = result.or(0)
// value is 0
}
use ard/result
fn safe_divide(a: Int, b: Int) Int!Str {
if b == 0 {
Result::err("Division by zero")
} else {
Result::ok(a / b)
}
}
fn main() {
let r1 = safe_divide(10, 2) // Success
let r2 = safe_divide(10, 0) // Error
}