Rc<T>
, the Reference Counted Smart Pointer
Rc,多引用小指针,可以让一个地址被多个对象引用,每多一个对象,引用个数+1
In the majority of cases, ownership is clear: you know exactly which variable owns a given value. However, there are cases when a single value might have multiple owners. For example, in graph data structures, multiple edges might point to the same node, and that node is conceptually owned by all of the edges that point to it. A node shouldn’t be cleaned up unless it doesn’t have any edges pointing to it.
To enable multiple ownership, Rust has a type called Rc<T>
, which is an abbreviation for reference counting. The Rc<T>
type keeps track of the number of references to a value to determine whether or not the value is still in use. If there are zero references to a value, the value can be cleaned up without any references becoming invalid.
Note that Rc<T>
is only for use in single-threaded scenarios.
Using Rc<T>
to Share Data
Each Cons
variant will now hold a value and an Rc<T>
pointing to a List
.
When we create b
, instead of taking ownership of a
, we’ll clone the Rc<List>
that a
is holding, thereby increasing the number of references from one to two and letting a
and b
share ownership of the data in that Rc<List>
.
use std::rc::Rc; pub enum List { Cons(i32, Rc<List>), Nil, } use crate::base::l1_rc::List::{Cons, Nil}; pub fn test() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); let b = Cons(3, Rc::clone(&a)); let c = Cons(4, Rc::clone(&a)); }
We’ll also clone a
when creating c
, increasing the number of references from two to three.
Every time we call Rc::clone
, the reference count to the data within the Rc<List>
will increase, and the data won’t be cleaned up unless there are zero references to it.
Rc::clone的作用只是增加传入地址被引用的个数,并返回这个地址,而不是复制传入的对象
We could have called a.clone()
rather than Rc::clone(&a)
, but Rust’s convention is to use Rc::clone
in this case. The implementation of Rc::clone
doesn’t make a deep copy of all the data like most types’ implementations of clone
do. The call to Rc::clone
only increments the reference count, which doesn’t take much time. Deep copies of data can take a lot of time.
查看引用个数
pub fn test2() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!("count after creating a = {}", Rc::strong_count(&a)); let b = Cons(3, Rc::clone(&a)); println!("count after creating b = {}", Rc::strong_count(&a)); { let c = Cons(4, Rc::clone(&a)); println!("count after creating c = {}", Rc::strong_count(&a)); } println!("count after c goes out of scope = {}", Rc::strong_count(&a)); }
count after creating a = 1 count after creating b = 2 count after creating c = 3 count after c goes out of scope = 2
RefCell<T>
and the Interior Mutability Pattern
Recall the borrowing rules:
- At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.
- References must always be valid.
With references and Box<T>
, the borrowing rules’ invariants are enforced at compile time. With RefCell<T>
, these invariants are enforced at runtime. With references, if you break these rules, you’ll get a compiler error. With RefCell<T>
, if you break these rules, your program will panic and exit.
Similar to Rc<T>
, RefCell<T>
is only for use in single-threaded scenarios and will give you a compile-time error if you try using it in a multithreaded context.
Here is a recap of the reasons to choose Box<T>
, Rc<T>
, or RefCell<T>
:
Rc<T>
enables multiple owners of the same data;Box<T>
andRefCell<T>
have single owners.Box<T>
allows immutable or mutable borrows checked at compile time;Rc<T>
allows only immutable borrows checked at compile time;RefCell<T>
allows immutable or mutable borrows checked at runtime.- Because
RefCell<T>
allows mutable borrows checked at runtime, you can mutate the value inside theRefCell<T>
even when theRefCell<T>
is immutable.
Mutating the value inside an immutable value is the interior mutability pattern.
Using RefCell<T>
is one way to get the ability to have interior mutability.
pub 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!"); } } } #[cfg(test)] mod tests { use super::*; use std::cell::RefCell; 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, message: &str) { self.sent_messages.borrow_mut().push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { // --snip-- 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); } }
self是不可变引用,本来是无法修改其内容的,但sent_messages是RefCell<Vec<String>>,就可以通过调用sent_messages.borrow_mut()来改变了
fn send(&self, message: &str) { self.sent_messages.borrow_mut().push(String::from(message)); }