What is Gleam?
Gleam is a friendly, type-safe functional programming language that compiles to Erlang and JavaScript. It runs on the battle-tested BEAM VM, giving you access to Erlang's world-class concurrency and fault tolerance.
Type Safety
Statically typed with full inference. Catch errors at compile time, not in production. No null, no exceptions.
Fault Tolerant
Runs on the Erlang VM (BEAM). Millions of lightweight processes, self-healing supervisors, battle-tested since 1986.
Friendly
Helpful error messages, clean syntax inspired by Rust and Elixir. Designed to be approachable.
Dual Target
Compiles to Erlang for the BEAM or JavaScript for browsers and Node.js. Use existing libraries from both ecosystems.
Basics
Hello World
import gleam/io pub fn main() { io.println("Hello, world!") }
Variables
// Variables are immutable let name = "Gleam" let age: Int = 1 // optional type annotation // Shadowing (rebinding) is allowed let x = 5 let x = x + 1 // x is now 6 // Type aliases pub type UserId = Int
Basic Types
// Integers (arbitrary precision on BEAM) let int = 42 let big = 1_000_000 let hex = 0xF let binary = 0b1010 // Floats (64-bit, different operators!) let float = 3.14 let sum = 1.0 +. 2.5 // float addition let product = 3.0 *. 4.0 // float multiplication // Strings (UTF-8) let greeting = "Hello, " <> "world!" // concatenation // Booleans let yes = True let no = False let result = True && False // False let either = True || False // True
+ for Int, +. for Float.
Operators
| Operation | Int | Float |
|---|---|---|
| Addition | + |
+. |
| Subtraction | - |
-. |
| Multiplication | * |
*. |
| Division | / |
/. |
| Remainder | % |
— |
| Comparison | < > <= >= |
<. >. <=. >=. |
| Equality | == != (works for all types) |
|
Data Types
Lists
// Lists are singly-linked, homogeneous let numbers: List(Int) = [1, 2, 3] let empty: List(String) = [] // Prepend with spread let more = [0, ..numbers] // [0, 1, 2, 3] // Pattern match on lists case numbers { [] -> "empty" [first] -> "single element" [first, ..rest] -> "has elements" }
Tuples
// Fixed-size, mixed types let pair = #(1, "hello") let triple = #(1, 2.0, "three") // Access by index let first = pair.0 // 1 let second = pair.1 // "hello" // Destructure let #(x, y) = pair
Nil
// Nil is Gleam's unit type (like void) let nothing: Nil = Nil // Functions that don't return anything return Nil pub fn say_hello() -> Nil { io.println("Hello!") }
Blocks
// Blocks create a new scope and return last expression let value = { let x = 10 let y = 20 x + y // returns 30 }
Functions
Defining Functions
// Public function pub fn add(a: Int, b: Int) -> Int { a + b // implicit return (last expression) } // Private function (no pub) fn helper(x: Int) -> Int { x * 2 }
Anonymous Functions
// Lambda syntax let double = fn(x) { x * 2 } let result = double(5) // 10 // With type annotations let add = fn(a: Int, b: Int) -> Int { a + b } // Closures capture environment let multiplier = 3 let triple = fn(x) { x * multiplier }
Labelled Arguments
// Define with labels pub fn greet(name name: String, greeting msg: String) -> String { msg <> ", " <> name <> "!" } // Call with labels (any order) greet(greeting: "Hello", name: "Lucy") greet(name: "Lucy", greeting: "Hi") // Shorthand: same internal and external name pub fn create_user(name: String, age age: Int) -> User { User(name: name, age: age) }
Function Capture
// Create partial function with _ let add_one = add(1, _) let result = add_one(5) // 6 // Equivalent to: let add_one = fn(x) { add(1, x) }
Pipe Operator
// Chain function calls with |> "hello world" |> string.uppercase |> string.reverse |> io.println // Equivalent to: io.println(string.reverse(string.uppercase("hello world"))) // Pipe to any argument position 5 |> int.to_string |> string.append("Number: ", _)
Generic Functions
// Type variables are lowercase pub fn identity(x: a) -> a { x } pub fn pair(a: first, b: second) -> #(first, second) { #(a, b) } // Higher-order generic function pub fn twice(f: fn(a) -> a, x: a) -> a { f(f(x)) }
Pattern Matching
Gleam uses case expressions for pattern matching. The compiler ensures all cases are handled.
Case Expressions
// Basic case case x { 0 -> "zero" 1 -> "one" _ -> "other" // wildcard matches anything } // With variable binding case x { 0 -> "zero" n -> "got: " <> int.to_string(n) }
List Patterns
case my_list { [] -> "empty" [only] -> "single: " <> only [first, second] -> "exactly two" [head, ..tail] -> "first: " <> head [_, _, ..rest] -> "at least two" }
String Patterns
// Match string prefix case text { "Hello, " <> name -> "Greeting for: " <> name "Goodbye, " <> name -> "Farewell to: " <> name _ -> "Unknown format" }
Alternative Patterns
// Multiple patterns with | case month { 1 | 3 | 5 | 7 | 8 | 10 | 12 -> 31 4 | 6 | 9 | 11 -> 30 2 -> 28 _ -> 0 }
Guards
// Add conditions with if case x { n if n < 0 -> "negative" n if n > 0 -> "positive" _ -> "zero" } // Guards with lists case numbers { [first, ..] if first > 100 -> "starts big" [first, ..rest] -> "normal list" [] -> "empty" }
Pattern Aliases
// Bind entire pattern with 'as' case pair { #(a, b) as tuple -> { io.debug(tuple) // use the whole thing a + b // also use the parts } }
Multiple Subjects
// Match multiple values at once case x, y { 0, 0 -> "both zero" 0, _ -> "x is zero" _, 0 -> "y is zero" _, _ -> "neither zero" }
Let Assert
// Assert pattern matches (panics on failure) let assert [first, ..] = items let assert Ok(value) = risky_function() // With custom error message let assert Ok(regex) = regex.compile("ab?c+") as "Invalid regex"
let assert sparingly! It causes a panic on failure. Prefer case or Result for recoverable errors.
Custom Types
Simple Enums
pub type Season { Spring Summer Autumn Winter } let current = Summer case current { Spring -> "Flowers bloom" Summer -> "Beach time" Autumn -> "Leaves fall" Winter -> "Snow falls" }
Records (Single Variant)
// Type with named fields pub type User { User(name: String, age: Int, active: Bool) } // Create instance let alice = User(name: "Alice", age: 30, active: True) // Access fields let name = alice.name let age = alice.age // Pattern match fields let User(name: n, age: a, active: _) = alice
Variants with Data
pub type Shape { Circle(radius: Float) Rectangle(width: Float, height: Float) Triangle(base: Float, height: Float) } pub fn area(shape: Shape) -> Float { case shape { Circle(r) -> 3.14159 *. r *. r Rectangle(w, h) -> w *. h Triangle(b, h) -> b *. h /. 2.0 } }
Record Updates
// Create new record with some fields changed let alice = User(name: "Alice", age: 30, active: True) let older_alice = User(..alice, age: 31) // Multiple updates let inactive_bob = User(..alice, name: "Bob", active: False)
Generic Types
// Type with type parameter pub type Box(inner) { Box(value: inner) } let int_box: Box(Int) = Box(42) let str_box: Box(String) = Box("hello") // Multiple type parameters pub type Pair(a, b) { Pair(first: a, second: b) }
Opaque Types
// Hide constructor from other modules pub opaque type PositiveInt { PositiveInt(value: Int) } // Only expose a validated constructor pub fn new(n: Int) -> Result(PositiveInt, String) { case n > 0 { True -> Ok(PositiveInt(n)) False -> Error("must be positive") } }
Results & Options
Gleam has no exceptions or null. Use Result for operations that can fail, and Option for values that may be absent.
Result Type
// Result is defined as: pub type Result(value, error) { Ok(value) Error(error) } // Function that can fail pub fn divide(a: Int, b: Int) -> Result(Int, String) { case b { 0 -> Error("division by zero") _ -> Ok(a / b) } } // Handle result case divide(10, 2) { Ok(value) -> io.println("Result: " <> int.to_string(value)) Error(msg) -> io.println("Error: " <> msg) }
Result Module Functions
import gleam/result // map: transform success value Ok(5) |> result.map(fn(x) { x * 2 }) // Ok(10) // try: chain operations that can fail Ok("42") |> result.try(int.parse) // Ok(42) or Error // unwrap: get value or provide default Error("oops") |> result.unwrap(0) // 0 // is_ok / is_error result.is_ok(Ok(1)) // True result.is_error(Ok(1)) // False
Option Type
import gleam/option.{type Option, Some, None// Option is defined as: pub type Option(a) { Some(a) None } // Optional fields pub type Person { Person(name: String, nickname: Option(String)) } let alice = Person("Alice", Some("Ali")) let bob = Person("Robert", None) // Handle option case alice.nickname { Some(nick) -> "Call me " <> nick None -> "No nickname" }
Custom Error Types
pub type ParseError { InvalidFormat OutOfRange(min: Int, max: Int) UnknownField(name: String) } pub fn parse_age(input: String) -> Result(Int, ParseError) { case int.parse(input) { Error(_) -> Error(InvalidFormat) Ok(age) if age < 0 -> Error(OutOfRange(0, 150)) Ok(age) if age > 150 -> Error(OutOfRange(0, 150)) Ok(age) -> Ok(age) } }
Use Expression
The use expression reduces nesting when working with callbacks and Result/Option chains. It's Gleam's most distinctive feature.
Basic Use
// Without use (deeply nested) pub fn without_use() -> Result(Int, String) { result.try(get_a(), fn(a) { result.try(get_b(), fn(b) { result.try(get_c(), fn(c) { Ok(a + b + c) }) }) }) } // With use (flat!) pub fn with_use() -> Result(Int, String) { use a <- result.try(get_a()) use b <- result.try(get_b()) use c <- result.try(get_c()) Ok(a + b + c) }
How Use Works
// use turns remaining code into a callback use x <- some_function(arg1, arg2) // ...rest of function... // Is equivalent to: some_function(arg1, arg2, fn(x) { // ...rest of function... })
Common Use Patterns
// Chaining Results pub fn process_user(id: Int) -> Result(String, DbError) { use user <- result.try(fetch_user(id)) use profile <- result.try(fetch_profile(user)) use avatar <- result.try(fetch_avatar(profile)) Ok(avatar.url) } // With list operations pub fn process_items(items: List(Int)) { use item <- list.each(items) io.println(int.to_string(item)) } // Mapping over results pub fn double_result(r: Result(Int, e)) -> Result(Int, e) { use value <- result.map(r) value * 2 }
Bool Module with Use
import gleam/bool // Early return pattern with bool.guard pub fn validate(x: Int) -> Result(Int, String) { use <- bool.guard(x < 0, Error("must be positive")) use <- bool.guard(x > 100, Error("must be <= 100")) Ok(x) }
use works with any function that takes a callback as its last argument. The standard library is designed around this pattern.
Modules & Imports
Imports
// Qualified import (recommended) import gleam/io import gleam/string io.println("Hello") string.uppercase("hello") // Aliased import import gleam/string as text text.reverse("hello") // Unqualified import import gleam/io.{println, debug} println("Hello") // Import types import gleam/option.{type Option, Some, None} let x: Option(Int) = Some(42)
Standard Library Highlights
// gleam/list list.map([1, 2, 3], fn(x) { x * 2 }) // [2, 4, 6] list.filter([1, 2, 3], fn(x) { x > 1 }) // [2, 3] list.fold([1, 2, 3], 0, fn(acc, x) { acc + x }) // 6 list.find([1, 2, 3], fn(x) { x > 1 }) // Ok(2) // gleam/dict (hash map) import gleam/dict let scores = dict.from_list([#("alice", 100), #("bob", 85)]) dict.get(scores, "alice") // Ok(100) dict.insert(scores, "carol", 90) // gleam/string string.split("a,b,c", ",") // ["a", "b", "c"] string.join(["a", "b"], "-") // "a-b" string.contains("hello", "ell") // True // gleam/int, gleam/float int.to_string(42) // "42" int.parse("42") // Ok(42) float.to_string(3.14) // "3.14"
Debugging
// echo keyword for quick debugging let result = echo calculate(42) // Prints: [src/main.gleam:5] 84 // io.debug returns the value (can be chained) 42 |> int.multiply(2) |> io.debug // prints and passes through |> int.to_string
Todo & Panic
// Mark unfinished code pub fn coming_soon() { todo as "implement this feature" } // Crash with message (for truly impossible cases) case impossible_value { _ -> panic as "this should never happen" }
OTP & Concurrency
Gleam runs on the BEAM, giving you access to Erlang's legendary concurrency model. The gleam_otp library provides type-safe actors.
Quick Commands
# Create new project gleam new myproject cd myproject # Build project gleam build # Run project gleam run # Run tests gleam test # Add dependencies gleam add gleam_json gleam add gleam_otp # Format code gleam format # Type check gleam check # Generate docs gleam docs build
Project Structure
myproject/ ├── gleam.toml # Project config ├── src/ │ └── myproject.gleam # Main module ├── test/ │ └── myproject_test.gleam └── build/ # Generated (gitignored)
gleam.toml
name = "myproject" version = "1.0.0" target = "erlang" # or "javascript" [dependencies] gleam_stdlib = "~> 0.40" gleam_otp = "~> 0.12" gleam_json = "~> 2.0" [dev-dependencies] gleeunit = "~> 1.0"
Processes
import gleam/erlang/process.{type Subject// Spawn a process let pid = process.start(fn() { io.println("Hello from process!") }, True) // Sleep process.sleep(1000) // milliseconds
Actors (Type-Safe Message Passing)
import gleam/otp/actor.{type Next} import gleam/erlang/process.{type Subject} // Define message type pub type Message { Increment GetCount(reply_to: Subject(Int)) } // Message handler fn handle_message(msg: Message, count: Int) -> Next(Message, Int) { case msg { Increment -> actor.continue(count + 1) GetCount(client) -> { process.send(client, count) actor.continue(count) } } } // Start actor pub fn start_counter() { let assert Ok(actor) = actor.start(0, handle_message) // Send messages process.send(actor, Increment) process.send(actor, Increment) // Request response let count = process.call(actor, GetCount, 1000) io.debug(count) // 2 }