Using Reference counted pointers in Rust
What they are
Reference counted pointers count the number of references to the object on the heap.
Each time a variable referencing the material moves out of scope, the Rc count decreases. When it hits zero, the object on the heap is then garbage collected.
Case study
Whe building a raytracer in Rust I had to specify the materials of objects.
To do so, I created a Material trait
and Material structs.
pub trait Material {
// Scatters incident rays
fn scatter (); // Definition is elided
}
pub struct Metal {
// Surface fuzziness
fuzz: f64,
}
impl Metal {
pub fn new (fuzz: f64) -> Self {
Self { fuzz }
}
}
impl Material for Metal {
fn scatter() { /* ... */ }
}
In the struct definitions I then declared a material
field which stored a Box
pointer to some Material on the heap.
An issue arose when multiple objects needed access to the same Material.
Taking a sphere object for example:
pub struct Sphere
{
// Some other fields have been elided
material: Rc<dyn Material>,
}
impl Sphere {
pub fn new(material: Box<dyn Material>) -> Self {
Sphere { material }
}
}
fn main () {
let metal_shiny = Box::new(Metal::new(0.0));
let sphere1 = Sphere::new(metal_shiny)
let sphere2 = Sphere::new(metal_shiny);
}
Since Box
pointers provided exclusive Rust ownership to the material
used, the material object couldn’t be referenced in the struct fields of other objects.
Since I only needed read access to materials, and shared ownership to the material
, I could use std::Rc
to wrap the trait instead:
// rustc use-rc.rs
use std::rc::Rc;
pub trait Material {
// Scatters incident rays
fn scatter (&self); // Definition is elided
}
pub struct Metal {
// Surface fuzziness
fuzz: f64,
}
impl Metal {
pub fn new (fuzz: f64) -> Self {
Self { fuzz }
}
}
impl Material for Metal {
fn scatter(&self) { /* ... */ }
}
pub struct Sphere
{
// Some other fields have been elided
material: Rc<dyn Material>,
}
impl Sphere {
pub fn new(material: Rc<dyn Material>) -> Self {
Sphere { material }
}
}
fn main () {
let metal_shiny = Rc::new(Metal::new(0.0));
let sphere1 = Sphere::new(metal_shiny.clone()); // Clone the pointer, incrementing the reference count
let sphere2 = Sphere::new(metal_shiny.clone()); // As above
}
You can view the gist here