website/docs/lang/nominal-structural.md
A static type checker can use either the name (nominal typing) or the structure (structural typing) of types when comparing them against other types (like when checking if one is a subtype of another).
Languages like C++, Java, and Swift have primarily nominal type systems.
// Pseudo code: nominal system
class Foo { method(input: string) { /* ... */ } }
class Bar { method(input: string) { /* ... */ } }
let foo: Foo = new Bar(); // Error!
In this pseudo-code example, the nominal type system errors even though both classes have a method of the same name and type. This is because the name (and declaration location) of the classes is different.
Languages like Go and Elm have primarily structural type systems.
// Pseudo code: structural system
class Foo { method(input: string) { /* ... */ } }
class Bar { method(input: string) { /* ... */ } }
let foo: Foo = new Bar(); // Works!
In this pseudo-code example, the structural type system allows a Bar to be used as a Foo,
since both classes have methods and fields of the same name and type.
If the shape of the classes differ however, then a structural system would produce an error:
// Pseudo code
class Foo { method(input: string) { /* ... */ } }
class Bar { method(input: number) { /* ... */ } }
let foo: Foo = new Bar(); // Error!
We've demonstrated both nominal and structural typing of classes, but there are also other complex types like objects and functions which can also be either nominally or structurally compared. Additionally, a type system may have aspects of both structural and nominal systems.
Flow uses structural typing for objects and functions, but nominal typing for classes.
When comparing a function type with a function it must have the same structure in order to be considered valid.
type FuncType = (input: string) => void;
function func(input: string) { /* ... */ }
let test: FuncType = func; // Works!
When comparing an object type with an object it must have the same structure in order to be considered valid.
type ObjType = {property: string};
let obj = {property: "value"};
let test: ObjType = obj; // Works
When you have two classes with the same structure, they still are not considered equivalent because Flow uses nominal typing for classes.
class Foo { method(input: string) { /* ... */ } }
class Bar { method(input: string) { /* ... */ } }
let test: Foo = new Bar(); // Error!
If you wanted to use a class structurally you could do that using an interface:
interface Interface {
method(value: string): void;
};
class Foo { method(input: string) { /* ... */ } }
class Bar { method(input: string) { /* ... */ } }
let test1: Interface = new Foo(); // Works
let test2: Interface = new Bar(); // Works
You can use opaque types to turn a previously structurally typed alias into a nominal one (outside of the file that it is defined).
// A.js
export type MyTypeAlias = string;
export opaque type MyOpaqueType = string;
const x: MyTypeAlias = "hi"; // Works
const y: MyOpaqueType = "hi"; // Works
In a different file, MyTypeAlias remains transparent but MyOpaqueType is nominal:
// B.js — when imported, type aliases are transparent but opaque types are not
type MyTypeAlias = string;
declare opaque type MyOpaqueType;
const x: MyTypeAlias = "hi"; // Works
const y: MyOpaqueType = "hi"; // Error! `MyOpaqueType` is not interchangeable with `string`
Flow Enums do not allow enum members with the same value, but which belong to different enums, to be used interchangeably.
enum A {
X = "x",
}
enum B {
X = "x",
}
const a: A = B.X; // Error!