龙空技术网

Rust学习笔记(四十七)Rc(引用计数)以及RefCell和内部可变性

凡事不平凡coding 134

前言:

今天咱们对“c语言rc”大致比较关注,看官们都需要分析一些“c语言rc”的相关资讯。那么小编同时在网络上搜集了一些有关“c语言rc””的相关内容,希望兄弟们能喜欢,朋友们一起来了解一下吧!

Rc<T>引用计数智能指针

有时一个值会有多个所有者,比如下面一个图的数据结构,其中6这个节点就被多个边与其它节点连接:

为了支持多重所有权:就有了Rc<T>(reference counting 引用计数)

它可以追踪到某个值的所有引用如果有0个引用,意味着该值可以被清理Rc<T>使用场景

需要在堆上分配数据,但是这些数据会被程序的多个部分读取(只读),但是在编译时无法确定哪个部分最后使用完这些数据。Rc<T>只能用于单线程场景。

Rc<T>不在预导入模块Rc::clone(&a)函数:会增加引用计数Rc::strong_count(&a):获得强引用计数Rc::weak_count(&a):获得弱引用计数

例:两个List共享另一个List的所有权

enum List {    Cons(i32, Rc<List>),    Nil,}use crate::List::{Cons, Nil};use std::rc::Rc;fn main() {    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));    println!("count after creating a = {}", Rc::strong_count(&a));//1,只有a    //Rc::clone只增加引用计数,不执行深copy    let b = Cons(3, Rc::clone(&a));    println!("count after creating b = {}", Rc::strong_count(&a));//2,有a和b    {        let c = Cons(4, Rc::clone(&a));        println!("count after creating c = {}", Rc::strong_count(&a));//3,a、b、c    }    println!("count after c goes out of scope = {}", Rc::strong_count(&a));//2,因为c离开作用域,只剩a、b}
Rc::clone()和类型的clone()方法

Rc::clone()只增加引用,不会执行数据的深度拷贝操作 类型的clone()方法:很多都会执行数据的深度拷贝操作(取决于各类型clone方法的具体实现)

Rc<T>通过不可变引用,使得我们可以在程序的不同部分之间共享只读数据。如果要修改数据,需要通过下面的RefCell和内部可变性来实现。

内部可变性

内部可变性是Rust的设计模式之一。它允许你在只持有不可变引用的前提下对数据进行修改。需要在数据结构中使用unsafe代码来绕过Rust正常的可变性和借用规则。

RefCell<T>

与Rc<T>不同,RefCell<T>类型代表了其持有数据的唯一所有权。

借用规则:

在任何给定的时间里,我们要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用。引用必须总是有效的RefCell<T>与Box<T>的区别

Box<T>

RefCell<T>

编译阶段强制代码遵守借用规则,否则编译错误

只会在运行时检查借用规则,如果运行时不满足借用规则,则触发panic

借用规则在不同阶段进行检查的差异

编译阶段

运行时

尽早暴露问题,没有运行时开销,对大多数场景是最佳选择,是Rust的默认行为

问题暴露延后,因借用计数产生些许性能损失,实现某些特定的内存安全场景(不可变环境中修改自身数据)

RefCell<T>也只能用于单线程场景。

Box<T>、Rc<T>、RefCell<T>的依据

Box<T>

Rc<T>

RefCell<T>

同一数据的所有者

一个

多个

一个

可变性、借用检查

可变,不可变借用(编译时检查)

不可变借用(编译时检查)

可变、不可变借用(运行时检查)

其中:即便RefCell<T>本身不可变,但我们仍能修改其中存储的值。

内部可变性:可变的借用一个不可变的值

例:

fn main() {    let x = 5;    let y = &mut x;}

正常情况下,我们不可以把不可变的值借用为可变引用,如以上代码就会报错。

内部可变性的用例:mock 对象

在其它语言中,一般都有Mock对象,可以用来在测试中代替某个对象。但是Rust标准库中没有提供内建mock对象的功能。但是我们可以创建一个与其功能类似的结构体。

如下是一个我们想要测试的场景:我们在编写一个记录某个值与最大值的差距的库,并根据当前值与最大值的差距来发送消息。例如,这个库可以用于记录用户所允许的 API 调用数量限额。

