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
- 💡 Build in stages. Get each of the three pieces compiling on its own before wiring them together.
- 💡 Let the types do the checking. Validating in Priority::new means an invalid priority can never exist downstream.
- 💡 Score yourself honestly. Any quiz answer you weren't sure of points straight at the lesson to review.
Frequently Asked Questions
🎉 Checkpoint Complete!
- ✅ Recapped collections, macros, Drop/RAII, newtypes, Cow, Box<dyn Error> , and unsafe
- ✅ Built a task runner combining a newtype, a macro, and Result error handling
- ✅ Checked your understanding against a full worked solution
- ✅ Self-tested with the checkpoint quiz
- ✅ Next up: the Capstone Project — bring the whole course together
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
- Previous: unsafe Rust
- Next: Capstone Project