RefCell<T> and internal variability mode
The previous section introduced Rc
, which counts references to data, but references are immutable. The RefCell
reference described in this section has interior mutability, which allows us to modify the data with only immutable references.
Internal variability: Variable borrowing of an immutable value
Normally we would fail to compile an immutable reference to an immutable reference:
let x = 5;
let y = &mut x; // Error, cannot borrow immutable variable as mutable variable
Copy the code
But we sometimes need a value that remains externally immutable while being able to modify itself inside a method. Other than the method of the value itself, the rest of the code still cannot modify the value. One way to achieve this internal variability is to use RefCell<T>.
RefCell<T> does not completely circumvent the borrowing rule
We passed the borrow check at compile time using internal variability, but the borrow check was only postponed to run, and if the borrow rule was violated, panic! .
For example, if we want to implement an observer pattern, the teacher can send a notification to all students:
/ / student trait
pub trait Student {
// Used to receive teacher messages, note that &self is an immutable reference
fn on_message(&self, msg: &str);
}
// Teacher structure
pub struct Teacher<'a, T: Student> {
// Store the teacher's attention students
students: VecThe < &'a T>,
}
// Implement some methods for teachers
impl<'a, T: Student> Teacher<'a, T> {
// Create a teacher
pub fn new() -> Teacher<'a, T> {
Teacher {
students: vec![].}}// Focus on a student
pub fn attach(&mut self, student: &'a T) {
self.students.push(student)
}
// Inform all students
pub fn notify(&self, msg: &str) {
for student in self.students.iter() {
// Call the on_message method for all students
student.on_message(msg)
}
}
}
Copy the code
Implement the business logic according to the above definition:
// Define a boy type first
struct Boy {
// Record the message sent by the teacher
messages: Vec<String>,}impl Boy {
// Implement a method to create a boy
fn new() -> Boy {
Boy {
messages: vec![]}}}// Implement a trait for boys
impl Student for Boy {
fn on_message(&self, message: &str) {
// After receiving the teacher's message, save it
self.messages.push(String::from(message)); Messages cannot be used as a mutable reference because &self is an immutable reference}}// Create a teacher
let mut teacher = Teacher::new();
// Create a student
let student = Boy::new();
// The teacher pays attention to the student
teacher.attach(&student);
// The teacher informs all the students
teacher.notify("Class");
println!("Number of messages students received: {}", student.messages.len());
Copy the code
Self. messages requires a mutable reference to self, so try changing the on_message argument &self:
fn on_message(&mut self, message: &str) { The on_message method has an incompatible type because the signature in the Student trait indicates that &self is immutable
self.messages.push(String::from(message));
}
Copy the code
Because self is defined as an immutable reference in the Student trait’s on_message signature, this change is incompatible with the signature.
Internal variability is achieved using RefCell<T>
Use the above code to change the definition of Boy using RefCell<T> :
use std::cell::RefCell; // from the standard library
struct Boy {
messages: RefCell<Vec<String> >,// Change the type of messages
}
impl Boy {
fn new() -> Boy {
Boy {
messages: RefCell::new(vec![]) // Save the veC in the RefCell}}}impl Student for Boy {
fn on_message(&self, message: &str) { Self is still an immutable reference
// Borrow messages of variable reference type at run time
self.messages.borrow_mut().push(String::from(message)); }}let mut teacher = Teacher::new();
let student = Boy::new();
teacher.attach(&student);
teacher.notify("Class");
// We need to borrow the length of the internal array
println!("Number of messages students received: {}", student.messages.borrow().len()); / / 1
Copy the code
Use RefCell<T> to record borrowing information at run time
Use the syntax & and &mut when creating immutable and mutable references, respectively. For RefCell<T>, the borrow and borrow_mut methods need to be used to achieve similar functions.
RefCell<T> also follows the borrowing rule
RefCell<T> maintains the same borrowing check rules as the compiler based on this technique: it only allows you to have multiple immutable borrows or one mutable borrows at any given time.
fn on_message(&self, message: &str) {
self.messages.borrow_mut().push(String::from(message));
// panic! Mutable references cannot be borrowed more than once
self.messages.borrow_mut().push(String::from(message));
}
Copy the code
Use Rc<T> with RefCell<T> to implement a variable data with multiple ownership
Once we have Rc<T> with multiple immutable references and RefCell<T> with mutable internal references, we can compose multiple reference mutable data types:
#[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() {
// use Rc to wrap an internally variable value of 5
let value = Rc::new(RefCell::new(5));
// The a node references value
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
// Node B references node A
let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
// node C references node A
let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));
// variable borrow and change the value of value
Rc
is dereferenced to RefCell
.
*value.borrow_mut() += 10;
println!("a after = {:? }", a);
// a after = Cons(RefCell { value: 15 }, Nil)
println!("b after = {:? }", b);
// b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
println!("c after = {:? }", c);
// c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
}
Copy the code
After changing the values shown above, the values in ABC are all changed.
Box<T>, Rc<T>, RefCell<T>
- Rc
allows a piece of data to have multiple owners, whereas Box
and RefCell
both have only one owner.
- Box
allows mutable or immutable borrows checked at compile time, Rc
only allows immutable borrows checked at compile time, and RefCell
allows mutable or immutable borrows checked at run time.
- Because the RefCell
allows us to check for mutable borrowing at run time, we can still change the values stored in the RefCell
even if it itself is immutable.