现在我定义一个接口Messager,用来send消息。
pub trait Messager {
fn send(&self, msg: &str);
}
然后一个LimitTracker类型根据指定的配额来send消息*
pub struct LimitTracker<'a, T: Messager> {
messager: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messager,
{
pub fn new(messager: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker { messager: messager, value: 0, max: max }
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messager.send("Error, You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messager.send("Urgent warning: You have used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messager.send("Warning: You have used up over 75% of your quota!");
}
}
}
最后是一段测试代码,用来测试set_value的返回是否符合预期*
#[cfg(test)]
mod tests {
use super::*;
struct MockMessager {
send_messages: Vec<String>,
}
impl MockMessager {
fn new() -> MockMessager {
MockMessager { send_messages: vec![] }
}
}
impl Messager for MockMessager {
fn send(&self, msg: &str) {
self.send_messages.push(String::from(msg));
}
}
#[test]
fn send_an_over_75_percentage_message() {
let mock_messager = MockMessager::new();
let mut limit_tracker = LimitTracker::new(&mock_messager, 100);
limit_tracker.set_value(80);
assert_eq!(mock_messager.send_messages.len(), 1);
}
}
我们发现上面的self.send_messages.push(String::from(msg));这一段报错了。很简单:因为我们最开始定义的send方法是一个immutable borrow reference,即:&self。所以不可以修改MockMessager实例的值。
解决方法有两种:第一种是直接修改Messager trait,send方法接收&mut self;第二种是使用Interior Mutable Pattern(A Mutable Borrow to an Immutable Value),可以使用RefCell智能指针,获得一个可变借用。
下面是第二种方法的全部代码:
// implement a mock object
pub trait Messager {
fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: Messager> {
messager: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messager,
{
pub fn new(messager: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker { messager: messager, value: 0, max: max }
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messager.send("Error, You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messager.send("Urgent warning: You have used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messager.send("Warning: You have used up over 75% of your quota!");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
struct MockMessager {
send_messages: RefCell<Vec<String>>,
}
impl MockMessager {
fn new() -> MockMessager {
MockMessager { send_messages: RefCell::new(vec![]) }
}
}
impl Messager for MockMessager {
fn send(&self, msg: &str) {
self.send_messages.borrow_mut().push(String::from(msg));
}
}
#[test]
fn send_an_over_75_percentage_message() {
let mock_messager = MockMessager::new();
let mut limit_tracker = LimitTracker::new(&mock_messager, 100);
limit_tracker.set_value(80);
assert_eq!(mock_messager.send_messages.borrow().len(), 1);
}
}
我使用RefCell将MockMessager中需要修改的值包装起来,然后通过borrow_mut()获得它的mutable 引用,通过borrow()获得immutable引用,从而可以达到修改MockMessager的目的。
RefCell和通常的Rust规则很不同,他是在runtime时进行borrow check,所以如果在程序运行时违反了Rust的借用规则,那么程序会Panic或者Exit。而不是想通常情况下直接Compile Error。另外,这种模式会有一些性能开销,因为需要在运行时跟踪它是否符合借用规则。