Unions and narrowing
A union of different kinds of values (number | string, string[] | boolean | null) is stored as one 64-bit word with a small type tag. Narrowing with typeof, ===, instanceof, Array.isArray, truthiness or a switch on a discriminant is a tag test, and inside the narrowed branch the value is unboxed again.
ts
// Unions of different kinds narrowed by typeof, Array.isArray and ===; a discriminated union
// narrowed by switch.
function describe(v: number | string | boolean[] | null): string {
if (v === null) return "nothing";
if (typeof v === "number") return `number ${v * 2}`;
if (typeof v === "string") return `string ${v.toUpperCase()}`;
return `flags ${v.filter((b: boolean): boolean => b).length}/${v.length}`;
}
console.log(describe(21), describe("hi"), describe([true, false, true]), describe(null));
type Event =
| { kind: "click"; x: number; y: number }
| { kind: "key"; code: string }
| { kind: "quit" };
function handle(e: Event): string {
switch (e.kind) {
case "click":
return `click at ${e.x},${e.y}`;
case "key":
return `key ${e.code}`;
case "quit":
return "bye";
}
}
const events: Event[] = [
{ kind: "click", x: 3, y: 4 },
{ kind: "key", code: "Enter" },
{ kind: "quit" },
];
for (const e of events) console.log(handle(e));
// An un-narrowed union can still be printed, compared, and defaulted.
const maybe: string | number | undefined = events.length > 2 ? 42 : undefined;
console.log(maybe ?? "none", maybe === 42);text
number 42 string HI flags 2/3 nothing
click at 3,4
key Enter
bye
42 trueWhat works on an un-narrowed union
Printing, String(x), template interpolation, === / !==, typeof, ?? and truthiness work on any union. Anything else (x + 1, x.length, calling a method) needs one kind first; the compiler rejects it with CS1239 and suggests the narrowing.
Unions of object types are one object type over their common fields, so a discriminated union like Event above needs no tag word at all: e.kind is an ordinary field read.
Limits today
- A union no runtime tag can tell apart, such as two different array types, is rejected (CS1233), as is a union used as a
MaporSetkey. - Passing a
number[]where a(number | string)[]is expected would let the callee write a string into your number array, so it is rejected (CS1240). Copy it into a new array of the wider type. ascasts that narrow are not allowed; narrowing is done with runtime checks, which tsc then follows.
Next: Generics.