Generics are a different way to reuse code for different types.
We can use interfaces for this, but interfaces have some limitations.
We often want to reuse a function for multiple types. On the interfaces page, we used an interface to allow the function to operate on any type that conformed to a certain contract.
A function that takes an interface doesn't have to care about the original type of the substruct we pass in.
However, that can also be its downside: it forgets the original type of the substruct we pass in. This can be mighty inconvenient, like in this example.
Notice how main is giving two Firefly to longerShip, so we know that longerShip should return a Firefly.
main doesn't know that though; all is sees is that longerShip returns an &ISpaceship.
Because of this, we can't call crazyIvan on the longer ship!
In future versions, this can be shortened to just fn length(this) int;.
We can make longerShip into a generic function.
Its parameters aren't exactly ISpaceship, they are any type T that implements ISpaceship.
Since the parameters and return type are all T, the compiler knows that if we hand in Fireflys, we return a Firefly.
In this case, we're calling it like longerShip<Firefly>(...), which specifies that we should use the version of longerShip where T is a Firefly.
longerShip<T> returns a T, so main knows that longerShip<Firefly> returns a Firefly.
In future versions, this can be <T impl ISpaceship> which will give a better compile error if someone calls longerShip with a non-ISpaceship.
We can actually leave off the <Firefly> here, the compiler can figure it out from the arguments we passed in.
We can make generic structs too.
Here we're making a Flock struct that can hold multiple of the same ship.