Step 1 of 6
Generic functions
Say you need the last element of a list of scores, then of a list of names, then of a list of prices. Without generics you'd write three functions with identical bodies:
fn last_i32(items: &[i32]) -> Option<&i32> { ... }
fn last_str(items: &[&str]) -> Option<&&str> { ... }
fn last_f64(items: &[f64]) -> Option<&f64> { ... }
Generics let you write it once, with a placeholder for the type:
fn last<T>(items: &[T]) -> Option<&T> {
if items.is_empty() {
None
} else {
Some(&items[items.len() - 1])
}
}
fn swap<A, B>(pair: (A, B)) -> (B, A) {
(pair.1, pair.0)
}
fn main() {
let scores = [88, 92, 75];
let names = vec!["ana", "bo"];
let empty: [f64; 0] = [];
println!("{:?}", last(&scores));
println!("{:?}", last(&names));
println!("{:?}", last(&empty));
println!("{:?}", swap(("crab", 8)));
}
Some(75)
Some("bo")
None
(8, "crab")
Piece by piece:
<T>right after the function name declares a type parameter calledT. Inside the function,Tis used like any type:&[T]is "a slice of T",Option<&T>"maybe a reference to a T".- At each call, the compiler works out what
Tis from the arguments:i32forscores,&strfornames,f64forempty. You don't write it. - A function can have several type parameters, separated by commas. In
swap<A, B>, the two halves of the pair can be different types. lastworks for anyTbecause it never does anything with the values except hand out references. It doesn't compare them, add them or print them. The next step shows what happens when you need to.
You've been using generic code all along: Vec<T>, Option<T>, Result<T, E> and HashMap<K, V> are generic types, and slice methods like .last() and .first() are generic functions.
Naming: type parameters are usually single capital letters: T for "type", U for a second one, K/V for keys and values, E for errors. A descriptive name like Item is fine when it reads better.
Common mistakes
- Forgetting to declare the parameter. Using
Twithout<T>after the function name:
fn last(items: &[T]) -> Option<&T> {
items.last()
}
fn main() {
println!("{:?}", last(&[1, 2]));
}
error[E0425]: cannot find type `T` in this scope
| fn last(items: &[T]) -> Option<&T> {
| ^ not found in this scope
help: you might be missing a type parameter
- One parameter for two different types. In
fn pair<T>(a: T, b: T), both arguments are the sameT, sopair(1, "two")ismismatched types: expected integer, found &str. If they may differ, use two parameters:fn pair<A, B>(a: A, b: B). - Writing a generic function too early. Generic code is a little harder to read. If you only ever call a function with
i32, write it fori32. Make it generic when a second real use shows up.
Your task
Fill in the blanks so middle works for slices of any type. It returns the element in the middle (at index len / 2), or None for an empty slice. The program prints:
Some(3)
Some("green")
Some('y')
None
Challenge 2
Write fn last_two<T>(items: &[T]) -> Option<(&T, &T)> that returns references to the last two elements, in order, or None if there are fewer than two. For [1, 2, 3] it returns Some((&2, &3)).
Challenge 3
Write a generic function from scratch: reverse_in_place, which takes a mutable slice of any type and reverses it in place. Use items.swap(i, j), which swaps two elements of a slice, and don't call .reverse(). It must work for numbers and for Strings, and do nothing for an empty slice.