Gleam Erlang Interop
Gleam compiles to Erlang bytecode and runs on the BEAM. This gives Gleam direct access to the entire Erlang ecosystem — over 35 years of battle-tested libraries for networking, databases, distributed systems, and more. Interop requires a thin bridge layer that Gleam calls the Foreign Function Interface (FFI).
Why Erlang Interop Matters
Available to Gleam via Erlang Interop
──────────────────────────────────────────────────
Erlang OTP → supervisors, processes, GenServer
Erlang standard lib → ets (in-memory tables), crypto
Elixir libraries → Phoenix, Ecto, and thousands more
Hex packages → every Erlang/Elixir package
You do not have to rewrite existing Erlang libraries in Gleam. You call them directly through FFI declarations.
Calling Erlang Functions
Declare an Erlang function with the @external attribute:
// Declare the external function
@external(erlang, "erlang", "now")
pub fn now() -> #(Int, Int, Int)
// Use it in Gleam
let timestamp = now()
// #(megaseconds, seconds, microseconds)
@external Syntax
──────────────────────────────────────────────────
@external(erlang, "module_name", "function_name")
pub fn gleam_name(params) -> ReturnType
│ │ │
│ └── Erlang module (atom as string)
└── target (erlang or javascript)
└── Erlang function name
Common Erlang Modules
Frequently Used Erlang Modules
──────────────────────────────────────────────────
erlang → system functions, time, atoms
lists → list operations (mostly wrapped by gleam/list)
maps → map operations
binary → binary data manipulation
crypto → cryptographic functions
os → operating system interaction
file → file system access
io_lib → string formatting
timer → delays and intervals
Practical FFI Examples
// System time in milliseconds
@external(erlang, "erlang", "system_time")
fn erlang_system_time(unit: atom) -> Int
// Random number
@external(erlang, "rand", "uniform")
pub fn random_int(max: Int) -> Int
// Sleep
@external(erlang, "timer", "sleep")
pub fn sleep(ms: Int) -> Nil
// Environment variable
@external(erlang, "os", "getenv")
fn erlang_getenv(name: charlist) -> charlist | false_atom
Handling Erlang Atoms
Erlang uses atoms — lightweight string-like identifiers — extensively. Gleam has no built-in atom type but can use them through the gleam/erlang package:
import gleam/erlang/atom
let ok_atom = atom.create_from_string("ok")
let result = atom.to_string(ok_atom) // "ok"
Charlists vs Strings
Erlang strings are charlists (lists of integers). Gleam strings are UTF-8 binaries. Conversion is required when calling Erlang functions that expect charlists:
Gleam String → Erlang charlist
──────────────────────────────────────────────────
"hello" → [104, 101, 108, 108, 111]
Convert with gleam/erlang:
import gleam/erlang
let cl = erlang.to_charlist("hello")
let s = erlang.from_charlist(cl)
Using Elixir Libraries
Elixir compiles to BEAM bytecode, so its modules are callable from Gleam. Elixir module names use the Elixir. prefix:
@external(erlang, "Elixir.String", "upcase")
pub fn elixir_upcase(s: String) -> String
// Works if the Elixir.String module is available in the BEAM path
Safe Wrapping Pattern
Always wrap external functions in a Gleam function that provides type safety and a clean API:
// Raw FFI declaration (private)
@external(erlang, "crypto", "strong_rand_bytes")
fn erlang_rand_bytes(n: Int) -> BitArray
// Safe Gleam wrapper (public)
pub fn random_bytes(count: Int) -> Result(BitArray, String) {
case count > 0 {
False -> Error("Count must be positive")
True -> Ok(erlang_rand_bytes(count))
}
}
Wrapper Pattern
──────────────────────────────────────────────────
Erlang world: erlang_rand_bytes(n) → raw BitArray
│
▼ wrapped in Gleam
Gleam world: random_bytes(n) → Result(BitArray, String)
│
└── type-safe, validated, documented
Calling Gleam from Erlang
Gleam modules are accessible from Erlang. Gleam compiles modules to Erlang with predictable names:
Gleam module → Erlang module name
──────────────────────────────────────────────────
my_app → my_app
user/profile → user@profile
From Erlang:
my_app:main().
'user@profile':get_user(Id).
Key Points
Erlang Interop Essentials
──────────────────────────────────────────────────
1. @external(erlang, "module", "function") declares FFI
2. Type annotations in Gleam → type-checked at compile time
3. Runtime types must match — Gleam cannot verify Erlang internals
4. Wrap raw FFI in Gleam functions for safety
5. Charlists and Strings require explicit conversion
6. Elixir libraries callable via "Elixir.Module" prefix
7. Gleam modules callable from Erlang with predictable names
Erlang interop gives Gleam access to the full BEAM ecosystem immediately. You get decades of proven libraries — database drivers, HTTP clients, cryptography, distributed systems — without waiting for the Gleam community to rewrite them natively.
