Results, Options, and Error Handling
- Gleam has no exceptions: errors are ordinary values returned by functions.
- The
Resulttype carries either a success value (Ok) or a failure reason (Error).- Functions that return
Resultforce callers to acknowledge failure at compile time.
- Functions that return
- The
Optiontype represents a value that may or may not be present.
Errors as Values
- Python raises an exception when something goes wrong
and expects callers to catch it with
try/except - But nothing in the type signature tells you whether a function might raise or what kind of exception to expect
- In Gleam, a function that might fail returns a Result, and the type signature makes that explicit
Result(a, e)is a type with two variants:Ok(value)carries a successful resultError(reason)carries a failure description
- The type of
reasonis up to you- While
Stringis convenient for examples, Gleam developers usually prefer an explicit custom error type so that the compiler can check that every error case is handled - A custom error type is just another
typedefinition:
- While
- A caller cannot use the value inside
Okwithout handling theErrorcase- The compiler enforces this
Parsing and Dividing Safely
fn parse_int(s: String) -> Result(Int, String) {
case int.parse(s) {
Ok(n) -> Ok(n)
Error(_) -> Error("not an integer: " <> s)
}
}
int.parse(s)from the standard library returnsResult(Int, Nil)- It returns
Ok(n)if the string is a valid integer - It returns
Error(Nil)if not; the error carries no information
- It returns
- The function wraps the result to provide a
Stringerror message instead - Compare to Python's
int(s)- Raises
ValueErroron bad input - But no way to know this from the function's type
- Raises
fn safe_divide(a: Int, b: Int) -> Result(Int, String) {
case b {
0 -> Error("division by zero")
_ -> Ok(a / b)
}
}
Ok(42)
Error("not an integer: not a number")
Ok(5)
Error("division by zero")
safe_dividereturnsError("division by zero")instead of crashing with aZeroDivisionError- The
case b { 0 -> … _ -> … }pattern is idiomatic for this kind of guard - Any caller that uses
safe_dividemust handle bothOkandError
Transforming Results
- Often want to apply a function to the value inside
Okwithout unwrapping it manually result.map(r, f)appliesfto the value insideOkand rewraps itresult.try(r, f)is similar butfreturns aResult, so the double-wrappingOk(Ok(v))is avoided- Most Gleam developers use
result.trymore often thanresult.map
- Most Gleam developers use
result.map_errortransforms the error value inErrorresult.unwrapextracts the value fromOkor returns a default
io.println(string.inspect(ok_int(42) |> result.map(fn(x) { x + 1 })))
io.println(string.inspect(error_str("oops") |> result.map(fn(x) { x + 1 })))
Ok(43)
Error("oops")
result.map(r, f)appliesfto the value insideOk(v)and returnsOk(f(v))- If
risError(e),result.mapreturnsError(e)unchanged
- If
- This avoids a
caseexpression when you only care about the success path
The Option Type
Option(a)is a type with two variantsSome(value)wraps a present valueNonerepresents absence
Optionis used mostly for optional fields in records and optional function parameters- For return types that might fail,
Resultis usually preferred Resultcan carry an error message;Optioncannot
- For return types that might fail,
fn option_inspect(opt: option.Option(Int)) -> String {
case opt {
option.Some(n) -> "got " <> int.to_string(n)
option.None -> "nothing"
}
}
Option(Int)is exactly like Python'sOptional[int]case opt { Some(n) -> ... None -> ... }is the standard way to unwrap it- The compiler will not let you use
noutside theSomearm
fn first_positive(nums: List(Int)) -> option.Option(Int) {
case nums {
[] -> option.None
[x, ..] if x > 0 -> option.Some(x)
[_, ..rest] -> first_positive(rest)
}
}
"got 42"
"nothing"
Some(99)
None
Some(3)
None
[x, .._rest] if x > 0combines a list pattern with a guard- If the head
xis positive, returnSome(x)immediately - Otherwise, recurse on the tail
- If the head
- An empty list or an all-negative list reaches
[] -> None - This is equivalent to Python's
next((x for x in items if x > 0), None)but the return type makes the "might be absent" case explicit
Guidelines for Handling Errors
- Return
Resultwhenever a function might fail for an externally visible reason (e.g., bad input, missing file, or network error) - Return
Optionwhen absence is the only failure mode - Use
let assert Ok(x) = ...only when failure truly cannot happen (like initialising a known-good constant at startup)- It panics on
Error - Unlike Python's
assert, which is a statement that checks a condition, Gleam'slet assertmatches a pattern and creates a variable
- It panics on
let assert Ok(n) = int.parse("42")
io.println("parsed: " <> int.to_string(n))
parsed: 42
- Use
result.tryandresult.mapfor short transformations - Prefer custom error types over
Stringfor the error type in non-trivial programs
A Note on Assertions
- Gleam also allows bare assertions like
assert left == right- Panics if the condition is not true
- Used less often than the
let assertform- Most functions return
Result, and if it'sOk(val), we usually want the value
- Most functions return
- Discussed more in the section on modules
Check Understanding
When should you use Option instead of Result?
-
Use
Optionwhen there is no useful error information to convey: a dictionary lookup either finds the key or it does not, and there is no meaningful "reason it failed". -
Use
Resultwhen you want to distinguish failure modes: parsing an integer from a string can fail because the string is empty, because it contains non-digit characters, or because it overflows. AResultlets you report which failure occurred.
Exercises
Safe division (5 minutes)
Write safe_divide(a: Int, b: Int) -> Result(Int, String)
that returns Error("division by zero") when b is 0
and Ok(a / b) otherwise.
Mapping over results (5 minutes)
Write a function that takes a List(String) and returns a List(Int)
containing only the elements that parse successfully as integers.
Use int.parse, list.filter_map, or a combination of list.map and list.filter.
First matching element (10 minutes)
Write find(items: List(a), pred: fn(a) -> Bool) -> Option(a)
that returns Some(x) for the first element where pred(x) is True,
or None if no element matches.
Write it using recursion with a case expression.