2.18 Generics

There are times when we wish to create a function that has one or more arguments that are able to be of any type: such times, we make use of generics. Generics gives us the possibility to have arguments of either a function or data type that can be of any type, instead of for instance being required to be of type i32.

Functions

In order to use generics for functions, so that we may have an argument that takes any type, we first need to specify a generic type using <>:

fn useless_return<T>

This tells the function we are currently creating, that there is a generic type associated with the function. Let us finish this function off:

fn useless_return<T>(thing: T) -> T
{
	thing
}

This function takes one argument thing which is of type T. The type named T is a type we defined inside the <>. The function returns a type that is the same as the type we used for thing. In this case, all this function does is return the same value we provided it with. Upon calling it, we have the freedom to type in any type we feel like entering:

println!("{}", useless_return(2))

The 2 we inputted could just as well has been a string containing some interesting information.

Multiple generic types

It is also possible to provide the function, or any structure for that matter, with multiple generic types:

fn useful_return<T, P>(thing: T, another_thing: P) -> (T, P)
{
	(thing, another_thing)
}

This function that is clearly more useful than the last one (judged by its cover) takes two arguments of two different types and returns a tuple containing both of them. The arguments could be of the same type, as both T and P can be of absolutely any type.

There is no limit to how many different generic types a function or structure can contain, it is however convention to name the first of defined types T (for type).

Enums

It works just as well using generics for enums:

enum Person<T>
{
	Named(T), 
	Unnamed
}

This person can either be named or unnamed, but upon having a name, his name can be of any type: meaning any Person may have their name set to a number, and who would not want to be named after a number? Imagine having a number from the Fibonacci Sequence. People would say ”Hello, 21!”

Other than the generic notation, this enum works just the same way as any enum: with the amazing feature of being able to take on any type for its Named alternative.

Structs

Quite obviously, generics work great for structs:

struct Fun<T>
{
	sun: T, 
	stun: T
}

When initializing this struct, all we need to make sure is that the two fields contained by the struct are set to the same type:

let fun = Fun { sun: 89.2, stun: 98.1 };

Just like in any other place generics may be used, we can use multiple generic types:

struct Fun<T, P>
{
	sun: T, 
	stun: P
}

With that, we may initialize sun and stun to be different types.

Exercises

  • Create a struct where all the fields use different generic types, then print everything out in a huge and surprising message.
  • Get bored at how pointless generics are for small-scale projects and move on to the next section.

Conclusion

With generics you can accomplish all kinds of things. On occasion it is nice being able to create functions or structures where the values are able to differ.

Enums
Traits