Nominal and Opaque Types
This example uses nominal types (:=) to keep meters and seconds distinct, even though both contain a number. It then uses an opaque nominal type (::) to ensure that a Positive value can only be created through a constructor that rejects zero.
import Positive Meters := { value : U64 }.{ is_eq : _ } Seconds := { value : U64 }.{ is_eq : _ } distance = Meters.{ value: 10 } duration = Seconds.{ value: 10 } expect distance.value == duration.value # distance == duration would be a type error: Meters and Seconds differ. expect { count = Positive.from_u64(3)? count.to_u64() == 3 } expect Positive.from_u64(0) == Err(Zero) # Positive.(0) is forbidden here: only Positive.roc can construct it.
The Positive type lives in a separate module, where its backing number is hidden from callers. Its constructor is the only way for this app to create a value, so the check against zero cannot be bypassed:
Positive :: U64.{ is_eq : _ from_u64 : U64 -> Try(Positive, [Zero]) from_u64 = |n| if n == 0 { Err(Zero) } else { Ok(Positive.(n)) } to_u64 : Positive -> U64 to_u64 = |Positive.(n)| n }
If we only wanted a shorter name for an existing type, we could use a type alias (:). Unlike the nominal types above, an alias does not give the type a separate identity.
Output
Run this from the directory that has main.roc in it:
$ roc test main.roc All (3) tests passed in 10.2 ms.
Validated with Roc nightly-2026-10-29-a0a9fc2.