I am studying the Traits and the Trait Object in Rust. In the Trait chapter, I solved the 6th exercise differently than the compiler suggestion. The following code defines two structs (Sheep
and Cow
) and a trait (Animal
). Both Sheep
and Cow
implement the Animal
trait by providing their own noise
method implementations.
There are two functions, the random_animal_if
and the random_animal_match
that take an argument and return a reference to a dynamic trait object.
struct Sheep {}
struct Cow {}
trait Animal {
fn noise(&self) -> String;
}
impl Animal for Sheep {
fn noise(&self) -> String {
"baaaaah!".to_string()
}
}
impl Animal for Cow {
fn noise(&self) -> String {
"moooooo!".to_string()
}
}
fn random_animal_if(random_number: f64) -> &'static dyn Animal {
if random_number < 10.0 {
&Sheep {}
} else if random_number > 20.0 {
&Cow {}
} else {
panic!()
}
}
fn random_animal_match(random_string: &str) -> &dyn Animal {
match random_string {
"sheep" => &Sheep {},
"cow" => &Cow {},
_ => panic!(),
}
}
fn main() {
let animal = random_animal_if(21.0);
println!("Randomly animal says {}", animal.noise());
let animal = random_animal_match("sheep");
println!("Randomly animal says {}", animal.noise());
}
Both function creates and returns either a Sheep
or Cow
object based on the input. One of them uses conditionals on a floating number input. The other uses pattern matching on a given string slice. The logic is identical, but if I omit the &'static
lifetime specification at random_animal_if
return type then the compiler throws this error:
error[E0106]: missing lifetime specifier
Interestingly, if the input parameter type is changed from f64
to &str
then the static lifetime annotation can be removed. Why? What is the difference between the two types?
This is due to Rust's lifetime elision rules.
If you have a function that takes a reference as an argument and returns a reference, then the compiler infers that the output borrows from the input, which looks like this:
fn random_animal_match<'a>(random_str: &'a str) -> &'a dyn Animal {
In fact, the only way for a function to return a non-static reference is if the returned data is borrowed from an argument.
However, in your code, the body of random_animal_match
does not borrow the return value from the argument. The compiler still infers the elided lifetimes as if that is the case but in fact the lifetime in the return type is always 'static
. This means that the lifetime in your function's return type is overly restrictive. A caller of the function will get compiler errors if they try to use the returned &dyn Animal
after the input &str
is dropped, even though this shouldn't actually be a problem:
fn main() {
let animal = {
let sheep = String::from("sheep");
random_animal_match(&sheep)
}; // - `sheep` dropped here while still borrowed
// `sheep` does not live long enough
println!("Randomly animal says {}", animal.noise());
}
To maximise the flexibility of this function, you should make the lifetime in the return type 'static
:
fn random_animal_match(random_str: &str) -> &'static dyn Animal {