2

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:

Community
  • 1
  • 1
hdante
  • 7,685
  • 3
  • 31
  • 36
  • What makes you believe that the answers you've seen elsewhere **aren't** a "complete general solution"? Perhaps you could [edit] your question to explain? – Shepmaster Apr 21 '17 at 21:53
  • Maybe I can link them directly – hdante Apr 21 '17 at 21:55
  • Said another way, why *shouldn't* this be viewed as a duplicate of [Passing mutable context into callbacks](http://stackoverflow.com/q/39089905/155423); [Creating a callback system using closures](http://stackoverflow.com/q/29540167/155423); [Execute callbacks like as mutable borrowing from cycle](http://stackoverflow.com/q/38027461/155423); and [Callback to mutable self](http://stackoverflow.com/q/39137364/155423)? – Shepmaster Apr 21 '17 at 21:56
  • Linking would be great; **and describing what's wrong with them**. In another vein, what makes you think there *is* a general solution? – Shepmaster Apr 21 '17 at 21:56
  • I'll need to check these questions. I don't know if there's a general solution. Stating that there's none would be a valid answer. This answer doesn't appear in other questions, though. – hdante Apr 21 '17 at 22:14

1 Answers1

3

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"));
}
Rufflewind
  • 8,545
  • 2
  • 35
  • 55
  • The first option looks ok to me, the second option I believe it wouldn't generally work with multiple widgets or require passing the complete widget tree or graph as the parameter. But since in your answer you have given up the static borrow checker I wonder if callbacks are intrinsically unsafe, or the borrow checker too limited ? – hdante Apr 21 '17 at 22:57
  • 2
    The second approach *is* more complicated, but that is exactly how one would express intent to the (static) borrow checker. The borrow checker does not analyze the whole program; it only looks at one function at a time, and each closure counts as its own function. So if a closure uses a `&mut Pen`, then that `&mut Pen` is “locked” for exclusive use for as long as that closure exists. The borrow checker does not attempt to examine the call sites of the closure. – Rufflewind Apr 22 '17 at 01:40