Error Handling
Toka provides type-safe error handling through the Result and Option types. This model, rooted in Algebraic Data Types (ADTs), traces its origins back to the ML language family (such as Standard ML and OCaml) and Haskell. Rust later popularized this approach within modern systems programming. Toka adopts this model, combining it with language-level syntax—such as the ! operator for error propagation—to deliver a concise developer experience.
The Option Type
Use Option for values that may or may not exist:
import core/option::{Option}
import std/io::println
fn find_user(id: i32) -> Option<string> {
if id == 1 {
return Option<string>::Some(string::from("Alice"))
}
return Option<string>::None
}
fn main() -> i32 {
auto result = find_user(1)
match result {
auto Option<string>::Some(&name) => println("Found: {}", name)
auto Option<string>::None => println("User not found")
}
return 0
}
The Result Type
Use Result for operations that can succeed or fail:
import core/result::{Result}
fn divide(a: f64, b: f64) -> Result<f64, string> {
if b == 0.0 {
return Result<f64, string>::Err(string::from("Division by zero"))
}
return Result<f64, string>::Ok(a / b)
}
The Result type is generic over two parameters:
T— The successful value typeE— The error type
Pattern Matching on Results
import core/result::{Result}
import std/io::println
fn divide(a: f64, b: f64) -> Result<f64, string> {
if b == 0.0 {
return Result<f64, string>::Err(string::from("Division by zero"))
}
return Result<f64, string>::Ok(a / b)
}
fn main() -> i32 {
match divide(10.0, 2.0) {
auto Result<f64, string>::Ok(value) => println("Result: {}", value)
auto Result<f64, string>::Err(&msg) => println("Error: {}", msg)
}
return 0
}
Short-Circuiting with !
Use the ! operator to propagate errors automatically:
import core/result::{Result}
fn read_file(path: string) -> Result<string, string> {
return Result<string, string>::Ok(path)
}
fn parse(content: string) -> Result<string, string> {
return Result<string, string>::Ok(content)
}
fn format_result(parsed: string) -> string {
return parsed
}
fn process_file(path: string) -> Result<string, string> {
auto content = read_file(path)! // Returns early on error
auto parsed = parse(content)! // Same here
return Result<string, string>::Ok(format_result(parsed))
}
This is much cleaner than nested match statements.
The Default Case with or
Provide a default value in case of error:
auto value = parse_int("42").unwrap_or(0) // Uses 0 if parsing fails
Custom Error Types
You can define structured errors:
import core/result::{Result}
pub shape Config()
pub shape ParseError(
line: i32,
col: i32,
message: string
)
fn parse_config(text: string) -> Result<Config, ParseError> {
if text.len() == 0 {
return Result<Config, ParseError>::Err(ParseError(line = 0, col = 0, message = string::from("Empty input")))
}
return Result<Config, ParseError>::Ok(Config())
}