What's the correct way to implement the equivalent of multiple mutable (statically allocated, statically dispatched, etc.) callbacks in Rust?

穿精又带淫゛_ 提交于 2019-12-17 21:15:14

问题


I have the following example code, which is the standard basis of event-driven APIs in other programming languages, but in Rust the borrow checker blocks it with "cannot borrow p1 as mutable more than once at a time":

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

The code can be used, for example, to implement two callbacks to a window: one for handling keyboard events and another for handling mouse events, both of which update the window state (ex: changing color, closing the window, etc.).

I know that this question appears elsewhere in Stack Overflow and other forums, but in general, the answers focus on describing the reason of the problem and rarely propose a complete general solution for it:

  • Cannot borrow `x` as mutable more than once at a time
  • How to bypass “cannot borrow as mutable more than once”?
  • Cannot borrow as mutable more than once at a time
  • Passing mutable context into callbacks
  • Creating a callback system using closures
  • Execute callbacks like as mutable borrowing from cycle
  • Callback to mutable self

回答1:


One way is to use a RefCell, which allows you to mutate things with only &Pen instead of &mut Pen, at the cost of pushing the borrow-checking to runtime. It’s very cheap: there is no allocation, just a single flag test. The main downside is that violating the rules will result in a panic at runtime. A useful rule of thumb is to never borrow for any longer than necessary (think of them as “single-threaded mutexes”).

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

Another way is to set up the callback system to pass in the object that you’re modifying as an argument. The trade-off is then your callback system needs to be aware of this state.

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


来源:https://stackoverflow.com/questions/43552480/whats-the-correct-way-to-implement-the-equivalent-of-multiple-mutable-statical

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!