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