strblr
strblr

Reputation: 960

How to give a default value to a generic parameter?

I have the following function :

function demo<T>(init: T) {
    return init;
}

I would like to give init the default value {} when omitted. So I wrote :

function demo2<T>(init: T = {}) {
    return init;
}

But of course, this gives me the error :

Type '{}' is not assignable to type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to '{}'.(2322)

Link to playground.

How could I force T to be {} when the parameter is omitted ? Thank you.

Upvotes: 1

Views: 466

Answers (1)

jcalz
jcalz

Reputation: 330086

The error is technically correct; the callers of your demo2() function is allowed to specify the type parameter T to be whatever they want it to be. This means a call like the following will not result in a compiler error, yet will easily lead to a runtime error:

const z2 = demo2<{ a: number }>(); // uh oh, no error 
z2.a.toFixed(); // no error at compile time but "z2.a is undefined" at runtime

Still, if you think the likelihood of someone doing that (manually specifying a generic type parameter) is low, you can use a type assertion to suppress the error, while at the same time using a generic parameter default so that the compiler will infer {} for T when you leave out the init property:

// assertion with default type parameter
function demo3<T = {}>(init: T = {} as T) {
    return init;
}

This gives you the following desirable behavior:

const x3 = demo3(); // {}
const y3 = demo3({ a: 123 }); // {a: number}

while still allowing the following undesirable behavior:

const z3 = demo3<{ a: number }>(); // no compiler error
z3.a.toFixed(); // RUNTIME ERROR!

If you want to prohibit manually specifying T without passing in a value of type T, you might consider using something like an overloaded function with multiple call signatures:

// overload

function demo4(): {};
function demo4<T>(init: T): T;
function demo4(init = {}) {
    return init;
}

Here, if a caller leaves out the init parameter, the function is no longer treated as generic; the return type is just {}. On the other hand, if the caller provides an init parameter, then the function is treated just like your original demo() function. The implementation of the function works for either call signature.

This also results in the desirable behavior for "normal" calls:

const x4 = demo4(); // {}
const y4 = demo4({ a: 123 }); // {a: number}

And also gives a compiler warning if someone tries to call it the wrong way:

const z4 = demo4<{ a: number }>(); // compiler error! an argument for init is required

Playground link to code

Upvotes: 2

Related Questions