回调可变的自我

时间:2016-08-25 05:32:17

标签: rust

有没有办法(生锈)将一个可变的借来的自我发送到一个没有我在以下MWE中使用的mem::replace黑客的回调?我正在使用锈稳定(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

3 个答案:

答案 0 :(得分:2)

,您的代码无法做到这一点。如果可能的话,您可以轻松地构建一个破坏内存安全性的示例,例如访问释放的内存(这是留给读者的练习)。

您可以考虑SelfCaller是否真的需要Inner的所有字段。如果没有,您可以将(希望很少)单个字段作为参数传递。如果没有,您可以创建另一个类型(让我们称之为$self.find('a').attr('href','new value'); ),其中包含对回调重要的所有字段并将其传递给函数。

答案 1 :(得分:2)

不,没有办法,因为这样做是不安全的。这是一个演示原因的例子(需要夜间编译器)。

#![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);
}

该程序的输出是:

calling callback
changing callback
Callback dropped!
changed callback
called callback
replaced message

好的,那是怎么回事?首先,我使用actSelfCaller编写了mem::replace的实现方式,以便我可以调用回调而不使用mem::transmute让编译器生成与self断开连接的新生命周期。

然后,我编写了一个回调函数(使用struct Callback,因为我需要一个实现FnMutDrop的类型来演示问题),它会改变SelfCaller 1}}通过更改其callback成员。这具有删除前一个回调的效果,这是当前正在执行的回调!如果Callback包含数据成员,则尝试读取它们会导致未定义的行为,因为它们现在处于释放的内存中(我们放弃了整个Box)。

顺便说一下,在使用mem::replace的代码中,回调无法更改回调,因为在回调调用结束后恢复回调。

答案 2 :(得分:2)

如果您不需要借用环境的回调,您可以使用函数而不是闭包:

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