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
?
-
Cell<T>
- Interior mutability by moving values in and out of the cell
-
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
implementsDeref<Target = T>
-
RefMut
implementsDeref<Target = T>
andDerefMut<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);
}