callback - 在 Rust 中实现多个可变(静态分配、静态分派(dispatch)等)回调的正确方法是什么?

标签 callback rust borrow-checker event-driven

我有以下示例代码,它是其他编程语言中事件驱动 API 的标准基础,但在 Rust 中,借用检查器会阻止它,并显示“不能多次借用 p1 作为可变的”一次”:

struct Pen {
    color_cmyk: u32,
    ink: usize,
}

impl Pen {
    pub fn new() -> Pen {
        Pen {
            color_cmyk: 0x80800000,
            ink: 20000,
        }
    }

    pub fn write(&mut self, text: &str) -> bool {
        if self.ink < text.len() {
            return false;
        }

        self.ink -= text.len();
        true
    }
}

fn main() {
    println!("Hello, world !");

    let mut p1 = Pen::new();
    p1.write("Hello");
    println!("ink: {}, color: {}", p1.ink, p1.color_cmyk);

    let mut cb = |text| if p1.write(text) {
        println!("{}", text);
    } else {
        println!("Out of ink !");
    };

    let mut cb2 = |text| {
        p1.write(text);
        p1.ink
    };

    cb("Hello");
    cb("World");
    println!("{}", cb2("Hello"));
}
error[E0499]: cannot borrow `p1` as mutable more than once at a time
  --> src/main.rs:37:23
   |
31 |         let mut cb = |text| if p1.write(text) {
   |                      ------    -- previous borrow occurs due to use of `p1` in closure
   |                      |
   |                      first mutable borrow occurs here
...
37 |         let mut cb2 = |text| {
   |                       ^^^^^^ second mutable borrow occurs here
38 |             p1.write(text);
   |             -- borrow occurs due to use of `p1` in closure
...
45 |     }
   |     - first borrow ends here

例如,该代码可用于实现对窗口的两个回调:一个用于处理键盘事件,另一个用于处理鼠标事件,两者都会更新窗口状态(例如:更改颜色、关闭窗口等) .).

我知道这个问题出现在 Stack Overflow 和其他论坛的其他地方,但一般来说,答案都集中在描述问题的原因,很少提出完整的通用解决方案:

最佳答案

一种方法是使用 RefCell,它允许您仅使用 &Pen 而不是 &mut Pen 来改变事物,但代价是将借用检查推至运行时。它非常便宜:没有分配,只有一个标志测试。 主要缺点是违反规则会导致运行时出现 panic 。一个有用的经验法则是永远不要借用超过必要的时间(将它们视为“单线程互斥体”)。

use std::cell::RefCell;

fn main() {
    println!("Hello, world !");

    let p1 = RefCell::new(Pen::new());
    {
        let mut rp1 = p1.borrow_mut();
        rp1.write("Hello");
        println!("ink: {}, color: {}", rp1.ink, rp1.color_cmyk);
    }

    let cb = |text| {
        if p1.borrow_mut().write(text) {
            println!("{}", text);
        }
        else {
            println!("Out of ink !");
        }
    };

    let cb2 = |text| {
        let mut rp1 = p1.borrow_mut();
        rp1.write(text);
        rp1.ink
    };

    cb("Hello");
    cb("World");
    println!("{}", cb2("Hello"));
}

另一种方法是设置回调系统以将要修改的对象作为参数传递。权衡是您的回调系统需要了解这种状态。

fn main() {
    println!("Hello, world !");

    let mut p1 = Pen::new();
    p1.write("Hello");
    println!("ink: {}, color: {}", p1.ink, p1.color_cmyk);

    let cb = |p1: &mut Pen, text| if p1.write(text) {
        println!("{}", text);
    } else {
        println!("Out of ink !");
    };

    let cb2 = |p1: &mut Pen, text| {
        p1.write(text);
        p1.ink
    };

    cb(&mut p1, "Hello");
    cb(&mut p1, "World");
    println!("{}", cb2(&mut p1, "Hello"));
}

关于callback - 在 Rust 中实现多个可变(静态分配、静态分派(dispatch)等)回调的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43552480/

相关文章:

c++ - 传递一个成员函数作为回调,boost::bind, boost::function

带引用的嵌套结构

async-await - 为什么 `Box<dyn Sink>` 在使用 Tokio 和实验性异步/等待支持时不实现 `Sink` 特征?

enums - 如何在 structopt 中使用枚举?

rust - 如何从 HashMap 或 HashSet 返回 get_mut 的结果?

swift - 如何检测 firebase 观察 childAdded 在达到 queryLimit 时停止调用?

javascript - Javascript 如何匹配回调函数中的参数?

rust - 如何升级我的 Slicable 特征以满足重复调用中的借用检查器的要求

rust - Vec 中的结构可以相互引用吗?

callback - GLib - 为类的所有实例调用回调