Home > Mobile >  What is the difference between Typescript Generic <T extends unknown[]> and <T>?
What is the difference between Typescript Generic <T extends unknown[]> and <T>?

Time:07-22

I am new to TS and am learning it by reading Programming Typescript book by O Reilly. In the book, the author is implementing his own version of JS's built-in call function.

function call<T extends unknown[], R>(
  f: (...args: T) => R,
  ...args: T 
  ): R {
     return f(...args)
}

function fill(length: number, value: string): string[] { 
  return Array.from({length}, () => value)
}

call(fill, 10, 'a') // evaluates to an array of 10 'a's

One thing I am really struggling with is why is unknown used here? Why can't we do it like this?

function call<T, R>(
  f: (...args: T[]) => R,
  ...args: T[] 
  ): R {
     return f(...args)
}

Of course, this version errors out but why?

Thanks

CodePudding user response:

Because we might have better information.

function call<T, R>(
  f: (...args: T[]) => R,
  ...args: T[] 
  ): R

This demands that we have some type T for which the array takes zero or more T (in the form of an array) as arguments. If we have a function that takes a variable number of strings, that works great.

However, there are other types that extend unknown[] that aren't, in as many words, arrays. Namely, those are tuple types. If we want to call this with a function

function foobar(x: string, y: number)

Then your version of the function would require that T be string | number, which means I could call your function with [0, "A"], or [0, 0, 0, 0, 0], or [], or any number of other incorrect call signatures. However, the other function you proposed,

function call<T extends unknown[], R>(
  f: (...args: T) => R,
  ...args: T 
  ): R {
     return f(...args)
}

This will take T to be [string, number]. Every instance of [string, number] can be assigned to a variable of type unknown[], so the former is compatible with the latter. And now we can only call this function with a list whose size is known statically to be 2 and whose first element is a string and the second is a number.

CodePudding user response:

By writing T extends unknown[] you restrain T to have all the properties of an unknown[]. Two consequences:

  • The caller of the call function can't use it with a T that wouldn't satisfy this constraint
  • In the implementation, you can use a variable of type T like an unknown[] even if the actual type is not known (only the caller knows it)
  • Related