Even though something like Vec<T>
is mentioned earlier, the exact meaning of this syntax is properly explained only in chapter 10 of the book. Are you this far yet?
The relevant subchapter is 10.1. The first occurrence of <T>
on that page also comes with detailed explanations:
To parameterize the types in a new single function, we need to name the type parameter, just as we do for the value parameters to a function. You can use any identifier as a type parameter name. But we’ll use T
because, by convention, type parameter names in Rust are short, often just a letter, and Rust’s type-naming convention is CamelCase. Short for “type,” T
is the default choice of most Rust programmers.
When we use a parameter in the body of the function, we have to declare the parameter name in the signature so the compiler knows what that name means. Similarly, when we use a type parameter name in a function signature, we have to declare the type parameter name before we use it. To define the generic largest
function, place type name declarations inside angle brackets, <>
, between the name of the function and the parameter list, like this:
fn largest<T>(list: &[T]) -> &T {
We read this definition as: the function largest
is generic over some type T
. This function has one parameter named list
, which is a slice of values of type T
. The largest
function will return a reference to a value of the same type T
.
A similar function signature is fn foo<T>(list: Vec<T>)
which has multiple <T>
s even, and further down the page there is the example
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
which shows generics in structs and impl blocks. The meaning of <T>
varies on context. Either it introduces a generic type parameter (as in fn largest<T>
, struct Point<T>
or impl<T>
), or it applies it (as in Vec<T>
in the fn foo
function signature above, or Point<T>
in the impl
block above).
As mentioned in the book, the name T
is arbitrary and you could rename it; usually names starting with capital letters (in “CamelCase”, as the except from the book, too, explains above) are used, otherwise the compiler will warn you. So just like
let t = 42;
f(t);
is the same as
let foo = 42;
f(foo);
we can rewrite
fn largest<T>(list: &[T]) -> &T {
…
}
just as well as
fn largest<Foo>(list: &[Foo]) -> &Foo {
…
}