该库只提供记录与最大值的差距,以及何种情况发送什么消息的功能。使用此库的程序则期望提供实际发送消息的机制:程序可以选择记录一条消息、发送 email、发送短信等等。库本身无需知道这些细节;只需实现其提供的 Messenger trait 即可。它有这么一个api,接收一个值,根据这个值与最大值的差值决定是否发送消息。这个api没有返回值,所以无法通过返回值来测试。

//src/lib.rspub trait Messenger {    fn send(&self, msg: &str);}pub struct LimitTracker<'a, T: Messenger> {    messenger: &'a T,    value: usize,    max: usize,}impl<'a, T> LimitTracker<'a, T>    where T: Messenger {    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {        LimitTracker {            messenger,            value: 0,            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.messenger.send("Error: You are over your quota!");        } else if percentage_of_max >= 0.9 {             self.messenger.send("Urgent warning: You've used up over 90% of your quota!");        } else if percentage_of_max >= 0.75 {            self.messenger.send("Warning: You've used up over 75% of your quota!");        }    }}

我们可以在测试中定义一个 MockMessenger 结构体,其 sent_messages 字段为一个 String 值的 Vec 用来记录被告知发送的消息。我们还定义了一个关联函数 new 以便于新建从空消息列表开始的 MockMessenger 值。接着为 MockMessenger 实现 Messenger trait 这样就可以为 LimitTracker 提供一个 MockMessenger。在 send 方法的定义中,获取传入的消息作为参数并储存在 MockMessenger 的 sent_messages 列表中。这样只需要检查列表中有没有要发送的消息,就知道被测代码有没有正常执行。

//src/lib.rs#[cfg(test)]mod tests {    use super::*;    struct MockMessenger {        sent_messages: Vec<String>,    }    impl MockMessenger {        fn new() -> MockMessenger {            MockMessenger {                sent_messages: vec![],            }        }    }    impl Messenger for MockMessenger {        fn send(&mut self, msg: &str) {            self.sent_messages.push(String::from(msg));        }    }    #[test]    fn it_sends_an_over_75_percent_warning_message() {        let mock_messenger = MockMessenger::new();        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);        limit_tracker.set_value(80);        assert_eq!(mock_messenger.sent_messages.len(), 1);    }}

以上代码无法编译,因为Messenger的send方法要的是不可变的参数,而我们要在MockMessenger的send方法中记录发送的消息,所以我们在MockMessenger的实现中需要可变的参数。这时候可以使用RefCell。

mod tests {    use std::cell::RefCell;    use super::*;    struct MockMessenger {        sent_messages: RefCell<Vec<String>>,    }    impl MockMessenger {        fn new() -> MockMessenger {            MockMessenger {                sent_messages: RefCell::new(vec![]),            }        }    }    impl Messenger for MockMessenger {        fn send(&self, msg: &str) {            self.sent_messages.borrow_mut().push(String::from(msg));        }    }    #[test]    fn it_sends_an_over_75_percent_warning_message() {        let mock_messenger = MockMessenger::new();        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);        limit_tracker.set_value(80);        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);    }}

运行测试,通过。

RefCell<T>的两个方法(安全接口):

borrow方法:返回智能指针Ref<T>,它实现了Derefborrow_mut方法:返回RefMut<T>,它实现了Deref

RefCell<T>会记录当前存在多少个活跃的Ref<T>和RefMut<T>智能指针:

每次调用borrow:不可变借用计数加1任何一个Ref<T>的值离开作用域时被释放:不可变借用计数减1每次调用borrow_mut:可变借用计数加1任何一个RefMut<T>的值离开作用域时被释放:可变借用计数减1

Rust就是这样来维护借用检查规则:任何一个给定时间里,只允许拥有多个不可变借用或一个可变借用。即使用了RefCell<T>,也只是将借用检查延迟到运行时,如果运行时违反了这个规则,就会panic。

将Rc<T>和RefCell<T>结合使用来实现一个拥有多重所有权的可变数据

//src/main.rs#[derive(Debug)]enum List {    Cons(Rc<RefCell<i32>>, Rc<List>),    Nil,}use crate::List::{Cons, Nil};use std::rc::Rc;use std::cell::RefCell;fn main() {    let value = Rc::new(RefCell::new(5));    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));    let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));    let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));    *value.borrow_mut() += 10;    println!("a after = {:?}", a);    println!("b after = {:?}", b);    println!("c after = {:?}", c);}
其它可实现内部可变性的类型

Cell<T>:通过复制来访问数据 Mutex<T>:用于跨线程情况下的内部可变性模式

标签: #c语言rc