Gleam Use Expression
The use expression flattens deeply nested callbacks into readable, sequential code. It is one of Gleam's most distinctive features — making code that would otherwise require many levels of nesting read like a straight list of steps.
The Problem use Solves
Many operations in Gleam use callbacks — functions passed as arguments that continue the computation. When you chain several of these, the code nests deeper with every step:
Without use — deep nesting:
──────────────────────────────────────────────────
result.then(get_user(id), fn(user) {
result.then(get_orders(user), fn(orders) {
result.then(calculate_total(orders), fn(total) {
Ok(format_invoice(user, total))
})
})
})
With use — flat and sequential:
──────────────────────────────────────────────────
use user <- result.then(get_user(id))
use orders <- result.then(get_orders(user))
use total <- result.then(calculate_total(orders))
Ok(format_invoice(user, total))
Both versions do exactly the same thing. The use version reads like a recipe: get the user, get their orders, calculate the total, format the invoice.
How use Works
The use expression is syntactic sugar. It rewrites nested callbacks into a flat sequence at compile time.
use pattern <- function(args)
rest_of_code
Is exactly the same as:
──────────────────────────────────────────────────
function(args, fn(pattern) {
rest_of_code
})
use Transformation Diagram
──────────────────────────────────────────────────
use x <- some_function(arg)
do_something(x)
↕ compiler rewrites to:
some_function(arg, fn(x) {
do_something(x)
})
use with Result Chaining
The most common use of use is flattening result.then chains:
import gleam/result
pub fn process_order(raw_id: String) -> Result(Receipt, AppError) {
use id <- result.then(parse_id(raw_id))
use order <- result.then(find_order(id))
use paid <- result.then(charge_payment(order))
use _ <- result.then(send_receipt(paid))
Ok(Receipt(order_id: id, status: "complete"))
}
use Chain Flow
──────────────────────────────────────────────────
parse_id("42") → Ok(42) → id = 42
find_order(42) → Ok(order) → order = Order(...)
charge_payment(order) → Ok(paid)→ paid = Payment(...)
send_receipt(paid) → Ok(Nil)
Ok(Receipt(...)) ← final result
If any step returns Error(e):
→ remaining steps are skipped
→ Error(e) propagates to the end
use with Option
import gleam/option
pub fn get_user_city(users: Map(Int, User), id: Int) -> Option(String) {
use user <- option.then(map.get(users, id) |> option.from_result)
use address <- option.then(user.address)
Some(address.city)
}
use with Custom Callbacks
Any function that takes a callback as its last argument works with use:
// A function that opens a resource, runs a callback, then closes it
pub fn with_db_connection(callback: fn(Connection) -> a) -> a {
let conn = open_connection()
let result = callback(conn)
close_connection(conn)
result
}
// Without use:
with_db_connection(fn(conn) {
query(conn, "SELECT ...")
})
// With use:
use conn <- with_db_connection()
query(conn, "SELECT ...")
Resource Management Pattern
──────────────────────────────────────────────────
use conn <- with_db_connection()
→ opens connection
→ runs everything below as the callback
→ closes connection when done (guaranteed)
Like try-with-resources in Java, or `with` in Python,
but expressed as a flat sequence.
use with gleam/io for Logging
pub fn logged_operation(name: String, op: fn() -> Result(a, e)) -> Result(a, e) {
io.println("Starting: " <> name)
let result = op()
case result {
Ok(_) -> io.println("Done: " <> name)
Error(_) -> io.println("Failed: " <> name)
}
result
}
// Use it:
use user <- result.then(logged_operation("fetch_user", fn() { get_user(1) }))
use data <- result.then(logged_operation("fetch_data", fn() { get_data(user) }))
Ok(process(data))
Multiple Patterns in use
// Destructure directly in the use pattern:
use #(user, profile) <- result.then(get_user_with_profile(id))
display(user.name, profile.avatar)
use vs Pipe Operator
When to Use Each
──────────────────────────────────────────────────
|> (pipe) → chain pure transformations
each step is a simple function call
no callbacks involved
Example: value |> trim |> uppercase |> split
use → flatten callback-based operations
each step takes a callback / continuation
pattern must match to proceed
Example: use x <- result.then(...)
use y <- option.then(...)
Practical Example — File Upload Pipeline
import gleam/result
type UploadError {
FileTooLarge
InvalidFormat
StorageFull
DatabaseError
}
pub fn handle_upload(file: RawFile) -> Result(String, UploadError) {
use validated <- result.then(validate_file(file))
use compressed <- result.then(compress(validated))
use stored_path <- result.then(save_to_disk(compressed))
use _ <- result.then(record_in_db(stored_path))
Ok("Upload complete: " <> stored_path)
}
Upload Pipeline
──────────────────────────────────────────────────
validate_file(file)
OK → compress(validated)
OK → save_to_disk(compressed)
OK → record_in_db(stored_path)
OK → Ok("Upload complete: /uploads/...")
Error(StorageFull) → stops here
Error(InvalidFormat) → stops here
Error(FileTooLarge) → stops here
Key Points
use Expression Essentials
──────────────────────────────────────────────────
1. use pattern <- function(args) → flattens callbacks
2. Everything below use becomes the callback body
3. Works with result.then, option.then, or any fn(callback)
4. Compiler rewrites it to nested callbacks — zero overhead
5. Each use binds the success value to a name
6. A failure at any step short-circuits the rest
7. Use _ when you don't need the bound value
The use expression is Gleam's answer to the callback pyramid problem. It makes sequential, fallible operations read as naturally as a shopping list — each step on its own line, each step building on the one before it.
