CS3984 Computer Systems in Rust



References Recap

References: At a single point in time, we can either have

  • One mutable reference
  • Any number of immutable references

a reframing of the same concept gives us

References: At a single point in time, we can either have

  • One exclusive access
  • Any number of shared accesses

The exclusive access provides mutability, while shared access provides aliasing.

Key tenet of Rust: Aliasing XOR Mutability

Ownership Recap

Exclusive access (mutability)

let a = Box::new(5);
let mut b = a;
*b = 6;


let mut a = 5;
let b = &mut a;
*b = 6;


Shared access (aliasing)

?

let a = 5;
let b = &a;
let c = &a;


Rc

  • A single-threaded, reference-counting pointer
  • Allocates contents + reference counts on the heap
  • Inner value is dropped when the last strong reference is dropped
use std::rc::{Rc, Weak};

fn main() {
    let x = Rc::new(1);

    let y: Rc<i32> = Rc::clone(&x); // x.clone()
    let z: Weak<i32> = Rc::downgrade(&x);

    let zz: Option<Rc<i32>> = z.upgrade();
    zz.unwrap();
}


Rc (2)

use std::rc::Rc;

struct Tv;
impl Drop for Tv { fn drop(&mut self) { println!("Turning off TV!"); } }

struct Watcher {
    name: &'static str,
    tv: Rc<Tv>,
}
impl Drop for Watcher {
  fn drop(&mut self) { println!("{} stopped watching", self.name); }
}

fn main() {
    let tv = Rc::new(Tv);
    let watcher_1 = Watcher { name: "John", tv: Rc::clone(&tv) };
    drop(tv);
    let watcher_2 = Watcher{ name: "Jim", tv: Rc::clone(&watcher_1.tv) };
}


Interior Mutability

What if we want Aliasing AND Mutability? Meaning, what if we want to be able to mutate data behind a shared reference &T?

  1. Cell<T>
    • Interior mutability by moving values in and out of the cell
  2. RefCell<T>
    • Interior mutability by tracking borrows at runtime

Note: These data structures are not thread safe, we will see thread-safe analogs in future weeks.

Cell

Observation: You can mutate the contents of a Cell through an immutable reference as much as you want, as long as there are no references to its contents.

pub const fn new(value: T) -> Cell<T>


Now, there is no longer a way to get a &T or a &mut T from a &Cell<T>.

pub fn set(&self, val: T);
pub fn get(&self) -> T;  // T: Copy
pub fn take(&self) -> T;  // T: Default


The following is A-OK because it requires a &mut Cell<T>.

pub fn get_mut(&mut self) -> &mut T


Cell (2)

use std::cell::Cell;

struct SomeStruct {
    regular_field: u8,
    special_field: Cell<u8>,
}

fn main() {
    let my_struct = SomeStruct { regular_field: 0, special_field: Cell::new(0) };

    // Err: cannot mutate immutable variable `my_struct` [E0384]
    //      cannot assign to `my_struct.regular_field`, as `my_struct` is not
    //      declared as mutable
    // my_struct.regular_field = 100;

    my_struct.special_field.set(100);
    assert_eq!(my_struct.special_field.get(), 100);
}


Cell (3)

The Rc type needs to update reference counts on clone:

fn clone(&self) -> Rc<T, A>


Interior mutability is necessary:

pub struct Rc<T: ?Sized, A: Allocator = Global> {
    ptr: NonNull<RcBox<T>>,
    phantom: PhantomData<RcBox<T>>,
    alloc: A,
}

struct RcBox<T: ?Sized> {
    strong: Cell<usize>,
    weak: Cell<usize>,
    value: T,
}


RefCell

Idea: Track borrows at runtime.

pub struct RefCell<T: ?Sized> {
    borrow: Cell<BorrowFlag>,
    borrowed_at: Cell<Option<&'static crate::panic::Location<'static>>>,
    value: UnsafeCell<T>,
}

type BorrowFlag = isize;
const UNUSED: BorrowFlag = 0;

#[inline(always)]
fn is_writing(x: BorrowFlag) -> bool {
    x < UNUSED
}

#[inline(always)]
fn is_reading(x: BorrowFlag) -> bool {
    x > UNUSED
}


RefCell (2)

Immutable/mutable access to content must be requested:

// These methods panic if borrowing rules are violated
pub fn borrow(&self) -> Ref<'_, T>;
pub fn borrow_mut(&self) -> RefMut<'_, T>;

// These methods return an error if borrowing rules are violated
pub fn try_borrow(&self) -> Result<Ref<'_, T>, BorrowError>;
pub fn try_borrow_mut(&self) -> Result<RefMut<'_, T>, BorrowMutError>;


  • Ref implements Deref<Target = T>
  • RefMut implements Deref<Target = T> and DerefMut<Target = T>

RefCell (3)

use std::cell::RefCell;

fn main() {
    let cell = RefCell::new(100);

    let mut one = cell.borrow_mut();
    *one = 5;
    println!("{one}");
    // drop(one);

    let mut two = cell.borrow_mut();
    *two = 10;
    println!("{two}");
    // drop(two);

    let three = cell.borrow();
    let four = cell.borrow();
    println!("{three} and {four}");
}


Rc and RefCell

Pros:

  • Multiple ownership
  • Interior mutability tracked at runtime

Cons:

  • Small overhead
  • Lack of compile-time guarantees

Question: What is the difference between Rc<RefCell<T>> and RefCell<Rc<T>>?

Reference Cycles Leak Memory

use std::cell::RefCell;
use std::rc::Rc;

struct Leak {
    other: RefCell<Option<Rc<Leak>>>,
}

fn main() {
    let a = Rc::new(Leak {
        other: RefCell::new(None),
    });

    let b = Rc::new(Leak {
        other: RefCell::new(None),
    });

    *a.other.borrow_mut() = Some(Rc::clone(&b));
    *b.other.borrow_mut() = Some(a);
}


Aside: Motivation of Cell

Why can’t we simultaneously have single-threaded mutation while holding references?

fn main() {
  let mut x = 5;

  let r1 = &x;

  let mut r2 = &mut x;
  *r2 = 6;

  println!("*r1 = {}", *r1);
}


Why shouldn’t this work? Aka, why can’t this work, and the output is *r1 = 6?

Aside: Motivation of Cell (2)

struct Wrapper {
  inner: String,
}

fn main() {
  let mut w = Wrapper { inner: String::from("hello!") };

  let rs: &str = &w.inner;

  w.inner = String::from("oops");

  println!("*rs = {}", *rs);
}


Oops, dangling pointer.

Aside: Motivation of Cell (3)

use std::cell::Cell;

struct Wrapper {
  inner: Cell<String>,
}

fn main() {
  let w = Wrapper { inner: Cell::new(String::from("hello!")) };

  // let rs: &str = &w.s;
  let rs: &Cell<String> = &w.inner;

  w.inner.set(String::from("oops"));

  println!("1. rs = {}", rs.take());
  println!("2. rs = {}", rs.take());
}


Aside: Motivation of Cell (4)

use std::cell::RefCell;

struct Wrapper {
  inner: RefCell<String>,
}

fn main() {
  let w = Wrapper { inner: RefCell::new(String::from("hello!")) };

  // let rs: &str = &w.s;
  let rs: &str = &w.inner.borrow();

  *w.inner.borrow_mut() = String::from("oops");

  println!("rs = {}", rs);
}