Using Opaque Types in Gleam for Encapsulated Data
Learn how to define and use Gleam opaque types to encapsulate data, enforce API stability, and hide internal representation while providing a clean public interface.
20 Jan 2026, 07:45 UTC

Desired outcome
Create a Gleam module that defines an opaque type to hide its internal representation while providing a stable public API for constructing and accessing the value.
Prerequisites
- Gleam toolchain installed (version 1.0.0 or newer).
- A terminal with permission to run
gleamcommands. - Basic familiarity with Gleam syntax and project structure.
Procedure
Create a new Gleam project:
gleam new opaque_demo cd opaque_demoDefine the opaque type in
src/types.gleam:pub opaque Username = Username(String) pub fn new_username(s: String) -> Username { Username(s) } pub fn username_value(u: Username) -> String { let Username(s) = u s }Create a consumer module in
src/user.gleamthat uses the opaque type:import types/{Username, new_username, username_value} pub fn greet() -> String { let un = new_username("alice") let name = username_value(un) "Hello, " ++ name }Attempt to pattern‑match on the opaque type from
src/user.gleamto confirm opacity (this should fail):pub fn bad_pattern(u: Username) -> String { // Uncommenting the line below causes a compile‑time error // let Username(s) = u "should not compile" }Build the project to see the compiler error for the illegal pattern match:
gleam buildExpected output: the build succeeds if the
bad_patternfunction is commented out or removed; otherwise the compiler reports an error such as "Unable to find constructor Username" confirming the type is opaque.Remove or comment out the illegal pattern match, then build and test:
gleam build gleam testExpected outcome: the build succeeds and any tests you write (e.g., asserting that
greet()returns "Hello, alice") pass, demonstrating that the opaque type works as intended.
Expected checks
- Compiler error when external code tries to pattern‑match or access the hidden constructor.
- Successful build and test run when only the public constructor and accessor are used.
- Ability to change the underlying representation (e.g., from
Stringto a tuple) and update onlynew_usernameandusername_valuewithout touching consumer code.
Recovery options
- If the representation changes, adjust the constructor and accessor in the defining module; consumer modules remain unaffected.
- If you need to expose additional operations, add more public functions that work with the opaque type; never expose the internal constructor directly.
- Should you accidentally expose the constructor (e.g., by removing
opaque), revert the change and rebuild to restore encapsulation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.