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.