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.

Leave a Comment

Your email address will not be published. Required fields are marked *