Why does TypeScript have both `void` and `undefined`?
TypescriptUndefinedVoidTypescript Problem Overview
In TypeScript, you can annotate a function as returning void
:
function fn1(): void {
// OK
}
function fn2(): void {
// Error
return 3;
}
You can also annotate a function to return undefined
:
function fn3(): undefined {
// OK
return;
}
function fn4(): undefined {
// Error
return 3;
}
So it seems that if you call a function returning void
, you'll always get back the value undefined
. Yet you can't write this code:
function fn5(): void {
}
let u: undefined = fn5(); // Error
Why isn't void
just an alias for undefined
? Does it need to exist at all?
Typescript Solutions
Solution 1 - Typescript
void
has special meaning in function return types, and is not an alias for undefined
. Thinking of it this way is very wrong. Why?
The intent of void
is that a function's return value will not be observed. This is very different from will be undefined
. It's important to have this distinction so that you can properly describe functions like forEach
. Let's consider a freestanding version of Array#forEach
, written with undefined
instead of void
in the callback return position:
declare function forEach<T>(arr: T[], callback: (el: T) => undefined): void;
If you tried to use this function:
let target: number[] = [];
forEach([1, 2, 3], el => target.push(el));
You'd get an error:
> Type "number" is not assignable to type "undefined"
This is a correct error - you said you wanted a function that returned the value undefined
, but you actually provided a function that returned the value number
because that's what Array#push
returns!
Using void
instead means that forEach
promises not to use the return value, so it can be called with a callback that returns any value
declare function forEach<T>(arr: T[], callback: (el: T) => void): void;
let target: number[] = [];
// OK
forEach([1, 2, 3], el => target.push(el));
Why not just use any
? If you're actually the one implementing forEach
, you really don't want that - having an any
floating is a dangerous thing that can defeat typechecking very easily.
The corollary to this is that if you have some function expression whose return type is void
, you cannot say with any certainty that the result of invoking that function is undefined
.
void
is not an alias for undefined
and an expression of type void
may have any value, not just undefined
Again, In a function body whose return type is explicitly listed as void
, TypeScript will stop you from "accidently" returning a value, even though this wouldn't create a type system violation. This is helpful for catching bugs that appear from a refactoring:
// Old version
function fn(arr: number[]): void {
const arr1 = arr.map(x => {
return 3;
});
}
// New version
function fn(arr: number[]): void {
for (const x of arr) {
// Oops, meant to do something else
return 3;
};
}
Solution 2 - Typescript
they are different in semantics.
undefined
is an adjective constraining a symbol.- "a symbol is undefined" means "this symbol is not bound to any value".
- you can't say "a value is undefined".
void
is an adjective constraining a value.- "a value is void" means "this value is not provided".
- you can't say "a symbol is unprovided".
example 1:
-
✔️
function f(): void {}
f
is a function which returns an unprovided value. -
❌
function f(): undefined {}
f
is a function which returns anundefined value.
example 2:
-
✔️
const a: void = <void>f();
a
is a symbol which can be bound to an unprovided value. -
❌
const a: undefined = <void>f();
a
is a symbol which can be bound to anundefined value.
example 3:
-
✔️
const a: void = <void>f();
assert the returned value of
f()
is an unprovided value. -
❌
const a: void = <undefined>f();
assert the returned value of
f()
is anundefined value.
example 4:
-
❌
function g(x?: number) { assert(x === undefined); }
x
is a symbol which is bound to anundefined value. -
✔️
function g(x?: number) { assert(typeof x === 'undefined'); }
x
is a symbol which is not bound to any value. -
❌
function g(x?: number) { assert(typeof x === 'void'); }
x
is aunprovided symbol.
example 5:
-
❌
function h(y: undefined | number) { }; h();
y
is a formal parameter symbol which can be bound to a number value or anundefined value. -
✔️
function h(y: void | number) { }; h()
y
is a formal parameter symbol which can be bound to a number value or an unprovided value. -
✔️
function h(y?: number) { }; h()
y
is a formal parameter symbol which can be bound to a number value or an unprovided value.