0

I am having a hard time solving problems with references and borrowing.

My end goal is that a user can create instance of a bot, add callbacks to it (pairs of (String, closure)) and then call listen. When the bot receives a message, it compares the text of the message to the keys in the hashmap of closures. If one of them matches, it calls the corresponding callback closure.

I need to use String field on instance b of the struct in the async move block in the closure passed to b's method add_closure and that's the problem.

What I've tried: different smart pointers, it didn't help though.

playground

use futures::Future;
use std::pin::Pin;

struct B {
    val: String,
    closures: Vec<Box<dyn FnMut() -> Pin<Box<dyn Future<Output = ()>>>>>,
}

impl B {
    fn add_closure(&mut self, closure: Box<dyn FnMut() -> Pin<Box<dyn Future<Output = ()>>>>) {
        self.closures.push(closure);
    }
}

fn main() {
    let mut b = B {
        val: "test".to_string(),
        closures: vec![],
    };

    b.add_closure(Box::new(|| {
        Box::pin(async {
            println!("{}", b.val);
        })
    }));
}

The compiler's error message is huge:

error[E0502]: cannot borrow `b` as mutable because it is also borrowed as immutable
  --> src/main.rs:21:5
   |
21 |        b.add_closure(Box::new(|| {
   |        ^             -        -- immutable borrow occurs here
   |  ______|_____________|
   | | _____|
   | ||
22 | ||         Box::pin(async {
23 | ||             println!("{}", b.val);
   | ||                            - first borrow occurs due to use of `b` in closure
24 | ||         })
25 | ||     }));
   | ||______-^ mutable borrow occurs here
   | |_______|
   |         cast requires that `b` is borrowed for `'static`

error[E0597]: `b` does not live long enough
  --> src/main.rs:23:28
   |
21 |       b.add_closure(Box::new(|| {
   |                     -        -- value captured here
   |  ___________________|
   | |
22 | |         Box::pin(async {
23 | |             println!("{}", b.val);
   | |                            ^ borrowed value does not live long enough
24 | |         })
25 | |     }));
   | |______- cast requires that `b` is borrowed for `'static`
26 |   }
   |   - `b` dropped here while still borrowed

error: aborting due to 2 previous errors

How to use

What I need to do to get what I wanted to work?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Zohnannor
  • 5
  • 2
  • 5
  • 1
    It looks like your question might be answered by the answers of [How can I put an async function into a map in Rust?](https://stackoverflow.com/q/58704424/155423); [How can I store an async function in a struct and call it from a struct instance?](https://stackoverflow.com/q/58173711/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Mar 29 '21 at 15:57
  • Your code moves captured values into the async block (via `async move`). You are trying to use `bot` inside there, which means it's captured, so you are trying to move `bot`. You can't move it because then how would you have it anymore? [Effectively this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1348eddefb0e1f3bf499135259fc306e). – Shepmaster Mar 29 '21 at 16:03
  • It's hard to answer your question because it doesn't include a [MRE]. We can't tell what crates (and their versions) are present in the code and it's much bigger than it needs to be. There are [Rust-specific MRE tips](//stackoverflow.com/tags/rust/info) you can use to reduce your original code for posting here. Thanks! – Shepmaster Mar 29 '21 at 16:04
  • @Shepmaster, yes. But how to not move it then and still be able to get its field value? – Zohnannor Mar 29 '21 at 16:14
  • @Shepmaster, I will try to remove any irrelevant code from my example. Thank you. – Zohnannor Mar 29 '21 at 16:16
  • Perhaps your question is answered by [Is there a way to have a Rust closure that moves only some variables into it?](https://stackoverflow.com/q/58459643/155423) (closures and `async move` work the same way here) – Shepmaster Mar 29 '21 at 16:39
  • With your modifications, your question may now be answered by [How can I modify self in a closure called from a member function?](https://stackoverflow.com/q/28597380/155423); [Mutably borrow one struct field while borrowing another in a closure](https://stackoverflow.com/q/36379242/155423) – Shepmaster Mar 29 '21 at 16:54
  • @Shepmaster you propose me to make reference to `b`'s field, however `b` is declared as `mut` and doing `let val = &b.val` and then using `val` in closure won't compile. because `cannot borrow b as mutable because it is also borrowed as immutable` and `val does not live long enough` – Zohnannor Mar 29 '21 at 17:04
  • Your playground and prose use `async move`, your code and error message do not. You should ensure you are talking about one thing consistently. – Shepmaster Mar 29 '21 at 17:20
  • Check [How can I modify self in a closure called from a member function?](https://stackoverflow.com/a/28597666/155423) again. – Shepmaster Mar 29 '21 at 17:21
  • @Shepmaster, thanks, I will do the same, pass `&mut` reference to the closure. But now I have new issue: I need to iterate over `Vec` of closures and somehow call them: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a9b37b654404a48d62588cfe36990c8d) – Zohnannor Mar 29 '21 at 17:50
  • I made your example [compileable](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=76f19917e6cd01bb703901e8bf47ee8b). Fixing the closures' lifetime issues just requires annotating that the async function can return a future bound to the lifetime of `b`. The other issue in `listen` is much more awkward and is indicative of bad design. As you've done, you have the opportunity to change the `closures` vec while you're iterating over it. The fix I've presented just swaps it out with a dummy and puts it back when its done. You should probably explain what the end goal is. – kmdreko Mar 29 '21 at 18:20
  • @Shepmaster Thank you. The end goal is: user can create instance of a bot, add callbacks to it - pairs of string:closure and then calls method `listen`. When bot receives a message, it compares text of a message to keys in hashmap of closures and if one of them match - calls corresponding callback closure. I couldn't think of anything better than this design :( – Zohnannor Mar 29 '21 at 18:32
  • @kmdreko i think that was for you ^ – Shepmaster Mar 29 '21 at 18:36
  • Generally the advice would be to separate the context used by the closure from the machinery of calling the closures itself. So it depends on what you want the closure to do. If it just needs info to reference, then you can do that. [Here's an example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8ab9871ad306183d4b7ae8bc9d99fe21) that just passes the `val` field. This can be more elaborate, but it can't just pass `self`/`B`. – kmdreko Mar 29 '21 at 19:50

0 Answers0