CS3210 Design Operating Systems

Taesoo Kim





Rust 7: Interior Mutability

Taesoo Kim

Administrivia

Administrivia

Administrivia

Q&A

It seems the entirety of the Rust standard library is built on top of unsafe code. Granted I understand why this is the case, I don’t understand why we can call Rust “safe.” No matter how structurally sound you build a building, if you build its foundation on unstable ground the building cannot truly be safe.

Agenda

(Again) Safety rule

Example

// in C++
std::string s = "Hello";
const char *c = s.c_str();

s.append(" World!");

std::cout << c;
// in Rust
let mut s = String::from("Hello");
let c = s.as_str();
    
s.push_str(" World!");

dbg!(c);

More examples

Mutability in Rust

struct S {
  e1: u32,
  e2: u32,
}

let mut s = S { e1: 1, e2: 2 };
//  ---> determins mutability of all elements of struct S

change_me(&mut s);

Rc: Reference counted smart pointer

let r1 = Rc::new(1);
let r2 = Rc::clone(&r1);

dbg!(r1);
dbg!(r2);

Rc: Reference counted smart pointer

// simplified
pub struct Rc<T> {
  ptr: NonNull<RcBox<T>>
  phantom: PhantomData<T>,
}

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

// note: &self not &mut self
fn clone(&self) -> Rc<T> {
  self.strong.set(self.strong + 1); // Q. guess?
  Rc { ptr: self.ptr }
}

Introducing Cell

pub fn get(&self) -> T;
pub fn set(&self, val: T);
pub fn replace(&self, val: T) -> T;

// example:
struct RcBox<T> {
  strong: Cell<usize>, // mutable!
  weak: Cell<usize>,   // mutable!
  value: T,
}

Example: Cell interfaces

let c = Cell::new(10);
let v1 = c.get();

c.set(v1 + 10);

let v2 = c.get(); // v1 != v2
dbg!(v2);         //=> 20

Wait, Q&A

Doesn’t Cell defeat the entire purpose of Rust (to provide simpler memory safety)? In addition, if not, then why isn’t Cell used for all mutable pointers when multiple mutable pointers attempt to access the same memory location?

Wait, Q&A

// in Rust
let mut s = String::from("Hello");
let c = s.as_str(); // Q?
    
s.push_str(" World!"); // Q?

dbg!(c); // Q?

Thinking of Cell

Note, not thread safe to access them concurrently, more on this topic later

Example implementation

impl<T> Cell<T> {
  pub fn set(&self, val: T) {
    let old = self.replace(val);
    drop(old);
  }

  pub fn replace(&self, val: T) -> T {
    // Q. Safety is well-justified?
    mem::replace(unsafe { &mut *self.value.get() }, val)
  }
}

Thinking of “Interior mutability”

Core type: UnsafeCell

#[repr(transparent)] // Q?
pub struct Cell<T: ?Sized> {
    value: UnsafeCell<T>,
}

#[lang = "unsafe_cell"]
#[repr(transparent)]
pub struct UnsafeCell<T: ?Sized> {
    value: T,
}

pub const fn get(&self) -> *mut T {
  // We can just cast the pointer from `UnsafeCell<T>` to `T` because of
  // #[repr(transparent)]
  self as *const UnsafeCell<T> as *const T as *mut T
}

RefCell: dynamically checked borrowing rules

let c = RefCell::new(10);
let s1 = c.borrow();
let s2 = c.borrow();

let mut m1 = c.borrow_mut(); // Q. what should happen?

Thinking of “Interior mutability”

Bonus, what if we wait instead of panicing?

  1. What if there exists only one outstanding sharable/immutable reference at runtime?
  2. Is the safety rule correctly enforced at runtime?
  3. Thus, is it safe to let immutable variables modifiable?

Summary

Note. limited our discussion to a single thread context

Compositing Rc and Cell

    let r = Rc::new(Cell::new(10));

    let c1 = Rc::clone(&r);
    let c2 = Rc::clone(&r);

    c1.set(20);
    c2.set(30);

    dbg!(c1); //=> Q?
    dbg!(c2); //=> Q?

“Interior Mutability” Patterns

Ref. Interior mutability patterns

References