We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Smart Pointers and Interior Mutability step 14 of 26
RAII: build your own guard
Write a scope guard and drive it from a script.
pub fn trace(events: Vec<String>) -> Vec<String>
A Span is a guard over a shared log. Constructing one appends
enter <name>; dropping one appends exit <name>. You write the Drop
impl — that is the exercise.
Then interpret the script, keeping open spans on a Vec<Span> used as a
stack:
| event | effect |
|---|---|
open <name> |
push a new Span (which logs enter <name>) |
close |
drop the innermost open span; no-op if none are open |
note <text> |
append note <text> |
| anything else |
append ? <the whole line> |
At the end of the script, every still-open span must close, innermost first. Return the finished log.
What ownership was for
Up to now ownership has mostly appeared as a list of things you may not do. This is what it buys.
A Span‘s cleanup is not something a caller can forget, skip, or get wrong,
because it is not something a caller does — it is a consequence of the value
going out of scope. Rust guarantees the destructor runs on every exit path
from that scope: falling off the end, an early return, a ? that propagates
an error, a break, and an unwinding panic. There is no finally, because
there is nothing to put in one.
That is RAII — Resource Acquisition Is Initialisation, a name from C++ that
describes the wrong half of the idea. The interesting half is the destructor.
You have been using it all along without writing one: File closes,
MutexGuard unlocks, Ref decrements the borrow flag, Box frees, Rc
decrements the count. Every resource API in Rust is this pattern.
The guard’s lifetime is the critical section. That single sentence
explains why { let _g = lock(); … } blocks are idiomatic, why holding a
Ref too long panics, and why the next item cares so much about the
difference between let _ and let _x.
Design rules for a guard
- It must own or borrow the resource, so that its destructor can reach it.
-
It must not be
Copy. ACopyguard would run its destructor once per copy, which is nonsense — and the compiler enforces this: a type cannot implement bothCopyandDrop. -
It should usually be
#[must_use], so thatlock();— creating a guard and dropping it immediately — is a warning rather than a silent bug. - Its scope is its meaning. If you want a shorter critical section, introduce a block; if you want a longer one, bind it further out.
Two things the tests will catch
Vec drops its elements front to back. So drop(stack) at the end of the
script closes the outermost span first, which is backwards. Verify it
yourself if you doubt it — dropping vec![S(1), S(2), S(3)] prints 1, 2, 3.
To close innermost-first you must pop:
while let Some(span) = stack.pop() {
drop(span);
}
Vec::pop returns Option<Span>, and it is the binding and dropping of
that Option that runs the destructor. This is worth noticing: pop is where
the “exit” line comes from, not clear, not truncate, not the end of the
function.
The guard must not steal the log. The starter’s Span::new takes
log: Log by value, so the first open moves the log into the span and
the second one has nothing to work with:
error[E0382]: use of moved value: `log`
| "open" => stack.push(Span::new(rest, log)),
| ^^^ value moved here,
| in previous iteration of loop
Read that as a design question rather than a syntax problem. Should
constructing a guard consume the caller’s handle to the resource? Obviously
not — several spans are open at once and they all need it. The guard should
take a shared reference and keep its own Rc handle, which is precisely
what Rc was invented for.
About the scaffolding
The log is an Rc<RefCell<Vec<String>>>, which is the pattern you met in the
last few items: shared ownership of one mutable cell. Several spans hold
handles to the same log, and each one appends through borrow_mut() in its
destructor. Keep the borrows short — a Drop impl that holds a borrow while
doing something else is exactly the re-entrancy hazard from the previous
problem, and here it would be much harder to see.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.