Borrow in `static closure

Im finding it difficult to even explain the problem but this playground should give you an idea of what Im trying to do.

Here's the code

use std::collections::HashMap;

struct Inner {
    
}
struct Outer {
    my_map: HashMap<String, String>,
    inner: Inner
}

impl Outer {
    fn check(&self, key: &String) -> bool {
        self.my_map.contains_key(key)
    }
    
    fn find(&mut self, input: String) {
        self.inner.takes_a_closure(|f| {
            self.check(&f)
        });
        self.my_map.insert(String::from("key"), String::from("value"));
    }
}

impl Inner {
    fn takes_a_closure<F>(&self, input: F) where
        F: Fn(String) -> bool + 'static,
        {
            //empty. Not required. 
        }
}

fn main() {
    //empty. Not required. 
}

Here's the error I get

   Compiling playground v0.0.1 (/playground)
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:17:36
   |
17 |           self.inner.takes_a_closure(|f| {
   |  ____________________________________^
18 | |             self.check(&f)
19 | |         });
   | |_________^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 16:5...
  --> src/main.rs:16:5
   |
16 | /     fn find(&mut self, input: String) {
17 | |         self.inner.takes_a_closure(|f| {
18 | |             self.check(&f)
19 | |         });
20 | |         self.my_map.insert(String::from("key"), String::from("value"));
21 | |     }
   | |_____^
   = note: ...so that the types are compatible:
           expected &&mut Outer
              found &&mut Outer
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `[closure@src/main.rs:17:36: 19:10 self:&&mut Outer]` will meet its required lifetime bounds
  --> src/main.rs:17:20
   |
17 |         self.inner.takes_a_closure(|f| {
   |                    ^^^^^^^^^^^^^^^

error: aborting due to previous error

error: could not compile `playground`.

To learn more, run the command again with --verbose.

So in short: how can that `static closure use the Outer's map (via check)? Is this even possible?

You will have to take ownership of the map. Either by cloning it or my moving out of it. If cloning is expensive, then consider wrapping it in Rc/Arc to make clones cheaper.

1 Like

Question about cloning: What happens if the map changes (like a new element gets added) after cloning? My closure won't give correct answers.
Since I dont want Arc Im leaning towards Rc.

It looks like you want shared mutability. Which is usually achieved by using Rc/Arc in combo with Cell/RefCell for Rc or Mutex/Rwlock for Arc

Yes normally if you clone and later mutate, that mutation won't be reflected in the clone. But by using Rc/Arc in combo with the shared mutability types above, those mutations will be reflected. This is because cloning an Rc/Arc only increments a counter, but both the original and the clone point to the same value.

You need to clone things because 'static means no short-lived lifetimes. This includes &mut self.

Completely other question: Is 'static really required?

1 Like

Rc with Cell/RefCell best fits my case. I have a very high level idea of what they are.
Time to dig in more details.
But, and I think I should have asked in my first post, let me tell you what I think the error means:
Outer lifetime is valid in find but since a closure can be called after the find method, &mut self borrow might or might not be valid and hence the error.

Is my understanding correct?

Yes, that is correct.

Let me add that this is only because it's 'static. Without, the compiler will annotate these lifetimes:

    fn takes_a_closure<'a, F>(&'a self, input: F) where
        F: Fn(String) -> bool + 'a,
        {
            //empty. Not required. 
        }

and the closure can only live as long as self.

3 Likes

I did something like this

use std::collections::HashMap;
use std::rc::Rc;
use core::cell::RefCell;

struct Inner {
    
}
struct Outer {
    my_map: Rc<RefCell<HashMap<String, String>>>,
    inner: Inner
}

impl Outer {
    fn check(&self, key: &String) -> bool {
        let borr_immut = self.my_map.borrow();
        borr_immut.contains_key(key)
    }
    
    fn find(&mut self, input: String) {
        let rc_to_map = Rc::clone(&self.my_map);
        self.inner.takes_a_closure(move |f| {
            //self.check(&f)
            let im = rc_to_map.borrow();
            im.contains_key(&f)
        });
    }
}

impl Inner {
    fn takes_a_closure<F>(&self, input: F) where
        F: Fn(String) -> bool + 'static,
        {
            
        }
}

fn main() {
    //empty. Not required. 
}

My understanding:
This works because in my closure, there is no borrow of Outer. With the help of Rc<Refcell> Im able to get the map that I need within the closure.

Is my understanding correct?

Yes, this is correct.

Can you please answer @Hocuri's question of why you need 'static. It is an unusual requirement outside of multi-threading.

1 Like

Sorry for late reply. @RustyYato I dont think he was asking me anything.
But the `static is part of existing project Im working on. I just copy-pasted and change certain things to explain the problem.
Also why is it unusual to have `static outside of multi-threading?

Because the 'static usually only comes from std::any::Any, Box<dyn Trait> or multi-threading with std. std::any::Any requires 'static for technical reasons. Boxed trait objects are not too common, and it is usually better to change it to Box<dyn Trait + 'a> to allow any lifetime instead of just 'static. With multi-threading, the std only provides std::thread::spawn, this requires 'static because threads are detached be default, so they could run for longer than their parent thread.

Overall, this means that 'static usually only comes up in some very specific an isolated cases.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.