I am venturing to the world of lifetimes and structs that contain mutable structs:
enum Resources {
Food,
Wood,
Tools,
Ore,
Metal,
}
struct ResourceEntry {
resource: Resources,
amount: i32,
}
impl ResourceEntry {
fn new(resource: Resources, amount: i32) -> ResourceEntry {
ResourceEntry {
resource: resource,
amount: amount,
}
}
}
trait Agent {
fn new<'a>(&'a mut Vec<ResourceEntry>) -> Self;
}
struct Miner<'a> {
inventory: &'a mut Vec<ResourceEntry>,
}
impl<'a> Agent for Miner<'a> {
fn new(starting_resource: &'a mut Vec<ResourceEntry>) -> Miner {
Miner { inventory: starting_resource }
}
}
fn main() {
let mut resource = ResourceEntry::new(Resources::Food, 3);
let mut vec = vec![resource];
let miner: Miner = Miner::new(vec);
miner.perform();
}
I get the following error
error[E0308]: method not compatible with trait
--> other.rs:47:5
|
47 | fn new(starting_resource: &'a mut Vec<ResourceEntry>) -> Miner
| ^ lifetime mismatch
|
= note: expected type `fn(&'a mut std::vec::Vec<ResourceEntry>) -> Miner<'a>`
= note: found type `fn(&'a mut std::vec::Vec<ResourceEntry>) -> Miner<'a>`
note: the lifetime 'a as defined on the block at 48:4...
--> other.rs:48:5
|
48 | {
| ^
note: ...does not necessarily outlive the lifetime 'a as defined on the block at 48:4
--> other.rs:48:5
|
48 | {
| ^
help: consider using an explicit lifetime parameter as shown: fn new(starting_resource: &'a mut Vec<ResourceEntry>) -> Miner
--> other.rs:47:5
|
47 | fn new(starting_resource: &'a mut Vec<ResourceEntry>) -> Miner
| ^
I can't for the life of me wrap my head around what the compiler is telling me. The error messages are saying to do exactly what I am doing. Maybe I'm misunderstanding but it's saying that the lifetime of a
doesn't match the lifetime of a
? I thought I had a pretty good grasp of borrowing and ownership but the use of explicit lifetimes and objects that reference other objects has me confused.
Is the problem
fn new<'a>(&'a mut Vec) -> Self;
I had trouble with getting new to accept a lifetime correctly and I'm wondering if this isn't something you should do in Rust?
That's a pretty bad error message, and I'd encourage you to report it. If you change your generic lifetime parameter names...
trait Agent {
fn new<'a>(&'a mut Vec<ResourceEntry>) -> Self;
}
struct Miner<'b> {
inventory: &'b mut Vec<ResourceEntry>,
}
impl<'c> Agent for Miner<'c> {
fn new(starting_resource: &'c mut Vec<ResourceEntry>) -> Miner {
Miner { inventory: starting_resource }
}
}
you get a better error:
error[E0308]: method not compatible with trait
--> src/main.rs:32:5
|
32 | fn new(starting_resource: &'c mut Vec<ResourceEntry>) -> Miner {
| ^ lifetime mismatch
|
= note: expected type `fn(&'a mut std::vec::Vec<ResourceEntry>) -> Miner<'c>`
= note: found type `fn(&'c mut std::vec::Vec<ResourceEntry>) -> Miner<'c>`
Adding a lifetime to an impl
block is not a shorthand for specifying a lifetime on each function; they have different scopes. You can see that what you are trying to do doesn't make sense:
fn new<'a>(&'a mut Vec<ResourceEntry>) -> Self;
That lifetime isn't used anywhere in the output. Instead, you need to make the the lifetime play a role in the trait:
trait Agent<'a> {
fn new(&'a mut Vec<ResourceEntry>) -> Self;
}
impl<'c> Agent<'c> for Miner<'c> {
fn new(starting_resource: &'c mut Vec<ResourceEntry>) -> Miner<'c> {
Miner { inventory: starting_resource }
}
}
Just so I know exactly what happened, the implementation of
Agent
forMiner
wasn't compatible because the traitAgent
didn't have a lifetime associated with it. So when it was trying to compilenew
in the implementation, it found that it had a lifetime from theAgent::new
buta
was a random other lifetime it couldn't figure out since that lifetime wasn't in the output.
Kind of. It wasn't compatible because the implementation of new
didn't have a lifetime parameter (fn new<'x>
) while the trait definition did. Adding a lifetime to new
would have "solved" that problem, but would either not compile or not do what you want.
The lifetime at the trait level allows you to associated the lifetime in the impl block
The lifetime at the trait level means that the types that implement the trait can be parameterized with a lifetime. The trait also will know about that lifetime.
and your able to say that the agent will have the same lifetime as the miner?
I think you understand the concept, but I'll point out that this terminology is subtly wrong. The Miner
will be provided with a reference
with a concrete lifetime; this is not the same thing as the Miner
's lifetime! The implementation of Agent
for Miner
will be able to make use of the provided lifetime, but Agent
does not itself have a lifetime; it's just a trait.
This is a weakness of humans and how we talk about things fast and loose. A value's lifetime is when it comes into being until it is moved. In Rust, 'a
are lifetime annotations / generic lifetime parameters, and these allow a value to contain references. The concrete lifetime will replace the parameter when the value is constructed with a reference.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With