Need help on this closure lifetime issue (does it have to be 'static?)

Hello guys,

I'm trying to implement a function, the function has an argument which is a closure.

Within the function, I create some local variable. and I pass the local variable to the closure. The variable will not be needed anymore once the closure returns.

I expected this to work, but I ended up receiving a lifetime error. Basically the local variable have a life time <'_> where as the closure's argument needs to have 'static. I don't understand this. Why does it have to be 'static. I followed some online instructions in an attempt to change that, but it didn't work.

here is my code:

    pub fn transaction<'a, F>(&'a self, f: F) -> Result<(), &str> 
        where  F: FnOnce(&'a HashMap<String, std::rc::Rc<std::cell::RefCell<dyn CollectionTrait>>>) -> Result<(), &'static str>
    {
        let mut conn = self.internal.borrow_mut();
        let tx = std::rc::Rc::new( std::cell::RefCell::new( TransactionInternal{ connection: conn.connection.transaction().unwrap() }));

        let mut collections: HashMap<String, std::rc::Rc<std::cell::RefCell<dyn CollectionTrait>>> =  HashMap::new();

        let tx_weak = std::rc::Rc::downgrade(&tx);

        for (key, value) in &self.collections {
            collections.insert(key.to_string(), std::rc::Rc::new(RefCell::new(Collection::<false, true, false, false> {
                name: key.to_string(),
                db: tx_weak.clone(),
                table_name: key.to_string(),
            })));
        }

        let transaction = Transaction {
            tx: Rc::downgrade(&tx)
        };

        f(&collections).unwrap();

       // tx.commit().unwrap();
     
        Err("Not implemented")
    }

the error I received was this

error[E0759]: `self` has lifetime `'a` but it needs to satisfy a `'static` lifetime requirement
   --> src/database.rs:392:38
    |
389 |     pub fn transaction<'a, F>(&'a self, f: F) -> Result<(), &str> 
    |                               -------- this data with lifetime `'a`...
...
392 |         let mut conn = self.internal.borrow_mut();
    |                        ------------- ^^^^^^^^^^
    |                        |
    |                        ...is captured here...
...
411 |         f(&collections).unwrap();
    |           ------------ ...and is required to live as long as `'static` here

it says the variable has a lifetime 'a, which I understand. but when I pass it into the closure, it has to be 'static. I don't understand this part and is a way to change this? Thanks!

Does it help to remove the 'a lifetime bound from the &self reference? I think you are asking the borrow passed to the closure to live at least as long as Self.

2 Likes

Or perhaps remove 'a altogether. If you leave it in the function parameter, you'll still be saying "The caller chooses how long my borrow of collections (and thus, collections itself) has to last."

My next guess after that would be to leave it on &'a self and then add it to the parameter:

where
  F: FnOnce(&HashMap<String, Rc<RefCell<dyn CollectionTrait + 'a>>>) -> Result<(), &'static str>
  //     No ^^^                                       Yes  ^^^^^

(Which doesn't seem necessary offhand, but it's hard to tell.)

1 Like

Quick follow-up before I go AFK -- just noticed you do need to keep 'a on &'a self to inhibit elision / to keep the return str lifetime from being linked to the borrow of self. Alternatively, return a &'static str instead (which is the only thing that can fulfill that independent lifetime return anyway).

1 Like

If the closure will be accepting a short-lived reference, you need to describe it with an HRTB. Something like this (untested):

    pub fn transaction< F>(&self, f: F) -> Result<(), &'static str> 
        where  F: for<'a> FnOnce(&'a HashMap<String, std::rc::Rc<std::cell::RefCell<dyn CollectionTrait>>>) -> Result<(), &'static str>
    {
       ...
    }

Edit: In this case, I think this is actually equivalent to @quinedot's suggestion, but I'm not sure:

    pub fn transaction< F>(&self, f: F) -> Result<(), &'static str> 
        where  F: FnOnce(&HashMap<String, std::rc::Rc<std::cell::RefCell<dyn CollectionTrait>>>) -> Result<(), &'static str>
    {
       ...
    }

When the compiler complains about 'static it doesn't really want that lifetime, it's just a poor way of saying it has found a temporary reference, and all temporary references are forbidden in this context.

You probably need to make the closure a move || closure, so that its context is moved/copied to it, instead of temporarily referenced.

no.

in fact, the initial version didn't have that 'a. I added that as an attempt to resolve the issue. but it couldn't work with or without 'a

Thank you.

I think + 'a could resolve the issue with the closure call. I now have:

    pub fn transaction<'a, F>(&'a self, f: F) -> Result<(), &str> 
        where for <'b> F: FnOnce(&'b HashMap<String, std::rc::Rc<std::cell::RefCell<dyn CollectionTrait + 'a>>>) -> Result<(), &'static str>
    {
        let mut conn  = self.internal.borrow_mut();
        let tx = std::rc::Rc::new( std::cell::RefCell::new( TransactionInternal::<'a>{ connection: conn.connection.transaction().unwrap() }));

        let mut collections: HashMap<String, std::rc::Rc<std::cell::RefCell<dyn CollectionTrait+'a>>> =  HashMap::new();

        let tx_weak = std::rc::Rc::downgrade(&tx);

        for (key, value) in &self.collections {
            collections.insert(key.to_string(), std::rc::Rc::new(RefCell::new(Collection::<false, true, false, false> {
                name: key.to_string(),
                db: tx_weak.clone(),
                table_name: key.to_string(),
            })));
        }

        let transaction = Transaction {
            tx: Rc::downgrade(&tx)
        };

        f(&collections).unwrap();

       // tx.commit().unwrap();
     
        Err("Not implemented")
    }

now it's complaining about

393 |         let tx = std::rc::Rc::new( std::cell::RefCell::new( TransactionInternal::<'a>{ connection: conn.connection.transaction().unwrap()...
    |                                                                                                    ^^^^-------------------------
    |                                                                                                    |
    |                                                                                                    borrowed value does not live long enough
    |                                                                                                    argument requires that `conn` is borrowed for `'a`

I think this is due to the borrow_mut call which returns a RefMut<'_, T>. I need to somehow tell it to return a RefMut<'a, T>

It's hard to tell what's going on without the type/trait/method signatures. But, does this change anything?

pub fn transaction<F>(&self, f: F) -> Result<(), &str> 
        where for <'b> F: FnOnce(
            &'b HashMap<String, Rc<RefCell<dyn CollectionTrait + '_>>>
        ) -> Result<(), &'static str>

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.