Error Handling
Ard makes failure and absence explicit in the type system:
T!Eis aResult: either an okTvalue or an errorEvalue.T?is aMaybe: either a presentTvalue or no value.tryunwraps aResultorMaybeon the success path and returns early on the failure/empty path.
Use ard/result and Maybe for detailed constructor and method reference.
Result Types
Section titled “Result Types”Result types represent operations that can succeed or fail. They are written as ValueType!ErrorType:
fn divide(a: Int, b: Int) Int!Str { match b == 0 { true => Result::err("division by zero") false => Result::ok(a / b) }}Create results with:
Result::ok(value)for successResult::err(error)for failure
Handle a result with match when both branches need local behavior:
use go:fmt
let result = divide(10, 2)match result { ok(value) => fmt::Println("Result: {value}") err(message) => fmt::Println("Error: {message}")}Or use methods such as .or(...), .expect(...), .map(...), and .and_then(...) for concise transformations. See ard/result for the full API.
Maybe Types
Section titled “Maybe Types”Maybe types represent values that may be absent. They are written with ? or as Maybe<T>:
struct User { name: Str, id: Int,}
fn find_user(id: Int) User? { match id == 42 { true => Maybe::new(User{name: "Alice", id: 42}) false => Maybe::new() }}Create Maybe values with:
Maybe::new(value)for a present valueMaybe::new<T>()or context-inferredMaybe::new()for an absent value
Handle a Maybe with match when you need both branches:
use go:fmt
match find_user(42) { user => fmt::Println("Found user: {user.name}") _ => fmt::Println("User not found")}Or use methods such as .or(...), .expect(...), .map(...), and .and_then(...) for concise transformations. See Maybe for the full API.
The try Keyword
Section titled “The try Keyword”Use try to unwrap a Result or Maybe and return early when it is an error or empty.
With Result
Section titled “With Result”When trying a Result, the enclosing function must return a compatible Result unless you provide a catch block.
fn calculate() Int!Str { let x = try divide(10, 2) let y = try divide(x, 3) Result::ok(y + 1)}If either call to divide returns err, calculate returns that error immediately.
With Maybe
Section titled “With Maybe”When trying a Maybe, the enclosing function must return a Maybe unless you provide a catch block.
fn get_user_name(id: Int) Str? { let user = try find_user(id) Maybe::new(user.name)}If find_user returns none, get_user_name returns none immediately.
Catch Blocks
Section titled “Catch Blocks”A catch block handles the failure or empty case and returns early from the enclosing function with the catch block’s value.
fn parse_number(raw: Str) Int!Str { match raw == "abc" { true => Result::err("not a number") false => Result::ok(42) }}
fn display_number(raw: Str) Str { let num = try parse_number(raw) -> err { "Failed to parse: {err}" } "Number is: {num}"}For Maybe, use _ because the absent case carries no payload:
fn user_display(id: Int) Str { let user = try find_user(id) -> _ { "Unknown user" } "User: {user.name}"}For simple Result error transformations, the catch handler can also be a function reference:
fn format_error(msg: Str) Str { "Error: {msg}"}
fn process() Str { let value = try parse_number("abc") -> format_error "Value: {value}"}Nested Maybes
Section titled “Nested Maybes”try can be applied to a chained property access. If any Maybe in the chain is empty, the catch block runs:
struct Profile { name: Str?,}
struct UserWithProfile { id: Int, profile: Profile?,}
fn profile_name(user: UserWithProfile?) Str { let name = try user.profile.name -> _ { "missing profile name" } name}