Using Gleam’s Result Type for Explicit Error Handling
Gleam’s Result type turns potential exceptions into explicit values, letting the compiler enforce error handling and giving you a composable, pattern‑matchable way to manage failure.
01 Mar 2026, 10:00 UTC

The problem: hidden failures in everyday code
When a function can fail, many languages let you ignore the failure until it surfaces as an exception at runtime. In a growing codebase this leads to surprises: a missing file, a network timeout, or malformed input can crash a process that seemed fine during testing.
Thesis: Gleam’s Result type makes failure a first‑class value
Gleam defines Result(a, e) as a simple discriminated union: Ok(a) for success and Error(e) for failure. Because the compiler treats Result like any other type, pattern matching on it is exhaustive – you must handle both branches or the code won’t compile. This turns potential runtime exceptions into compile‑time guarantees.
Understanding the building blocks
The standard library returns Result for operations that can fail. For example, gleam/io.read_line has the signature:
pub fn read_line() -> Result(String, Nil)
Here String is the successful value and Nil (an empty tuple) represents the absence of additional error information.
To combine multiple fallible steps Gleam provides helper functions:
Result.maptransforms the success value while leaving an error untouched.Result.map2runs two independentResultcomputations in parallel, returning an error if either fails.Result.try(alias forand_then) chains a function that itself returns aResult.
These helpers let you write pipelines that resemble monadic binding in other languages, but with explicit error values.
Worked example: reading lines until EOF
We’ll build a tiny program that reads lines from standard input and counts them, treating an end‑of‑file signal as a normal Error(Nil) case.
- Create a new project:
# Run in a terminal, no special permissions needed
gleam new line_counter
cd line_counter
- Replace the contents of
src/main.gleamwith the following:
import gleam/io
import gleam/result
import gleam/int
pub fn main() -> Result(Int, Nil) {
let mut count = 0
loop {
case io.read_line() {
Ok(_) -> {
count = int.add(count, 1)
continue
}
Error(Nil) -> break // EOF reached
}
}
Ok(count)
}
- Compile and run:
# Compile to Erlang beam files (default target)
gleam build
# Execute the program
gleam run
When you type text and press Enter, each line increments the counter. Pressing Ctrl+D (EOF) sends an Error(Nil) from read_line, breaking the loop and returning the final count wrapped in Ok. The program then prints the integer to stdout.
If you attempt to omit the Error(Nil) branch, the compiler emits:
error: All constructors must be matched when pattern matching against a ResultThus the compiler forces you to consider the failure case.
Trade‑off: verbosity vs. safety
Using
Resulteverywhere can make code feel verbose, especially when many fallible steps are sequential. Forgetting to useResult.map2for independent validations leads to nestedcasestatements. The mitigation is to adopt the helper functions early and to define small wrapper functions that encapsulate common patterns, keeping the main flow readable.Another consideration arises when Gleam code interacts with existing Erlang/OTP libraries that use error signals (exits) rather than tuples. In those cases you must explicitly convert between
Resultand the Erlang{ok, Value}/{error, Reason}representation, for example withgleam/erlang.result_to_tuple. Overlooking this step can cause unhandled exits, so a thin conversion layer at the boundary is recommended.Actionable closing
Start by replacing one function that currently throws an exception with a
Resultreturn type. Use the compiler’s exhaustiveness check to verify you’ve handled bothOkandError. Then gradually propagateResultthrough call sites, applyingmap,map2, andtryto keep the code flat. Finally, test the boundary with Erlang by building to the-t erlangtarget and inspecting the generated beam files to confirm the{ok, …}/{error, …}mapping.By treating errors as values you gain compile‑time safety, clearer failure paths, and smoother interoperability with the BEAM ecosystem—all without sacrificing the functional style that makes Gleam pleasant to write.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.