Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method not compatible with trait with confusing error message

Tags:

rust

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?

like image 939
Erik Avatar asked Dec 05 '16 18:12

Erik


1 Answers

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 for Miner wasn't compatible because the trait Agent didn't have a lifetime associated with it. So when it was trying to compile new in the implementation, it found that it had a lifetime from the Agent::new but a 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.

like image 74
Shepmaster Avatar answered Nov 04 '22 04:11

Shepmaster