Why does TypeScript have both `void` and `undefined`?

TypescriptUndefinedVoid

Typescript 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.

Again, void is not an alias for undefined and an expression of type void may have any value, not just undefined

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 an undefined 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 an undefined 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 an undefined value.

example 4:

  • function g(x?: number) { assert(x === undefined); }

    x is a symbol which is bound to an undefined 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 a unprovided symbol.

example 5:

  • function h(y: undefined | number) { }; h();

    y is a formal parameter symbol which can be bound to a number value or an undefined 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRyan CavanaughView Question on Stackoverflow
Solution 1 - TypescriptRyan CavanaughView Answer on Stackoverflow
Solution 2 - TypescriptZimView Answer on Stackoverflow