Trouble understanding "dropped while still borrowed" issue

I'm having a hard time understanding this lifetime issue; This is a simplified version of what I'm working on. The compiler says that cave is being dropped while still borrowed, but this is confusing - where is cave still being borrowed? After each call to has_echo(), shouldn't the borrows be dropped?

pub trait Echo {
    fn id(&self) -> i32;
}

#[derive(Debug, Default)]
struct RealEcho {
    id: i32,
}

impl Echo for RealEcho {
    fn id(&self) -> i32 {
        self.id
    }
}

pub trait EchoManager<'e, T: Echo + 'e> {
    fn echo(&'e self, id: i32) -> Option<&T>;
}

struct RealEchoManager {
    echos: Vec<RealEcho>,
}

impl<'e> EchoManager<'e, RealEcho> for RealEchoManager {
    fn echo(&'e self, id: i32) -> Option<&RealEcho> {
        self.echos.iter().find(|e| e.id() == id)
    }
}

struct Cave<'a, T: Echo> {
    echo_manager: Box<dyn EchoManager<'a, T>>,
}

impl<'a, T: Echo> Cave<'a, T> {
    pub fn new(echo_manager: Box<dyn EchoManager<'a, T>>) -> Self {
        Self { echo_manager }
    }

    pub fn has_echo(&'a self, id: i32) -> bool {
        self.echo_manager.echo(id).is_some()
    }
}

fn main() {
    let echos = vec![RealEcho { id: 1 }, RealEcho { id: 2 }];
    let manager = RealEchoManager { echos };
    let cave = Cave::new(Box::new(manager));

    let has_echo = cave.has_echo(1);
    assert_eq!(has_echo, true);

    let has_echo = cave.has_echo(2);
    assert_eq!(has_echo, true);

    let has_echo = cave.has_echo(3);
    assert_eq!(has_echo, false)
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0597]: `cave` does not live long enough
  --> src/main.rs:49:20
   |
47 |     let cave = Cave::new(Box::new(manager));
   |         ---- binding `cave` declared here
48 |
49 |     let has_echo = cave.has_echo(1);
   |                    ^^^^ borrowed value does not live long enough
...
57 | }
   | -
   | |
   | `cave` dropped here while still borrowed
   | borrow might be used here, when `cave` is dropped and runs the destructor for type `Cave<'_, RealEcho>`

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

I believe the issue arises in the fact that dyn Trait<T> is invariant in T. I think you create something like the &'a mut T<'a> antipattern when you borrow from dyn EchoManager<'a> in Cave::has_echo, the echo you are borrowing living for as long as dyn EchoManager does (because dyn EchoManager<'a> is invariant in 'a), which happens to be as long as the Cave instance lives since the echo manager is an owned field of the Cave. Eliding the lifetime in EchoManager makes the invariance go away (by getting rid of the invariant lifetime parameter), if that helps with your real code. Or maybe instead of using a trait object you can just use a generic parameter?

Probably the least intrusive change you could make would be to get rid of the 'e lifetime in &'e self in the EchoManager::echo signature. This avoids the &'e Box<dyn EchoManager<'e>> construct that results in the forever-borrow.

3 Likes

If you don't think you can get rid of the lifetime parameter in the trait, a higher-ranked type may help instead.

(Or there may be deeper problems, hard to say from the reduced example.)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.