1

tl;dr blocked by "argument requires that `x` is borrowed for `'y`"; how can I coerce variable x to lifetime 'y?

Given the following code, I'm blocked when trying to create the Box pointer pointing to a reference. I know the referenced object instance lives long enough. However, the rust compiler is concerned it does not.
How do I tell the rust compiler that Box::new(&thing) is valid for the lifetime of the containing struct instance?

Code example

This code is essentially:

  1. Things (a Vec), holding multiple Things
  2. counter of Things (a HashMap), holding multiple Box pointers to different &Thing as keys, a u64 count as values

(My best attempt at) a minimal example (rust playground):

use std::collections::HashMap;

type Thing<'a> = (&'a str, usize);
type Things<'a> = Vec<Thing<'a>>;

type ThingsCounterKey<'a> = Box<&'a Thing<'a>>;
type ThingsCounter<'a> = HashMap<ThingsCounterKey<'a>, u64>;

pub struct Struct1<'struct1> {
    things1: Things<'struct1>,
    thingscounter1: ThingsCounter<'struct1>,
}

impl<'struct1> Struct1<'struct1> {
    pub fn new() -> Struct1<'struct1> {
        Struct1{
            things1: Things::new(),
            thingscounter1: ThingsCounter::new(),
        }
    }
    fn things_update(&mut self, thing_: Thing<'struct1>) {
        self.things1.push(thing_);
        let counter = self.thingscounter1.entry(
            ThingsCounterKey::new(&thing_)
        ).or_insert(0);
        *counter += 1;
    }
}

fn main() {
    let mut s1 = Struct1::new();
    for (x, y) in [("a", 1 as usize), ("b", 2 as usize)] {
        s1.things_update((x, y));
    }
}

This results in compiler error:

error[E0597]: `thing_` does not live long enough
  --> src/main.rs:24:35
   |
14 | impl<'struct1> Struct1<'struct1> {
   |      -------- lifetime `'struct1` defined here
...
24 |             ThingsCounterKey::new(&thing_)
   |             ----------------------^^^^^^^-
   |             |                     |
   |             |                     borrowed value does not live long enough
   |             argument requires that `thing_` is borrowed for `'struct1`
...
27 |     }
   |     - `thing_` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error

How can I tell the rust compiler "&thing_ has the lifetime of 'struct1"?



Similar questions

Before this Question is marked as duplicate, these questions are similar but not quite the same. These Questions address 'static lifetimes or other slightly different scenarios.

JamesThomasMoon
  • 6,169
  • 7
  • 37
  • 63

2 Answers2

2

thing_ is passed by value. Its lifetime ends at the end of things_update. That means, the reference to thing_ becomes invalid after the end of the function but still be held by the struct, which is certainly should be rejected.

QuarticCat
  • 1,314
  • 6
  • 20
  • Thanks @QuarticCat , I thought the parameter declaration `thing_: Thing<'struct1>` would mean `thing_` lives as long as the `Struct1` instance. So I was surprised the rust compiler regards `thing_` as invalid when `things_update` finishes. – JamesThomasMoon Apr 07 '22 at 00:16
  • 2
    `thing_: Thing<'struct1>` means `thing_` holds a reference to a string and this reference has a lifetime `'struct1`. It doesn't specify the lifetime of itself. Here is a [blog](https://github.com/pretzelhammer/rust-blog/blob/master/posts/common-rust-lifetime-misconceptions.md) that may help you understand lifetime better. – QuarticCat Apr 07 '22 at 00:22
1

Like many times, actually the compiler is right and you were wrong. The compiler prevented you from use-after-free.

thing_ doesn't live long enough: you pushed a copy of it into the vector. It was clear if you'd used a type that isn't Copy:

type Thing<'a> = (&'a str, String);
// ...

Besides the previous error, now you also get:

error[E0382]: borrow of moved value: `thing_`
  --> src/main.rs:24:35
   |
21 |     fn things_update(&mut self, thing_: Thing<'struct1>) {
   |                                 ------ move occurs because `thing_` has type `(&str, String)`, which does not implement the `Copy` trait
22 |         self.things1.push(thing_);
   |                           ------ value moved here
23 |         let counter = self.thingscounter1.entry(
24 |             ThingsCounterKey::new(&thing_)
   |                                   ^^^^^^^ value borrowed here after move

Playground.

In other case, you would have to first push the item into the vector and then retrieve a reference to the pushed item there, like:

self.things1.push(thing_);
let counter = self.thingscounter1.entry(
    ThingsCounterKey::new(self.things1.last().unwrap())
).or_insert(0);

But in this case it will not work, and you'll get a long "cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements" error. This is because you're essentially trying to do the impossible: create a self-referential struct. See Why can't I store a value and a reference to that value in the same struct? for more about this problem and how to solve it.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77