Shiklo Tools
PDF40
Image20
Calculators & Converters25
Text & Writing13
Developer26
Video14
Audio14
Document14
Data & Spreadsheet12
Web & SEO9
OCR & Scanning5
Archive & File12
Security & Privacy23
Health & Medical29
QR & Barcode5

Rust Smart Pointers

A pointer is a value that tells you where some other data lives in memory. References like &x are the simplest kind. Smart pointers are pointers with extra abilities: they own the data they point to, and they clean it up when they are done. You have already used two of them without the name: String and Vec are both smart pointers to text and items stored on the heap, the part of memory for data whose size is decided while the program runs.

This topic introduces three smart pointers you will meet in real Rust code. You do not need them often as a beginner, but it is good to know what they are for.

Box: a value on the heap

Box<T> puts a single value on the heap and keeps a pointer to it. Using a boxed value feels just like using the value itself:

fn main() {
    let boxed = Box::new(41);
    let answer = *boxed + 1;
    println!("{} and {}", boxed, answer);
}
Output
41 and 42

The main reason to use a Box is a type that contains itself. A list where each item points to the next one cannot have a fixed size, because it could be any length. Putting the next item in a Box gives it a known size, a single pointer:

enum List {
    Item(i32, Box<List>),
    End,
}

fn total(list: &List) -> i32 {
    match list {
        List::Item(value, rest) => value + total(rest),
        List::End => 0,
    }
}

fn main() {
    let list = List::Item(3, Box::new(List::Item(4, Box::new(List::Item(5, Box::new(List::End))))));
    println!("The list adds up to {}", total(&list));
}
Output
The list adds up to 12

Rc: more than one owner

Normally every value has exactly one owner. Sometimes several parts of a program genuinely need to share one value, like several people sharing one family car. Rc<T>, for reference counted, keeps count of how many owners there are, and frees the value only when the last one is gone:

use std::rc::Rc;

fn main() {
    let car = Rc::new(String::from("blue hatchback"));
    let for_mum = Rc::clone(&car);
    let for_dad = Rc::clone(&car);
    println!("Everyone shares the {}", for_mum);
    println!("Owners: {}", Rc::strong_count(&car));
    drop(for_dad);
    println!("Owners after one gives it up: {}", Rc::strong_count(&car));
}
Output
Everyone shares the blue hatchback
Owners: 3
Owners after one gives it up: 2

Rc::clone does not copy the string; it only adds one to the count, which is very cheap.

RefCell: changing shared data

An Rc only gives read access, because many owners changing the same data at once would break the borrowing rules. RefCell<T> moves the borrow check from compile time to run time: you ask with borrow_mut() while the program runs, and it panics if the rules would be broken. Together, Rc<RefCell<T>> gives you shared data that can change:

use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let scoreboard = Rc::new(RefCell::new(Vec::new()));
    let team_a = Rc::clone(&scoreboard);
    let team_b = Rc::clone(&scoreboard);
    team_a.borrow_mut().push("A scores");
    team_b.borrow_mut().push("B scores");
    team_a.borrow_mut().push("A scores again");
    println!("{:?}", scoreboard.borrow());
}
Output
["A scores", "B scores", "A scores again"]

Which one when?

Use plain values and references first; they are the simplest and fastest. Reach for Box for recursive types, Rc when ownership truly has to be shared, and RefCell inside an Rc when that shared data must change. If you find yourself using them everywhere, it is often a sign that the program's structure could be simpler.

Practice exercises

  1. Box a String and print its length through the box.
  2. Add a function count(list: &List) -> u32 that counts the items of the boxed list.
  3. Share one Rc<String> between three variables and print the count after each drop.