Typescript Function with Generic Return Type
type FuncGenericReturn = <T>() => T;
const funcReturnsNumber: FuncGenericReturn = (): number => 1;
(Sandbox)
Getting this error:
Type 'number' is not assignable to type 'T'. 'number' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.(2322) input.ts(1, 26): The expected type comes from the return type of this signature.
I would expect typescript to automatically infer T as number and just use it. Why is it complaining? What is the proper way to write something like this? Thanks.
It's important to pay attention to where the generic type parameters are declared and what scope they have. The type
type FuncGenericReturn = <T>() => T;
is a concrete type referring to a generic function. <T>() => T
means: "a function whose caller specifies a type T
and which returns a value of type T
." This is essentially impossible to implement safely. Imagine if you had such a function:
declare const funcGenericReturn: FuncGenericReturn;
Then you should be able to call it this way:
const someNumber: number = funcGenericReturn<number>();
const someString: string = funcGenericReturn<string>();
But of course at runtime those will both compile to
const someNumber = funcGenericReturn();
const someString = funcGenericReturn();
Meaning that funcGenericReturn()
would just have to "know" at runtime that it should first return a number
and then a string
, based on type information which is erased before the JavaScript is generated. So properly implementing a FuncGenericReturn
would require magical foreknowledge.
To reiterate: when you have a generic function, the generic type parameters are specified by the caller, not by the implementer. It's true that sometimes the compiler will infer these type parameters so that the person writing the code doesn't have to spell it out, but again, these inferences are happening at call time. Two different calls to the same generic function could end up having two different choices for the type parameters.
Let's compare this to a different but related type definition:
type FuncConcreteReturn<T> = () => T;
Here, FuncConcreteReturn
is a generic type referring to a concrete function. It would be more accurate to say that FuncConcreteReturn
is not really a type; it's more like a type operator which takes an input type T
and produces an output type () => T
.
For any particular type T
, the type FuncConcreteReturn<T>
is a concrete function type which takes no parameter and returns a value of type T
. So a FuncConcreteReturn<string>
is a function that takes no arguments and returns a string
, while a FuncConcreteReturn<number>
is a function that takes no arguments and returns a number
. Note that FuncConcreteReturn<string>
is a different type from FuncContreteReturn<number>
, and neither of them are a FuncConcreteReturn
because that's not a valid type. So the following is valid:
const funcReturnsNumber: FuncConcreteReturn<number> = () => 1;
const funcReturnsString: FuncConcreteReturn<string> = () => "";
Again, funcReturnsNumber
is not a generic function. It is a concrete function that always returns a number. And FuncConcreteReturn<T>
is a generic type, where the value of T
is chosen when the type is written out. Since these types are function types, the type T
is chosen by the implementer of these functions, and not by the caller.
By the way, the relationship between a generic function type like
type G = <T, U>(t: T, u: U) => [T, U]
and a generic type like
type H<T, U> = (t: T, u: U) => [T, U]
is that any instance of the former will be an instance of the latter, but not vice versa. This means that if you did have a FuncGenericReturn
, you could assign it to a value of type FuncConcreteReturn<string>
or a FuncConcreteReturn<number>
:
const fn: FuncConcreteReturn<number> = funcGenericReturn; // okay
const fs: FuncConcreteReturn<string> = funcGenericReturn; // okay
Or, for the G
and H
types above, you could do this:
const g: G = <T, U>(t: T, u: U) => [t, u];
g("a", 1); // okay
g(1, "a"); // okay
const h1: H<string, number> = g; // okay
h1("a", 1); // okay
h1(1, "a"); // error
const h2: H<number, string> = g; // okay
h2(1, "a"); // okay
h2("a", 1); // error
Okay, I hope that gives you some understanding on the difference between generic functions and generic types. Good luck!
Playground link to code
Isn't this syntax working for you?
type FuncGenericReturn<T> = () => T;
const funcReturnsNumber: FuncGenericReturn<number> = () => 1;