trait Media {
fn title(&self) -> &str;
fn duration(&self) -> std::time::Duration;
}Traits can be used as types of function arguments, using the impl
keyword. This keyword means that the concrete type will be decided
at compile time. Since multiple concrete types can be used with
this function as long as they implement the required trait, the
compiler will generate a function for each concrete type used:
fn info_media(f: &impl Media) {
println!("Movie \"{}\" lasts {} minutes.", f.title(),
f.duration().as_secs() / 60);
}fn info_media<T: Media>(f: &T) {
println!("Movie \"{}\" lasts {} minutes.", f.title(),
f.duration().as_secs() / 60);
}fn compare_medias(f1: &impl Media, f2: &impl Media) {
let (longest, shortest) = if f1.duration() > f2.duration() {(f1, f2)}
else {(f2, f1)};
println!("Movie \"{}\" lasts longer than movie \"{}\".",
longest.title(), shortest.title());
}Using the generic notation, we can express the fact that f1 and
f2 must have the same concrete type that implements the Media
trait:
fn compare_medias<T: Media>(f1: &T, f2: &T) {
let (longest, shortest) = if f1.duration() > f2.duration() {(f1, f2)}
else {(f2, f1)};
println!("Movie \"{}\" lasts longer than movie \"{}\".",
longest.title(), shortest.title());
}We need a Movie structure to test our trait:
struct Movie<'a> {
title: &'a str,
duration: std::time::Duration,
}With the trait implementation:
impl<'a> Media for Movie<'a> {
fn title(&self) -> &'a str {
self.title
}
fn duration(&self) -> std::time::Duration {
self.duration.clone()
}
}We define two movies:
let movie1 = Movie {
title: "Despicable Me",
duration: std::time::Duration::from_mins(95),
};
let movie2 = Movie {
title: "Despicable Me 2",
duration: std::time::Duration::from_mins(98),
};The two functions are tested using the two movies:
info_media(&movie2);
compare_medias(&movie1, &movie2);Movie "Despicable Me 2" lasts 98 minutes.
Movie "Despicable Me 2" lasts longer than movie "Despicable Me".
32.3.1Multiple traits¶
It is possible to have multiple trait bounds on the same argument,
using the + sign to link them.
Let us first define another trait for playing a video:
trait Video {
fn play_on_screen(&self);
}We implement this new trait for the Movie structure:
impl<'a> Video for Movie<'a> {
fn play_on_screen(&self) {
println!("Playing video {}...", self.title);
}
}We define now a function to play a video that takes an item that
implements the two traits Media and Video:
fn play_video(x: &(impl Media + Video)) {
println!("Video: {}, length: {} min",
x.title(), x.duration().as_secs() / 60);
x.play_on_screen();
}This function could also be defined directly as a generic:
fn play_video<T: Media + Video>(x: &T) {
println!("Video: {}, length: {} min",
x.title(), x.duration().as_secs() / 60);
x.play_on_screen();
}Here is the invocation of the function with a movie instance:
play_video(&movie1);Video: Despicable Me, length: 95 min
Playing video Despicable Me...
32.3.2Returning a trait¶
Returning a trait is also possible. Here is the impl syntax with
two traits bound:
fn get_video() -> impl Media + Video {
Movie {
title: "Despicable Me 3",
duration: std::time::Duration::from_mins(90),
}
}{
let movie3 = get_video();
info_media(&movie3);
}Movie "Despicable Me 3" lasts 90 minutes.
()For instance, we define a trait to build an object from a title and a duration:
trait FromTitle<'a> {
fn from_title(title: &'a str, duration: std::time::Duration) -> Self;
}Then we implement it for Movie:
impl<'a> FromTitle<'a> for Movie<'a> {
fn from_title(title: &'a str, duration: std::time::Duration) -> Self {
Movie { title, duration }
}
}We now add the FromTitle as a bound and use its constructor
method:
fn get_video_bis<'a, T: Media + Video + FromTitle<'a>>() -> T {
T::from_title("Despicable Me 3", std::time::Duration::from_mins(90))
}To use it we need now to specify the wanted type (here Movie):
let movie3: Movie = get_video_bis();
info_media(&movie3);Movie "Despicable Me 3" lasts 90 minutes.
32.3.3Where clause¶
The where clause allows for clearer declarations. For instance, in
the following function we want to merge two different video files,
with possibly different types. We express the trait bounds inside the
where clause, making the function header clearer:
fn merge_videos<'a, T, U>(t: &T, u: &U) -> T
where
T: Media + Video + FromTitle<'a>,
U: Media + Video,
{
println!("Merging \"{}\" with \"{}\".", t.title(), u.title());
// Merging T with U...
T::from_title("Merged movies", t.duration() + u.duration())
}Here is the execution:
let merged = merge_videos(&movie1, &movie2);
info_media(&merged);Merging "Despicable Me" with "Despicable Me 2".
Movie "Merged movies" lasts 193 minutes.
32.3.4Bounding a generic method¶
Trait bounds can also be used to specialize partially a generic type, and implement methods only for this specialization.
For instance given the following generic structure:
struct Bag<T> {
items: Vec<T>,
}We may define the following method that will exist only for
primitive integers. This example requires the external num_traits
crate, which is not available in this notebook’s kernel, so it is
shown but not executed:
use num_traits::PrimInt;
impl<T: PrimInt> Bag<T> {
fn sum(&self) -> T {
let mut s: T = T::zero();
for n in &self.items {
s = s + *n;
}
s
}
}If we define the following bag of integers:
let bag = Bag { items: vec![1, 20, -4, 5] };Couldn't automatically determine type of variable `bag`.
Please give it an explicit type.We would then be able to call the sum() method:
bag.sum()