回调可变自身 [英] Callback to mutable self

查看:30
本文介绍了回调可变自身的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有一种方法(生锈)可以在没有 mem::replace hack 的情况下将可变借用的 self 发送到回调我在以下 MWE 中使用?我正在使用 Rust stable (1.11.0).

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

播放

推荐答案

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

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

这个程序的输出是:

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

好的,怎么回事?首先,我为 SelfCaller 编写了 act 的实现,这样我可以在没有 mem:: 的情况下调用回调替换,使用mem::transmute让编译器生成一个与self断开连接的新生命周期.

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.

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

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).

顺便说一下,在你使用 mem::replace 的代码中,回调不能改变回调,因为你在回调调用结束后恢复回调.

By the way, in your code using mem::replace, callbacks cannot change the callback, since you restore the callback after the callback call ends.

这篇关于回调可变自身的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