b = 5
Performant Software Systems with Rust — Lecture 11
What is a Pointer?
A pointer is a variable that contains an address in memory — it points to some other data
In Rust, a pointer is a reference, indicated by &, which borrows the value it points to
Learn more: Smart Pointers
What is a Smart Pointer?
A smart pointer is a data structure that acts like a pointer, but also has additional metadata and capabilities
struct, and implements Deref and Drop traitsExamples of smart pointers we used
StringVec<T>Learn more: Smart Pointers
Box<T>
Stores data on the heap, and the pointer on the stack
Zero performance overhead, but no additional capabilities either
Box<T> in three situations:
Learn more: Using Box
Using Box<T> to Store Data on the Heap
b = 5
Learn more: Using Box
When a Box Goes Out Of Scope
Learn more: Using Box
Trait Objects
Recall that we can use an enum to store different types of data in each cell, while still having a vector of these cells
There Is a Problem with Our Approach
The types of cells in the vector are fixed
But sometimes we are writing a library (crate) to be used by others, and want our library user to be able to extend the set of types
For example, in a graphical UI, we wish the vector to store all the objects that can draw themselves
Towards Using Trait Objects
Let’s say we wish to implement a vector that contains animals that can make a sound in our library
But the types of these animals are to be defined by the users of our library
We first define a trait
Towards Using Trait Objects
A user of our library defines two concrete types: Dog and Cat
These types are of different sizes
Placing Trait Objects in Vectors
Will it compile successfully?
Live Demo
Correct Solution
Why Boxing Trait Objects?
Trait objects are dynamically sized types (DSTs) — we don’t know their sizes at compile-time
But the T in Vec<T> must implement the Sized trait
Sized traitBoxed trait objects have known sizes — we know the size of a pointer!
Generic Types Implement Sized
unless we explicitly opt out:
Learn more: Dynamically Sized Types and the Sized Trait
Dynamically Sized Types and Wide Pointers
str is a dynamically sized type
&str, however, has a known size
str and its lengthLearn more: Dynamically Sized Types and the Sized Trait
Wide Pointers for Slices
str or [T], is simply a view into some continuous data, such as a vectorWide Pointers for Trait Objects
dyn Animal, consists of a data pointer and a vtable pointer
T) that the trait object is storingvtable is a struct of function pointers, pointing to the concrete piece of machine code for each methodLearn more: Trait Objects
Back to Box<T>
Implements the Deref trait, allowing its values to be treated like references
Implements the Drop trait, allowing the memory it boxes to be deallocated
Box<T> Implements the Deref Trait
Learn more: Using Box<T> Like a Reference
Defining Our Own Simple Box
Can it be dereferenced?
Learn more: Defining Our Own Smart Pointer
Implementing the Deref Trait
Deref Coercion
Hello, Rust!
Deref Coercion and the DerefMut Trait
&T to &U when T: Deref<Target=U>&mut T to &mut U when T: DerefMut<Target=U>&mut T to &U when T: Deref<Target=U>Learn more: How Deref Coercion Interacts with Mutability
The Drop Trait
By implementing the drop method (that takes &mut self) in the Drop trait, you specify the code to run when a value goes out of scope
Learn more: Running Code on Cleanup with the Drop Trait
Dropping a Value Early by with std::mem::drop
Live Demo
Learn more: Dropping a Value Early with std::mem::drop
Rc<T> — The Reference Counted Smart Pointer
Arc<T>Learn more: Rc<T>, the Reference Counted Smart Pointer
use std::rc::Rc; // remember to import Rc
fn main() {
// Create a new reference-counted string
let first = Rc::new(String::from("Hello"));
println!("Rc after creation: {}", Rc::strong_count(&first));
{
// Create a second reference to the same data
let second = Rc::clone(&first);
println!("Rc after clone: {}", Rc::strong_count(&first));
// Both references can read the data
println!("First: {}", *first);
println!("Second: {}", *second);
} // second is dropped here
// Reference count decreases when second goes out of scope
println!("Rc after second is dropped: {}",
Rc::strong_count(&first));
}Learn more: Rc<T>, the Reference Counted Smart Pointer
RefCell<T> and the Interior Mutability Pattern
RefCell<T>Learn more: RefCell<T> and the Interior Mutability Pattern
RefCell<T>
Rc<T>Rc for shared mutable state in single-threaded contextsArc<Mutex<T>> instead of Rc<RefCell<T>> in multi-threaded contextsLearn more: RefCell<T> and the Interior Mutability Pattern
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
// Create a RefCell wrapped in an Rc for shared mutable access
let counter = Rc::new(RefCell::new(0));
// Create multiple references to the same data
let counter_ref1 = Rc::clone(&counter);
let counter_ref2 = Rc::clone(&counter);
// Modify the value through the first reference
*counter_ref1.borrow_mut() += 1;
// `borrow()` returns a Ref<T> which derefs to &T
println!("After first modification: {}", counter_ref1.borrow());
// Modify the value through the second reference
*counter_ref2.borrow_mut() += 2;
println!("After second modification: {}", counter_ref2.borrow());
// Demonstrate runtime borrowing rules
// `borrow_mut()` returns a RefMut<T> which derefs to &mut T
let mut first_borrow = counter.borrow_mut();
*first_borrow += 10;
// This would panic at runtime (uncomment to see)
// since `counter` is already mutably borrowed!
// let _second_borrow = counter.borrow_mut();
drop(first_borrow); // Release the mutable borrow
// Now we can borrow again
println!("Final value: {}", counter.borrow());
}Live Demo
Learn more: RefCell<T> and the Interior Mutability Pattern
The Rust Programming Language, Chapter 15.1 – 15.5, 18.2, 20.3
Rust for Rustaceans, Jon Gjenset, Chapter 2 (Types)