Value dropped while still borrowed

Hello,

I've been working on a project that takes input over a socket connection (set up using socketioxide and axum) and evaluates it using an interpreter for a language I'm working on. I've been fighting lifetime issues pretty much all day in the interpreter, but got stuff to work for the most part.

Until now. When i pass my ast variable into the interpreter, i get the compiler error saying "borrowed value does not live long enough" (borrowed value being the ast). The interp function i use takes in the ast as a reference and if i change that, I'd have to change a lot of my project. I just don't know what to do to make ast live longer.

Please note that nearly all the logic here is done in the closure passed to the socket.on() method. I was thinking of maybe replacing the closure with an Fn and indicating lifetimes there? But maybe there is another way.

Also, I'm not sure if my use of .leak() is proper or not, but it got rid of some other lifetime issues. I just want this part to work and in the future I will come back and fix this up to make it better.

async fn on_connect(socket: SocketRef) {
    info!("socket connected: {}{}{}", BLUE, socket.id, DFLT);
    let env: Arc<Mutex<HashMap<String, LalaType>>> = Arc::new(Mutex::new(HashMap::new()));

    let env_clone = Arc::clone(&env);

    socket.on("run", move |s: SocketRef, Data::<Cell>(data)| {
        let mut env = env_clone.lock().unwrap();

        info!("Received message from {}{}{}: {:?}", BLUE, s.id, DFLT, data);

        let input = data.cell_text.leak();
        let ast: Vec<Box<parser::AstNode>> = parser::parse(input).unwrap();

        let response = interp(&ast, Some(&mut *env), true).unwrap();

        let output = CellOutput {
            output: response
        };
        

        info!("Sending message to {}{}{}: {:?}", BLUE, s.id, DFLT, output);

        let _ = s.emit("output", output);
    });
}

Here's the full error:

error[E0597]: `ast` does not live long enough
  --> src/main.rs:57:31
   |
55 |         let ast: Vec<Box<parser::AstNode>> = parser::parse(input).unwrap();
   |             --- binding `ast` declared here
56 |
57 |         let response = interp(&ast, Some(&mut *env), true).unwrap();
   |                               ^^^^ borrowed value does not live long enough
...
67 |     });
   |     -
   |     |
   |     `ast` dropped here while still borrowed
   |     borrow might be used here, when `env` is dropped and runs the `Drop` code for type `std::sync::MutexGuard`
   |
   = note: values in a scope are dropped in the opposite order they are defined

I'm really just at a loss now. I'd really appreciate some help because I just want this to work; I dont want to think a poor design choice I made 9 months ago when i first wrote the interpreter and parser is gonna end up messing me up now.

All the code is also on github and can be found here.

Again, any help is appreciated. I've never had as many issues with lifetimes in the past year that i've been learning rust than i have today.

didn't look at the repository yet, but the error message suggests the interp function probably have some kind of "self borrowing" anti pattern in its type signature.

I was getting some lifetime errors this morning when i was working on it and after some reading and searching and following the compiler hints/quick fixes, i made this as the interp function signature:

pub fn interp<'a, 'b>(
    ast: &'b Vec<Box<AstNode<'b>>>,
    map: Option<&mut HashMap<String, LalaType<'a>>>,
    tcp: bool,
) -> Result<String, Error>
where
    'b: 'a,
{
// function

could it be that since i specify b lives longer than a that the compiler is having issues with the lifetimes of my variables from the closure i post about?

it turns out my initial guess was right. the type &'b Vec<Box<AstNode<'b>>> is a common anti-pattern in rust, it causes the argument be borrowed forever. although this case it's not a mut borrow, I think the existence of Vec and/or Box would result a invariant subtype situation.

you can get a through explanation here:

I assume it's intended that, in interp, things from the ast will end up stored in the map? That's what the signature implies to me. I'll keep assuming so for the rest of this comment.

Is it viable for your function to have this signature? It may help.

pub fn interp<'a>(
    ast: &[Box<AstNode<'a>>],
    map: Option<&mut HashMap<String, LalaType<'a>>>,
    tcp: bool,
) -> Result<String, Error>

If not you can probably use this signature...

pub fn interp<'a>(
    ast: &'a [Box<AstNode<'_>>],
    map: Option<&mut HashMap<String, LalaType<'a>>>,
    tcp: bool,
) -> Result<String, Error>

...but it probably won't help on its own. It may also technically not be necessary if AstNode<'x> is covariant in 'x (I've only skimmed the code, but I think that's the case).

Here's a more minimal example. The problem is that the signature says the MutexGuard<HashMap> can capture a borrow of the &Vec, and then (as far as the borrow checker is concerned) be observed when the MutexGuard drops. Two possible fixes for the playground are

  • Declare ast first to change the drop order
  • Only borrow from the contents of the Vec and not the &Vec (by changing interp's signature)

I'm not sure if that will help your use case or not as I haven't dug into all the details (like cell_text.leak() for example); exactly what's going on with the inner lifetime may also matter.

thank you for those links... i never knew about this before. i will not make this same mistake in the future.

thanks a lot. the second type signature helped me:

pub fn interp<'a>(
    ast: &'a [Box<AstNode<'_>>],
    map: Option<&mut HashMap<String, LalaType<'a>>>,
    tcp: bool,
) -> Result<String, Error>
{

in my closure i simply had to add a .leak() a the end of ast's declaration:

let ast = parser::parse(input).unwrap().leak()
let response = interp(ast, Some(&mut *env), true).unwrap();

funnily enough i found out about .leak() by accident yesterday morning when I was typing and vscode recommended the method. i'm unsure about how wise it is to continue using but it has solved 3 problems for me so far. thank you for the type signature!

edit: wow, after reading more about it i'm definitely going to go back and change the structure of my code in the future so i dont use .leak(). for now though, as this is a small part of a web development project im doing for school, i will leave it.

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.