In short: I'm trying to implement an struct
that contains a callback to an closure, which captures state mutably. Idea is that user provides callback (closure) and can be informed later when a specific event happens.
I have a working proof-of-concept version (code sample #1).
fn fun<F>(f: F) -> F
where
F: FnMut() -> (),
{
f
}
fn main() {
let mut abc = "abc".to_string();
let mut f = fun(|| {
abc.push_str(".");
println!("closure: {:?}", abc);
});
f();
f();
f();
println!("end: {:?}", abc);
}
Output:
closure: "abc."
closure: "abc.."
closure: "abc..."
end: "abc..."
Same idea (slightly different) as previous but trying to contain closure inside Foo
.
struct Foo<'a, T> {
pub cb: Box<dyn FnMut(&T) + 'a>,
}
impl<'a, T> Foo<'a, T> {
fn new(f: impl FnMut(&T) + 'a) -> Self {
Self { cb: Box::new(f) }
}
fn on_change(&mut self, f: impl FnMut(&T) + 'a)
{
self.cb = Box::new(f);
}
}
impl<'a, T> Default for Foo<'a, T>
{
fn default() -> Self {
Self::new(|_| {})
}
}
fn main() {
let mut abc = "abc".to_string();
let mut f = Foo::default();
f.on_change(|a| {
abc.push_str("."); // PROBLEM HERE; uncomment and it works!
println!("- param: {:?}", a);
});
let param = "a".to_string();
(f.cb)(¶m);
(f.cb)(¶m);
(f.cb)(¶m);
println!("end: {:?}", abc);
}
Expected output:
- param: "a"
- param: "a"
- param: "a"
end: "abc..."
Actual output (compiler error):
error[E0502]: cannot borrow `abc` as immutable because it is also borrowed as mutable
--> src/main.rs:33:27
|
25 | f.on_change(|a| {
| --- mutable borrow occurs here
26 | abc.push_str("."); // PROBLEM HERE; uncomment and it works!
| --- first borrow occurs due to use of `abc` in closure
...
33 | println!("end: {:?}", abc);
| ^^^ immutable borrow occurs here
34 | }
| - mutable borrow might be used here, when `f` is dropped and runs the destructor for type `Foo<'_, String>`
The compiler error is pretty clear and it definitely has something to do with lifetimes. I think my problem is that I need to tell the compiler about the closure and its parameter lifetimes – but the question is, how?
How should I modify code sample #2 to get callback registration work like in code sample #1?
REPL:
abc
is still bound in f
.
Variables get destroyed at the end of the scope they live in. f
lives in main
, so f
gets destroyed at the end of main, which means that during the println
, f
still lives and binds abc
.
To destroy a variable earlier, you have to possibilities:
drop
on itHere with drop
:
struct Foo<'a, T> {
pub cb: Box<dyn FnMut(&T) + 'a>,
}
impl<'a, T> Foo<'a, T> {
fn new(f: impl FnMut(&T) + 'a) -> Self {
Self { cb: Box::new(f) }
}
fn on_change(&mut self, f: impl FnMut(&T) + 'a) {
self.cb = Box::new(f);
}
}
impl<'a, T> Default for Foo<'a, T> {
fn default() -> Self {
Self::new(|_| {})
}
}
fn main() {
let mut abc = "abc".to_string();
let mut f = Foo::default();
f.on_change(|a| {
abc.push_str("."); // PROBLEM HERE; uncomment and it works!
println!("- param: {:?}", a);
});
let param = "a".to_string();
(f.cb)(¶m);
(f.cb)(¶m);
(f.cb)(¶m);
drop(f);
println!("end: {:?}", abc);
}
And here with a nested scope:
struct Foo<'a, T> {
pub cb: Box<dyn FnMut(&T) + 'a>,
}
impl<'a, T> Foo<'a, T> {
fn new(f: impl FnMut(&T) + 'a) -> Self {
Self { cb: Box::new(f) }
}
fn on_change(&mut self, f: impl FnMut(&T) + 'a) {
self.cb = Box::new(f);
}
}
impl<'a, T> Default for Foo<'a, T> {
fn default() -> Self {
Self::new(|_| {})
}
}
fn main() {
let mut abc = "abc".to_string();
{
let mut f = Foo::default();
f.on_change(|a| {
abc.push_str("."); // PROBLEM HERE; uncomment and it works!
println!("- param: {:?}", a);
});
let param = "a".to_string();
(f.cb)(¶m);
(f.cb)(¶m);
(f.cb)(¶m);
}
println!("end: {:?}", abc);
}
Both code snippets print:
- param: "a"
- param: "a"
- param: "a"
end: "abc..."