Reputation: 10697
Is there a way (in rust) to send a mutable borrowed self to a callback without the mem::replace
hack I am using in the following MWE? I am using rust stable (1.11.0).
use std::mem;
trait Actable {
fn act(&mut self);
}
// Not Cloneable
struct SelfCaller {
message: String,
callback: Box<FnMut(&mut SelfCaller)>,
// other stuff
}
impl Actable for SelfCaller {
fn act(&mut self) {
fn noop(_: &mut SelfCaller) {}
let mut callback = mem::replace(&mut self.callback, Box::new(noop));
callback(self);
mem::replace(&mut self.callback, callback);
}
}
impl Drop for SelfCaller {
fn drop(&mut self) {/* unimiportant to the story */}
}
fn main() {
fn change(messenger: &mut SelfCaller) {
messenger.message = "replaced message".to_owned();
}
let mut messenger = SelfCaller {
message: "initial message".to_owned(),
callback: Box::new(change),
};
messenger.act();
println!("{}", &messenger.message);
}
Upvotes: 1
Views: 1534
Reputation: 5216
If you don't need callbacks that borrow an environment, you can use a function instead of the closure:
trait Actable {
fn act(&mut self);
}
struct SelfCaller {
message: String,
callback: fn(&mut SelfCaller),
}
impl Actable for SelfCaller {
fn act(&mut self) {
(self.callback)(self);
}
}
fn main() {
fn change(messenger: &mut SelfCaller) {
messenger.message = "replaced message".to_owned();
}
let mut messenger = SelfCaller {
message: "initial message".to_owned(),
callback: change,
};
messenger.act();
println!("{}", &messenger.message);
}
Upvotes: 2
Reputation: 65782
No, there is no way, because it is unsafe to do so. Here's an example that demonstrates why (requires a nightly compiler).
#![feature(fn_traits)]
#![feature(unboxed_closures)]
use std::mem;
trait Actable {
fn act(&mut self);
}
struct SelfCaller {
message: String,
callback: Box<FnMut(&mut SelfCaller)>,
}
impl Actable for SelfCaller {
fn act(&mut self) {
let mut callback: &mut Box<FnMut(&mut SelfCaller)> = unsafe { mem::transmute(&mut self.callback) };
println!("calling callback");
callback(self);
println!("called callback");
}
}
struct Callback;
impl Drop for Callback {
fn drop(&mut self) {
println!("Callback dropped!");
}
}
impl<'a> FnOnce<(&'a mut SelfCaller,)> for Callback {
type Output = ();
extern "rust-call" fn call_once(mut self, args: (&mut SelfCaller,)) {
self.call_mut(args)
}
}
impl<'a> FnMut<(&'a mut SelfCaller,)> for Callback {
extern "rust-call" fn call_mut(&mut self, (messenger,): (&mut SelfCaller,)) {
println!("changing callback");
messenger.callback = Box::new(|messenger| {});
println!("changed callback");
messenger.message = "replaced message".to_owned();
}
}
fn main() {
let change = Callback;
let mut messenger = SelfCaller {
message: "initial message".to_owned(),
callback: Box::new(change),
};
messenger.act();
println!("{}", &messenger.message);
}
The output of this program is:
calling callback
changing callback
Callback dropped!
changed callback
called callback
replaced message
OK, so what's going on? First, I've written the implementation of act
for SelfCaller
in such a way that I can call the callback without mem::replace
, using mem::transmute
to get the compiler to generate a new lifetime disconnected from self
.
Then, I've written a callback (using the struct Callback
, since I needed a type that implements both FnMut
and Drop
to demonstrate the problem) that mutates the SelfCaller
by changing its callback
member. This has the effect of dropping the previous callback, which is the callback that is currently executing! If Callback
contained data members, attempting to read them would cause undefined behavior, since they are now in deallocated memory (we dropped the whole Box
).
By the way, in your code using mem::replace
, callbacks cannot change the callback, since you restore the callback after the callback call ends.
Upvotes: 4
Reputation: 88696
No, this is not possible with your code. If it were possible, you could easily construct an example that destroys memory safety, by for example accessing freed memory (this is left as an exercise for the reader 😉).
You could think about whether or not the FnMut
really needs all of the fields of SelfCaller
. If not, you can pass the (hopefully few) single fields as arguments. If not, you can create another type (let's call it Inner
) that contains all fields important to the callback and pass it to the function.
Upvotes: 2