Result: success or error as a value
Result<T, E> is an enum with two variants: Result.Ok(value) for success and
Result.Err(error) for failure. Using Result as a return type lets the
compiler require callers to handle both cases — nothing is silenced.
Mind the punctuation: use dot (
.) to construct —Result.Ok(v)— and double colon (::) to match —Result::Ok(v).
The example below shows how to construct variants, check the state, and safely extract the value:
Constructors, is_ok / is_err, unwrap, unwrap_or and a function that returns Result.
// Feature: Result — success or error as a value
// Syntax: `Result.Ok(v)` / `Result.Err(e)`
// When to use: functions that may fail in an expected way.
use std::Result
// `Result` is an enum with two variants:
// - `Result.Ok(value)` success
// - `Result.Err(error)` failure
let ok = Result.Ok(42)
let err = Result.Err("something went wrong")
// -- Check the variant -----------------------------------------
print(ok.is_ok()) // true
print(ok.is_err()) // false
print(err.is_ok()) // false
print(err.is_err()) // true
// -- Extract the value ----------------------------------------
print(ok.unwrap()) // 42 — panic if Err
print(err.unwrap_or(0)) // 0 — default if Err
print(err.unwrap_err()) // "something went wrong"
// -- Function that returns Result -----------------------------
fn divide(a: int, b: int) -> Result<int, str> {
if b == 0 {
return Result.Err("division by zero")
}
return Result.Ok(a / b)
}
let r1 = divide(10, 2)
let r2 = divide(10, 0)
print(r1.is_ok()) // true
print(r1.unwrap()) // 5
print(r2.is_err()) // true
print(r2.unwrap_err()) // division by zero
// -- Read with unwrap_or — provides a safe default ------------
print(divide(100, 4).unwrap_or(-1)) // 25
print(divide(100, 0).unwrap_or(-1)) // -1
With match you handle both cases exhaustively — the compiler rejects code
that omits one of the arms:
match as a statement and as an expression; bindings with descriptive names.
// Feature: Match on Result
// Syntax: `match r { Result::Ok(v) => ..., Result::Err(e) => ... }`
// When to use: handle success and failure exhaustively.
use std::Result
fn divide(a: int, b: int) -> Result<int, str> {
if b == 0 {
return Result.Err("division by zero")
}
return Result.Ok(a / b)
}
// -- Exhaustive match ------------------------------------------
fn describe(r: Result<int, str>) {
match r {
Result::Ok(v) => print("ok: {v}"),
Result::Err(e) => print("error: {e}"),
}
}
describe(divide(10, 2))
// expected: ok: 5
describe(divide(10, 0))
// expected: error: division by zero
// Note the match syntax: `Result::Ok` (with `::`), not `.`.
// To CONSTRUCT, use `Result.Ok(v)` (with `.`).
// -- Match as expression ---------------------------------------
fn format(r: Result<int, str>) -> str {
return match r {
Result::Ok(v) => "value = {v}",
Result::Err(_) => "(failed)",
}
}
print(format(divide(10, 2))) // value = 5
print(format(divide(10, 0))) // (failed)
// -- Bindings with different names -----------------------------
let r = divide(20, 4)
match r {
Result::Ok(number) => print("computed {number}"),
Result::Err(reason) => print("failed: {reason}"),
}
// expected: computed 5
Challenge
Add a third call describe(divide(10, 10)) and verify that the Ok arm prints
ok: 1. Then replace unwrap() with unwrap_or(99) on r2 — what is the
output?
See also