Design Trade-offs

This section is about design trade-offs in Rust. To be an effective Rust engineer, it's not enough just to know how Rust works. You have to decide which of Rust's many tools are appropriate for a given job. In this section, we will give you a sequence of quizzes about your understanding of design trade-offs in Rust. After each quiz, we will explain in-depth our rationale for each question.

Here's an example of what a question will look like. It will start out by describing a software case study with a space of designs:

Context: You are designing an application with a global configuration, e.g. containing command-line flags.

Functionality: The application need to pass immutable references to this configuration throughout the application.

Designs: Below are several proposed designs to implement the functionality.

use std::rc::Rc;
use std::sync::Arc;

struct Config { 
    flags: Flags,
    // .. more fields ..
}

// Option 1: use a reference
struct ConfigRef<'a>(&'a Config);

// Option 2: use a reference-counted pointer
struct ConfigRef(Rc<Config>);

// Option 3: use an atomic reference-counted pointer
struct ConfigRef(Arc<Config>);

Given just the context and key functionality, all three designs are potential candidates. We need more information about the system goals to decide which ones make the most sense. Hence, we give a new requirement:

Select each design option that satisfies the following requirement:

Requirement: The configuration reference must be sharable between multiple threads.

Answer:

Option 1
Option 2
Option 3

In formal terms, this means that ConfigRef implements Send and Sync. Assuming Config: Send + Sync, then both &Config and Arc<Config> satisfy this requirement, but Rc does not (because non-atomic reference-counted pointers are not thread-safe). So Option 2 does not satisfy the requirement, while Option 3 does.

We might also be tempted to conclude that Option 1 does not satisfy the requirement because functions like thread::spawn require that all data moved into a thread can only contain references with a 'static lifetime. However, that does not rule out Option 1 for two reasons:

  1. The Config could be stored as a global static variable (e.g., using OnceLock), so one could construct &'static Config references.
  2. Not all concurrency mechanisms require 'static lifetimes, such as thread::scope.

Therefore the requirement as-stated only rules out non-Send types, and we consider Options 1 and 3 to be the correct answers.


Now you try with the questions below! Each section contains a quiz focused on a single scenario. Complete the quiz, and make sure to read the answer context after each quiz. These questions are both experimental and opinionated — please leave us feedback via the bug button 🐞 if you disagree with our answers.

Along with each quiz, we have also provided links to popular Rust crates that served as inspiration for the quiz.

References

Inspiration: Bevy assets, Petgraph node indices, Cargo units

Trait Trees

Inspiration: Yew components, Druid widgets

Dispatch

Inspiration: Bevy systems, Diesel queries, Axum handlers

Intermediates

Inspiration: Serde and miniserde