Opaque Type Aliases

Enforcing abstraction through the type system

Opaque type aliases are type aliases that do not allow access to their underlying type outside of the file in which they are defined.

1
opaque type ID = string;

Opaque type aliases, like regular type aliases, may be used anywhere a type can be used.

1
2
3
4
5
6
7
// @flow
opaque type ID = string;

function identity(x: ID): ID {
  return x;
}
export type {ID};

Opaque Type Alias Syntax

Opaque type aliases are created using the words opaque type followed by its name, an equals sign =, and a type definition.

1
opaque type Alias = Type;

You can optionally add a subtyping constraint to an opaque type alias by adding a colon : and a type after the name.

1
opaque type Alias: SuperType = Type;

Any type can appear as the super type or type of an opaque type alias.

1
2
3
4
5
6
7
8
opaque type StringAlias = string;
opaque type ObjectAlias = {
  property: string,
  method(): number,
};
opaque type UnionAlias = 1 | 2 | 3;
opaque type AliasAlias: ObjectAlias = ObjectAlias;
opaque type VeryOpaque: AliasAlias = ObjectAlias;

Opaque Type Alias Type Checking

Within the Defining File

When in the same file the alias is defined, opaque type aliases behave exactly as regular type aliases do.

1
2
3
4
5
6
7
8
9
10
//@flow
opaque type NumberAlias = number;

(0: NumberAlias);

function add(x: NumberAlias, y: NumberAlias): NumberAlias {
    return x + y;
}
function toNumberAlias(x: number): NumberAlias { return x; }
function toNumber(x: NumberAlias): number { return x; }

Outside the Defining File

When importing an opaque type alias, it behaves like a nominal type, hiding its underlying type.

exports.js

1
export opaque type NumberAlias = number;

imports.js

1
2
3
4
5
6
7
import type {NumberAlias} from './exports';

(0: NumberAlias) // Error: 0 is not a NumberAlias!

function convert(x: NumberAlias): number {
  return x; // Error: x is not a number!
}

Subtyping Constraints

When you add a subtyping constraint to an opaque type alias, we allow the opaque type to be used as the super type when outside of the defining file.

exports.js

1
export opaque type ID: string = string;

imports.js

1
2
3
4
5
6
7
8
9
import type {ID} from './exports';

function formatID(x: ID): string {
    return "ID: " + x; // Ok! IDs are strings.
}

function toID(x: string): ID {
    return x; // Error: strings are not IDs.
}

When you create an opaque type alias with a subtyping constraint, the type in the type position must be a subtype of the type in the super type position.

1
2
3
//@flow
opaque type Bad: string = number; // Error: number is not a subtype of string
opaque type Good: {x: string} = {x: string, y: number};
number This type is incompatible with string

Generics

Opaque type aliases can also have their own generics, and they work exactly as generics do in regular type aliases

1
2
3
4
5
6
7
8
9
10
11
12
// @flow
opaque type MyObject<A, B, C>: { foo: A, bar: B } = {
  foo: A,
  bar: B,
  baz: C,
};

var val: MyObject<number, boolean, string> = {
  foo: 1,
  bar: true,
  baz: 'three',
};

Library Definitions

You can also declare opaque type aliases in libdefs. There, you omit the underlying type, but may still optionally include a super type.

1
2
declare opaque type Foo;
declare opaque type PositiveNumber: number;

Was this guide helpful? Let us know by sending a message to @flowtype.