Michael Deardeuff
Michael Deardeuff

Reputation: 10697

Callback to mutable self

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);
}

Play

Upvotes: 1

Views: 1534

Answers (3)

aSpex
aSpex

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

Francis Gagn&#233;
Francis Gagn&#233;

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

Lukas Kalbertodt
Lukas Kalbertodt

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

Related Questions