Use Gleam’s Result and try for recoverable errors
Use Gleam’s Result type and try expression to handle expected failures without exceptions or nested case blocks.
13 Jul 2026, 04:44 UTC

The problem: a missing config file shouldn’t crash your app
You are writing a Gleam service that reads a JSON configuration file. The file might be missing, or it might contain malformed data. In languages with exceptions, you might wrap the call in a try/catch block or return null and hope callers check. Gleam does not encourage exceptions for expected failures. Instead, it models recoverable errors with the Result type, and the compiler makes sure you deal with both outcomes.
Takeaway: use Result(value, error) for failures you can recover from, and use try to unwrap values without writing nested case blocks for every call.
What Result means in Gleam
Result is a parameterized type: Result(value, error). A value is either Ok(value) or Error(error). Because Gleam’s pattern matching is exhaustive, a case on a Result must handle both branches, or the compiler rejects the code. This turns forgotten error handling into a compile-time error instead of a runtime surprise.
On the Erlang target, Result compiles to tuples. On the JavaScript target, it compiles to tagged objects. The type safety survives compilation, so callers on either target see the same Ok/Error structure.
Flattening logic with try
The try expression unwraps a Result inside a let binding. If the result is Ok, execution continues with the inner value. If it is Error, the function returns that error immediately. This removes the pyramid of case expressions that appears when several fallible calls depend on each other.
Without try
pub fn load() -> Result(Config, ConfigError) {
case read_config("config.json") {
Ok(config) -> Ok(config)
Error(error) -> Error(error)
}
}
With try
pub fn load() -> Result(Config, ConfigError) {
let config = try read_config("config.json")
Ok(config)
}
Worked example: load configuration safely
Create a new Gleam project in your terminal:
gleam new config_loader
cd config_loader
Run these commands in the project directory. No special permissions are required. Replace config_loader with your project name. Add a module that defines the error type, the configuration type, and a fallible constructor:
pub type ConfigError {
MissingFile
InvalidJson(String)
}
pub type Config {
Config(host: String, port: Int)
}
pub fn read_config(path: String) -> Result(Config, ConfigError) {
// Implementation depends on your file and JSON libraries.
// The signature declares that failure is expected and recoverable.
todo
}
pub fn load() -> Result(Config, ConfigError) {
let config = try read_config("config.json")
Ok(config)
}
Run gleam build to compile. The compiler will reject the todo placeholder until you supply a real implementation, but it accepts the Result signature and the try binding. To verify that unhandled errors are rejected, deliberately call read_config without try or without a case and run gleam build again; the compiler should report a type error. Run gleam test to execute any tests you add.
Risk: changing a function’s return type from a plain value to Result ripples through every caller. Each caller must adopt try or explicit case handling.
Trade-offs and limits
- Use
Resultfor recoverable errors only. Unrecoverable conditions, such as a corrupted internal state, still callpanic, which terminates the process. - Erlang functions that raise exceptions must be wrapped manually before callers can work with
Resultinstead of exceptions. - Do not wrap every fallible call in
try. Complex recovery logic can read more clearly with an explicitcaseexpression that inspects the error value.
Check your work
- Write a function that returns
Resultand call it withtry. - Run
gleam buildand confirm the compiler accepts both branches. - Remove the error branch from a
caseand confirm the compiler rejects the code. - Inspect the generated code under
build/dev/erlangorbuild/dev/javascriptto confirmResultappears as tuples or tagged objects, depending on your target.
Gleam’s Result and try make expected failures explicit at compile time. Start with one fallible function, convert its callers to try, and let the compiler catch the rest.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.