Struggle with Rust compiler —lifetime of dyn 1 Link to heading
Let’s talk about the dyn and impl keywords and 'static lifetimes!
The dyn keyword is used to denote an object of a some type that implements a given trait. This is the essence of polymorphism — we don’t need to know the concrete type at compile time; all we need to know is that the type implements the trait. When we invoke a method associated with the trait, it will be dynamically dispatched. Since the concrete object is unknown, the compiler can’t directly pass it around because its size is not known. Instead, it needs to pass around a pointer or a reference to the object. This is why the dyn keyword is typically used along with a smart pointer, as in Box<dyn Trait>.
On the other hand, the impl keyword denotes that the concrete type is known at compile time. This is essentially a generic type and is equivalent to a template in C++. The compiler knows exactly which type it is and can directly pass around the object. If we call any method associated with the trait, it will be statically dispatched. However, since the concrete type is fixed, we cannot replace the object with a different object at runtime.
Consider the following code (link):
trait Weapon {
fn fire(&self);
}
struct M16Rifle;
impl Weapon for M16Rifle {
fn fire(&self) {
println!("dut dut");
}
}
struct Bazuka;
impl Weapon for Bazuka {
fn fire(&self) {
println!("Kablam!!");
}
}
struct Player {
weapon: Box<dyn Weapon>,
}
impl Player {
fn new(weapon: impl Weapon) -> Self {
Self { weapon: Box::new(weapon) }
}
fn change_weapon(&mut self, weapon: impl Weapon) {
self.weapon = Box::new(weapon);
}
fn shoot(&self) {
self.weapon.fire();
}
}
fn main() {
let mut player = Player::new(M16Rifle);
player.shoot(); // dut dut
player.change_weapon(Bazuka);
player.shoot(); // Kablam!!
}
The Player object holds some object implementing Weapon using a Box<dyn Weapon>. The player’s weapon can be swapped to a different type at runtime, for example, from M16Rifle to Bazuka. The code looks fine, but the Rust compiler outputs an error:
error[E0310]: the parameter type `<impl Weapon>` may not live long enough
--> src/bin/dyn_lifetime.rs:25:24
|
25 | Self { weapon: Box::new(weapon) }
| ^^^^^^^^^^^^^^^^ ...so that the type `<impl Weapon>` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound...
|
24 | fn new(weapon: impl Weapon + 'static) -> Self {
| +++++++++
Why is the compiler asking to add a 'static lifetime specifier? This may seem scary because you don’t want your weapon objects to have a 'static lifetime. Memory occupied by 'static lifetime objects won’t be freed until the end of the program, potentially causing memory leaks, right?
Well, actually, that’s not what the compiler is suggesting. Specifying the 'static lifetime as the compiler suggests does not make the weapon suddenly become statically allocated.
- fn new(weapon: impl Weapon) -> Self {
+ fn new(weapon: impl Weapon + 'static) -> Self {
- fn change_weapon(&mut self, weapon: impl Weapon) {
+ fn change_weapon(&mut self, weapon: impl Weapon + 'static) {
Rather, adding the 'static specifier in the method signature is saying that the weapon object type must meet one of the following requirements:
- It has no variable associated with a lifetime.
- It has a variable associated with a lifetime, but that lifetime is
'static.
In our case, neither the M16Rifle nor Bazuka structs have any variables associated with a lifetime, so they satisfy requirement 1. These objects will be dropped normally when they go out of scope. In fact, when you write Box<dyn Weapon>, it implicitly adds the 'static specifier — Box<dyn Weapon + 'static>— so this 'static lifetime specifier should not scare you.
So, when does requirement 2 come into play? Let’s add a third type of weapon to our game:
+struct CustomWeapon<'a> {
+ sound: &'a str
+}
+
+impl<'a> CustomWeapon<'a> {
+ fn new(sound: &'a str) -> Self {
+ Self { sound }
+ }
+}
+
+impl<'a> Weapon for CustomWeapon<'a> {
+ fn fire(&self) {
+ println!("{}", self.sound)
+ }
+}
+
+ player.change_weapon(CustomWeapon::new("pew pew"));
+ player.shoot(); // pew pew
Our third weapon, CustomWeapon<'a>, takes a lifetime specifier 'a because it has a variable sound that is a reference to a str. When we create this weapon with CustomWeapon::new("pew pew"), the literal string we provide has a 'static lifetime. Therefore, this object satisfies requirement 2 and compiles just fine.
In conclusion, the use of the dyn and impl keywords in Rust allows for flexible and efficient code design. The dyn keyword enables polymorphism by allowing objects of different concrete types to be treated uniformly through trait implementation. On the other hand, the impl keyword provides static dispatch and precise type information at compile time, allowing direct object manipulation. Understanding the role of lifetimes in these scenarios is crucial for ensuring correct code behavior. While adding a 'static lifetime specifier may seem daunting, it does not imply static allocation but rather signifies specific requirements for the object’s lifetime. By leveraging these keywords and lifetimes effectively, Rust programmers can build powerful and maintainable codebases.
https://doc.rust-lang.org/stable/reference/lifetime-elision.html#default-trait-object-lifetimes