2017-04-21 23 views
1

我有下面的示例代码,它是其他编程语言中事件驱动的API的标准基础,但在Rust中,借用检查程序阻止它“不能每次多次借用p1作为mutable”:在Rust中实现多重可变(静态分配,静态分派等)回调等价物的正确方法是什么?

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 

该代码可用于,例如,实现两个回调的窗口:一个用于处理键盘事件,而另一个用于处理鼠标事件的,这两者的更新的窗口状态(例如:改变颜色,闭合窗户等)。

我知道,出现在堆栈溢出和其他论坛的其他地方这个问题,但在一般情况下,答案集中在描述问题的原因,很少提出了一个完整的通用解决方案:

+0

是什么让你相信你在其他地方看到的答案不是**“完整的通用解决方案”?也许你可以[编辑]你的问题来解释? – Shepmaster

+0

也许我可以直接链接它们 – hdante

+0

换句话说,为什么*不应该被看作是[将可变上下文传入回调](http://stackoverflow.com/q/39089905/155423); [使用闭包创建回调系统](http://stackoverflow.com/q/29540167/155423); [执行回调,如从周期可变借用](http://stackoverflow.com/q/38027461/155423);和[回调可变自我](http://stackoverflow.com/q/39137364/155423)? – Shepmaster

回答

1

一种方法是使用RefCell,它允许你发生变异的东西,只有&Pen代替&mut Pen,在推借检查运行时的成本。这很便宜:没有分配,只有一个标志测试。 主要的缺点是违反规则将导致运行时恐慌。一个有用的经验法则是永远不要再借用(认为它们是“单线程互斥体”)。

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

第一个选项对我来说看起来不错,第二个选项我相信它通常不会用于多个小部件,或者需要传递完整的小部件树或图形作为参数。但是因为在你的答案中,你已经放弃了静态借用检查器,我想知道回调本质上是不安全的还是借用检查器太有限了? – hdante

+1

第二种方法*更复杂,但这正是如何向(静态)借用检查器表达意图的方式。借阅检查员不会分析整个程序;它一次只查看一个函数,每个闭包算作自己的函数。因此,如果一个闭包使用'&mut Pen',那么只要该闭包存在,该'&mut Pen'就会被“锁定”以供专用。借用检查器不会试图检查关闭的调用地点。 – Rufflewind

相关问题