Checkpoint: Systems Rust
This checkpoint consolidates the systems-programming half of the course — extra collections, macros, Drop and RAII, newtypes, Cow, , 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.
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 . 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 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.
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.