Let's go through one more example to show how we can link the input and output types of a function thanks to generics.
Suppose that we want to write a function that accepts different types as input and returns the same types as output. Without generics, we would need to rely on any, which would mean losing valuable type information. With generics, we can define the function as follows:
function log<T>(arg: T): T { console.log("Log entry: ", arg); return arg; } class Person { constructor(private name: string){} } const person: Person = log(new Person("foo"));
In this case, the log function's return type will always match the argument type.
Of course, you can also use generics when defining interfaces:
interface Logger<T> { debug(arg: T): T info(arg: T): T warn(arg: T): T error(arg: T): T fatal(arg: T): T } interface BasicLogger { log<T>(arg: T): T }
In the preceding code, you can see two ways of achieving the same result. The first form, shown in the Logger interface, is preferred if the generic argument can be reused for multiple elements of the definition (as illustrated in the example). The second form, in the BasicLogger interface, is only relevant if there's only a single place where a generic type needs to be used.