Why does rustc think value is still borrowed?

Here's my erroneous code:

use std::collections::HashSet;

pub struct Program<'a: 'b, 'b> {
    pub list: Vec<Box<dyn Op<'a, 'b>>>,
}

/* Here's a very simple example of a struct that could be in my Program.list.
I am keeping it commented because the error still happens without it.
*/
/*
#[derive(Debug)]
pub struct Instruction<'a>(Reg<'a>);
impl<'a: 'b, 'b> TaintAnalysis<'a, 'b> for Instruction<'a> {
    fn taint_analysis(&'b self, tainted: &mut HashSet<GlobalOrReg<'a, 'b>>) -> bool {
        todo!()
    }
}
impl<'a: 'b, 'b> Op<'a, 'b> for Instruction<'a> {}
*/

pub trait Op<'a: 'b, 'b>: std::fmt::Debug + TaintAnalysis<'a, 'b> {}
pub trait TaintAnalysis<'a: 'b, 'b> {
    fn taint_analysis(&'b self, tainted: &mut HashSet<GlobalOrReg<'a, 'b>>) -> bool;
}

pub enum GlobalOrReg<'a: 'b, 'b> {
    Global(&'b Global<'a>),
    Reg(&'b Reg<'a>),
    Other,
}

#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Global<'a>(pub &'a str);

#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Reg<'a>(pub &'a str);

fn main() -> Result<(), String> {
    let prog = Program {list: vec![/*Instruction(Reg("t1"))*/]};
    { // I added this scope to try to fix the error but it did nothing
        let mut tainted_set = HashSet::new();
        for op in &prog.list {
            if op.taint_analysis(&mut tainted_set) {
                println!("FLOW");
                return Ok(());
            }
        }
        println!("NO FLOW");
        return Ok(());
    }
}

The error:

   Compiling playground v0.0.1 (/playground)
error[E0597]: `prog.list` does not live long enough
  --> src/main.rs:42:19
   |
39 |     let prog = Program {list: vec![/*Instruction(Reg("t1"))*/]};
   |         ---- binding `prog` declared here
...
42 |         for op in &prog.list {
   |                   ^^^^^^^^^^ borrowed value does not live long enough
...
51 | }
   | -
   | |
   | `prog.list` dropped here while still borrowed
   | borrow might be used here, when `prog` is dropped and runs the destructor for type `Program<'_, '_>`

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

I notice that when I remove tainted_set from main, the error goes away. So I think that means that Rust thinks that the references stored in tainted_set could still be around when prog is dropped. But that doesn't make sense to me, because tainted_set is dropped before prog, especially since I put it in its own scope. What is going on? What does the type analysis see here?

There's a few things going on...

  • Trait parameters are invariant
  • Trait parameters are part of the type of dyn Trait<..>
  • Trait objects have non-trivial destructors

When combined with this signature:

pub trait TaintAnalysis<'a: 'b, 'b> {
    fn taint_analysis(&'b self, tainted: &mut HashSet<GlobalOrReg<'a, 'b>>) -> bool;
}

When you create a &'b dyn Op<'a, 'b>, the trait object is borrowed forever,[1] and that conflicts with having a non-trivial destructor.


  1. the shared variant ↩ī¸Ž

1 Like

This compiles by letting the shared reference be shorter than the trait parameter, or you could move that to the method I suppose.

//                                          vvvvvvv                   vv
pub trait Op<'a: 'b, 'b>: std::fmt::Debug + for<'c> TaintAnalysis<'a, 'c> {}

(or)

    fn taint_analysis<'c>(&'c self, tainted: &mut HashSet<GlobalOrReg<'a, 'c>>) -> bool
    where
        'b: 'c;

I'm not sure if this gets rid of all the problems or fits your use case entirely.