Gleam Panic and Assert
Panic and assert are emergency exits in Gleam. They crash the current process with a clear message when something that should never happen does happen. These tools are not for normal error handling — they are for programming bugs and violated invariants.
panic — Crash With a Message
Call panic when you reach a code path that should be impossible:
pub fn direction_to_degrees(d: Direction) -> Int {
case d {
North -> 0
East -> 90
South -> 180
West -> 270
}
}
With exhaustive case, you rarely need panic. But sometimes you hold an invariant that the type system cannot verify:
pub fn head_or_crash(list: List(a)) -> a {
case list {
[first, ..] -> first
[] -> panic as "Expected a non-empty list"
}
}
panic as "message" Output
──────────────────────────────────────────────────
→ src/main.gleam:8
panic as "Expected a non-empty list"
The process crashes immediately with this message
and a stack trace.
The panic as Syntax
panic as "This should never happen"
panic as "Corrupted state: " <> context
panic // without message — less informative
Always include a message. A panic without context is hard to debug — you see a crash but not the reason.
assert — Verify an Assumption
The assert expression verifies a boolean condition. If the condition is False, it panics:
let count = list.length(items)
assert count > 0 // panics if count is 0 or less
assert Flow
──────────────────────────────────────────────────
assert count > 0
count = 5 → condition True → continues normally
count = 0 → condition False → PANIC
let assert — Pattern Assert
let assert matches a pattern and panics if the match fails:
let assert Ok(port) = int.parse("8080")
// port = 8080
// Panics if int.parse returns Error
let assert vs case
──────────────────────────────────────────────────
let assert — for guaranteed matches (panics on failure):
let assert Ok(n) = int.parse("42")
case — for handled alternatives (no panic):
case int.parse(user_input) {
Ok(n) -> use_number(n)
Error(_) -> show_error()
}
When to Use Each
Decision Guide
──────────────────────────────────────────────────
Situation │ Tool
──────────────────────────────────┼────────────────
Handling expected failures │ Result / case
User input that might be wrong │ Result / case
Setup code where you control data │ let assert
A case that "cannot happen" │ panic as "reason"
Verifying a logic invariant │ assert condition
Tests — known-good input │ let assert
Panic in Tests
Test frameworks use panics to signal failures:
import gleeunit/should
pub fn add_test() {
let result = add(2, 3)
result |> should.equal(5) // panics if not equal
}
Test panics are caught by the test runner. They appear as test failures, not crashes of the whole test suite.
Process Isolation and Panic
On the BEAM, each Gleam process is isolated. A panic in one process does not crash other processes:
Process Isolation Diagram
──────────────────────────────────────────────────
Process A → runs normally
Process B → PANICS
Process C → runs normally
Process B's crash does not affect A or C.
A supervisor can restart B automatically.
This isolation is why Erlang and Gleam systems can claim high availability. Crashes are contained and recoverable.
Panic Messages Best Practices
Good panic messages:
──────────────────────────────────────────────────
✓ panic as "Payment state machine in invalid state: refunded before paid"
✓ panic as "Config missing required key: database_url"
✓ panic as "List must not be empty before calling max()"
Bad panic messages:
──────────────────────────────────────────────────
✗ panic → no message — useless for debugging
✗ panic as "error" → too vague
✗ panic as "bug" → tells nothing about the bug
Practical Example — State Machine Guard
type OrderState { Draft; Submitted; Paid; Shipped; Cancelled }
pub fn transition(state: OrderState, action: String) -> OrderState {
case #(state, action) {
#(Draft, "submit") -> Submitted
#(Submitted, "pay") -> Paid
#(Paid, "ship") -> Shipped
#(Draft, "cancel") -> Cancelled
#(Submitted, "cancel") -> Cancelled
_ ->
panic as "Invalid transition: " <> action <> " from state " <> debug_state(state)
}
}
The state machine only allows valid transitions. An invalid combination represents a programming bug — panic is the right response because the caller violated the expected usage contract.
Key Points
Panic and Assert Summary
──────────────────────────────────────────────────
1. panic as "msg" → crash immediately, for impossible states
2. assert cond → crash if condition is False
3. let assert pat → crash if pattern doesn't match
4. Use for programming bugs, never for user errors
5. On BEAM, panics are isolated to the current process
6. Supervisors can restart crashed processes automatically
7. Always write a descriptive message in panic as
Panic and assert are not error handling — they are bug detection. They say "if you reach this code, there is a mistake in the program logic." Used correctly, they make bugs loud, obvious, and easy to locate during development, while production systems handle the rare crash through process supervision.
