Branded/Opaque types in rescript?

Hey there :wave:
I’m very new to rescript and I have searched the docs for such a concept but haven’t found anything related to it.
Is there such a thing as an opaque type in rescript?

Yes definitely great support for that. Read about module types/signatures. For example, here the underlying type of UniqueId is hidden from code that tries to use it.

module type UniqueIdType = {
  type t
  let makeRandom: unit => t
  let equal: (t, t) => bool

module UniqueId: UniqueIdType = {
  type t = float
  let makeRandom = () => Js.Math.random()
  let equal = (v1, v2) => v1 === v2

You can also wrap primitive types like type uniqueId = UniqueId(float) to create new ‘nominal’ types which is awkward to do in TypeScript.


I see… Awesome stuff!
Thanks for the explanation :+1:

Another simple example (ignore the obviously flawed logic for validating the email):

With that example, you can be sure that any interaction with an Email.t is indeed with a proper, validated email.

This functionality is very accessible in ReScript, and it’s personally one of my favorite features. The fact that it’s so easy to have the type system help you control what values go where, and prevent accidentally mixing things up, is really valuable.


We especially use this all the time for writing bindings to more dynamic-style JavaScript code, to capture things like ‘a prop can be either a string or a bool’.


@yawaramin do you mean in the following way?

  1. Create an opaque type
  2. create fromBool, fromString functions which return the opaque type (internally using identity)
  3. create a binding to external js function which accepts this opaque type?
1 Like

Right, e.g. Unwrap array of polymorphic variants? - #2 by yawaramin

1 Like

I use validated opaque primitives all over the place and build them with functors. For example, I can build a module for a type holding just the numbers 0-10 like this. The module that gets built includes functions for making, comparing, ordering, validating, exporting, serializing, etc. I use it for strings to control min/max length and content mostly.

type rangeError = TooSmall | TooBig

module ZeroToTen = ValidatedPrimitive.Make({
  type domain = int
  type error = rangeError
  let cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0
  let normalize = (. v) => v < 0 ? Error(TooSmall) : v > 10 ? Error(TooBig) : Ok(v)

And then use it like…

let a = ZeroToTen.makeExn(2)
let b = ZeroToTen.makeExn(6)
let areSame = ZeroToTen.eq(a, b)
let aValue = a->ZeroToTen.value

Code here…
playground code for validated primitives