Checkpoint: Systems Rust

This checkpoint consolidates the systems-programming half of the course — extra collections, macros, Drop and RAII, newtypes, Cow, Box<dyn Error> , and unsafe — into one build challenge and a self-test quiz.

Learn Checkpoint: Systems Rust in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Rust course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

You'll recap each topic, then write a small task runner that combines a validated newtype, a custom macro, and Result-based error handling, with a full worked solution to check yourself against.

What This Checkpoint Reviews

1️⃣ Section Recap

Before the challenge, here is the systems-Rust toolkit in one place. Each row links a concept to the one-line idea you should remember.

Topic

Key idea

Collections

VecDeque for both ends, BTreeMap for sorted/range, BinaryHeap for priority

Macros

macro_rules! matches tokens and expands code at compile time

Drop / RAII

Cleanup runs at scope end, in reverse declaration order

Newtype

Wrap a type for safety and to beat the orphan rule

Cow

Borrow for free, allocate only when you modify

Box<dyn Error>

One error type for all failures; ? converts and propagates

unsafe

Five superpowers; keep it small, sound, and wrapped

2️⃣ Build Challenge: A Validated Task Runner

Now put three of those tools together. You'll build a Priority newtype that only accepts values 1–5, a task_queue! macro that constructs a VecDeque of tasks, and a process function that validates each task and propagates failures through Box<dyn Error> . Try it yourself first.

Here is a complete, compiling reference implementation. Compare it against your own attempt — there is more than one valid way to structure it.

3️⃣ 📝 Checkpoint Quiz

Answer each question in your head first, then click to reveal. If any feel uncertain, revisit that lesson before the capstone.

VecDeque . It is a ring buffer, so push_front , pop_front , push_back , and pop_back are all O(1). A plain Vec is O(n) to remove from the front.

Wrap each value in std::cmp::Reverse before pushing. Reverse flips the ordering, so the smallest underlying value pops first.

They are dropped at the end of the scope, in the reverse of their declaration order — the last value created is the first destroyed, like unwinding a stack.

A newtype is a genuinely distinct type, so the compiler stops you mixing Meters with Feet and lets you implement foreign traits on it. A type alias is just a nickname for the same type and adds no safety.

It unwraps an Ok value, or on an Err returns early — automatically converting the concrete error into Box<dyn Error> via the From trait.

No. Normal references, ownership, and the type system are all still enforced. Unsafe only enables five extra operations (such as dereferencing raw pointers); raw pointers are simply not tracked by the borrow checker in the first place.

Pro Tips

Frequently Asked Questions

🎉 Checkpoint Complete!

Practice quiz

Which standard collection gives O(1) push and pop at BOTH the front and the back?

  • Vec
  • BTreeMap
  • VecDeque
  • BinaryHeap

Answer: VecDeque. VecDeque is a ring buffer, so push_front, pop_front, push_back, and pop_back are all O(1). A plain Vec is O(n) to remove from the front.

How do you turn a BinaryHeap (a max-heap) into a min-heap?

  • Wrap each value in std::cmp::Reverse before pushing
  • Call .reverse() on the heap
  • Use BinaryHeap::min() instead of new()
  • It is impossible without a custom struct

Answer: Wrap each value in std::cmp::Reverse before pushing. Wrapping values in std::cmp::Reverse flips the ordering, so the smallest underlying value pops first, giving min-heap behavior.

In what order are values dropped within a scope?

  • In declaration order (first declared, first dropped)
  • In a random order
  • All at once with no defined order
  • In reverse declaration order (last declared, first dropped)

Answer: In reverse declaration order (last declared, first dropped). Values are dropped at the end of the scope in the reverse of their declaration order, like unwinding a stack.

Why use a newtype like 'struct Meters(f64)' instead of a 'type' alias?

  • A newtype is faster at runtime
  • A newtype is a genuinely distinct type and lets you implement foreign traits on it
  • A type alias cannot hold an f64
  • There is no real difference

Answer: A newtype is a genuinely distinct type and lets you implement foreign traits on it. A newtype is a distinct type, so the compiler stops you mixing Meters with Feet and lets you implement foreign traits. A type alias is just a nickname and adds no safety.

For a function returning Result<T, Box<dyn Error>>, what does the ? operator do on an Err?

  • It returns early, converting the concrete error into Box<dyn Error> via From
  • It panics immediately
  • It ignores the error and continues
  • It loops until the call succeeds

Answer: It returns early, converting the concrete error into Box<dyn Error> via From. ? unwraps an Ok value, or on an Err returns early, automatically converting the concrete error into Box<dyn Error> through the From trait.

Does an 'unsafe' block turn off the borrow checker?

  • Yes, it disables all of Rust's safety checks
  • Yes, but only for references
  • No; it only enables five extra operations such as dereferencing raw pointers
  • No, it has no effect on what code can do

Answer: No; it only enables five extra operations such as dereferencing raw pointers. unsafe does not disable the borrow checker. Normal references, ownership, and the type system are still enforced; unsafe only unlocks five extra operations.

What does macro_rules! operate on, and when does it expand?

  • Runtime values, expanded at runtime
  • Tokens, expanded at compile time
  • Bytes, expanded at link time
  • Strings, expanded by the OS

Answer: Tokens, expanded at compile time. macro_rules! is a declarative macro that matches tokens and expands into code at compile time.

What is Cow (clone-on-write) primarily used for?

  • Forcing every value to be cloned eagerly
  • Sharing data across threads safely
  • Replacing Box<dyn Error>
  • Borrowing data for free and allocating only when you actually need to modify it

Answer: Borrowing data for free and allocating only when you actually need to modify it. Cow lets you borrow data for free and only allocates/clones when modification is actually needed, avoiding unnecessary allocations.

Which trait do you implement so that custom cleanup runs when a value goes out of scope?

  • Clone
  • Drop
  • Default
  • Copy

Answer: Drop. The Drop trait lets you define cleanup logic (drop) that runs automatically at the end of a value's scope — the basis of RAII in Rust.

In the task runner, what does Priority::new return so its constructor can validate the range 1..=5?

  • Priority directly
  • Option<Priority>
  • Result<Priority, Box<dyn Error>>
  • u8

Answer: Result<Priority, Box<dyn Error>>. Priority::new returns Result<Priority, Box<dyn Error>>, returning Ok for a valid value and an Err (BadPriority) when the value is out of the 1..=5 range.

Continue this course