RefCell 数据类型

内部可变性(interior mutability)是RUST的设计模式之一,它允许你在只持有不可变引用的前提下对数据进行修改。为了能改变数据,内部可变性模式在它的数据结构中使用了unsafe(不安全)代码来绕过RUST正常的可变性和借用规则。

假如我们能够保证自己的代码在运行时符合借用规则,那么即使编译器无法在编译阶段保证符合借用规则,也能使用那些采用了内部可变性模式的类型。

对于一般引用和Box<T>的代码,RUST会在编译阶段强制代码遵守借用规则。而对于RefCell<T>的代码,RUST则只会在运行时检查这些规则,并在违反借用规则的情况下触发panic来终止程序。

Rc<T>相似,RefCell<T>只能被用于单线程场景中。强行将它用于多线程环境中会产生编译时错误。下面是选择使用Box<T>Rc<T>RefCell<T>的依据:

  • Rc<T>允许一份数据有多个所有者,而Box<T>RefCell<T>都只有一个所有者
  • Box<T>允许在编译时检查可变或不可变借用,Rc<T>仅允许编译时检查不可变借用,RefCell<T>允许运行时检查可变或不可变借用。
  • 由于RefCell<T>允许我们在运行时检查可变借用,所以即使RefCell<T>本身是不可变的,我们仍然能够更改其中存储的值。
rust 复制代码
pub trait Messager {
    fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: 'a + Messager> {
    messager: &'a T,
    value: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messager,
{
    pub fn new(messager: &T, value: usize) -> LimitTracker<T> {
        LimitTracker {
            messager: messager,
            value: value,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;
        self.messager.send("msg");
    }
}

这段代码的重点是Messager trait,它唯一的send方法可以接受self的不可变引用及一条文本消息作为参数。

示例的结构体同时使用生命周期标注和泛型约束,结构体实现关联函数new和方法set_value。紧跟impl后的尖括号声明指明结构体自身的约束。

下面对set_value做单元测试,mock一个实现了Message trait的结构体MockMessage进行验证,调用send方法会向结构体字段中写入值。

rust 复制代码
#[cfg(test)]
mod test {
    use super::*;

    struct MockMessage {
        sent_message: Vec<String>,
    }

    impl MockMessage {
        fn new() -> MockMessage {
            MockMessage {
                sent_message: vec![],
            }
        }
    }

    impl Messager for MockMessage {
        fn send(&self, msg: &str) {
            self.sent_message.push(String::from(msg));
        }
    }

    #[test]
    fn send_warning_message() {
        let mock_messager = MockMessage::new();
        let mut limit_tracker = LimitTracker::new(&mock_messager, 2);
        limit_tracker.set_value(10);
    }
}

编译器报错,send方法接收的self属于不可变引用,我们无法修改MockMessage中的内容。如果将send方法self修改为&mut self又不符合Messager trait约束。

在保持外部值不可变的前提下,使用RefCell<T>来修改内部存储的值。sent_message字段类型调整成RefCell<Vec<String>>,最后调整成下面这个样子:

rust 复制代码
#[cfg(test)]
mod test {
    use super::*;
    use std::cell::RefCell;

    struct MockMessage {
        sent_message: RefCell<Vec<String>>,
    }

    impl MockMessage {
        fn new() -> MockMessage {
            MockMessage {
                sent_message: RefCell::new(vec![]),
            }
        }
    }

    impl Messager for MockMessage {
        fn send(&self, msg: &str) {
            self.sent_message.borrow_mut().push(String::from(msg));
        }
    }

    #[test]
    fn send_warning_message() {
        let mock_messager = MockMessage::new();
        let mut limit_tracker = LimitTracker::new(&mock_messager, 2);
        limit_tracker.set_value(10);
    }
}

我们调用RefCell<Vec<String>>类型的borrow_mut方法来获取内部值的可变引用,接着,我们便可以在动态数据的可变引用上调用push方法来存入数据。

我们会在创建不可变引用和可变引用时分别使用语法&&mutRefCell<T>实现了borrowborrow_mut方法分别返回Ref<T>RefMut<T>这两种智能指针。由于这两种指针都实现了Deref,所以我们可以把它们当做一般的引用来对待。

RefCell<T>会记录当前存在多少个活跃的Ref<T>RefMut<T>智能指针,在任何一个给定的时间里,它只允许你拥有多个不可变借用和一个可变借用。当我们违背借用规则时,相比于一般引用导致的编译错误,RefCell<T>会在运行时触发panic

相关推荐
DongLi012 天前
rustlings 学习笔记 -- exercises/05_vecs
rust
番茄灭世神3 天前
Rust学习笔记第2篇
rust·编程语言
shimly1234563 天前
(done) 速通 rustlings(20) 错误处理1 --- 不涉及Traits
rust
shimly1234563 天前
(done) 速通 rustlings(19) Option
rust
@atweiwei3 天前
rust所有权机制详解
开发语言·数据结构·后端·rust·内存·所有权
shimly1234563 天前
(done) 速通 rustlings(24) 错误处理2 --- 涉及Traits
rust
shimly1234563 天前
(done) 速通 rustlings(23) 特性 Traits
rust
shimly1234563 天前
(done) 速通 rustlings(17) 哈希表
rust
shimly1234563 天前
(done) 速通 rustlings(15) 字符串
rust
shimly1234564 天前
(done) 速通 rustlings(22) 泛型
rust