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
Implement a guard that records when a span opens and closes, then use it
in trace(events: Vec<String>) -> Vec<String>.
Constructing a Span appends enter <name> to a shared log. Implement
Drop so destroying that Span appends exit <name>. Keep open guards
in a vector used as a stack.
Script rules
Split each event at its first ASCII space. The part before it is the command; the remainder is the argument. If there is no space, the whole event is the command and the argument is empty. Do not trim either part.
| Command | Effect |
|---|---|
open |
Create and push a Span whose name is the argument |
close |
Remove and drop the most recently opened Span; ignore the argument |
note |
Append note <argument> |
| Anything else | Append ? <whole original event> |
Names and notes may be empty or contain spaces. For example, "open"
logs "enter ", while "open a" logs "enter a". Commands are
case-sensitive, and a tab does not replace the command's space separator.
Closing an empty stack does nothing. After the last event, close all remaining spans from innermost to outermost, then return the completed log.
For ["open outer", "open inner", "note work", "close"], return:
["enter outer", "enter inner", "note work", "exit inner", "exit outer"]
Empty input produces an empty log.
What a guard does
RAII means Resource Acquisition Is Initialization. A guard ties cleanup to an owned value. Its constructor establishes a state, and its destructor releases or completes that state when the value is dropped.
A local guard is normally dropped when its scope exits, including on an
early return, error propagation with ?, or an unwinding panic. It can
also be dropped earlier with std::mem::drop(guard), or moved elsewhere
so its owner keeps it alive longer.
Destruction is not guaranteed on every possible exit. For example,
mem::forget, reference cycles, process termination and aborting panics
can prevent it. RAII makes ordinary cleanup automatic; it is not a
guarantee against every leak or interrupted process.
File handles, lock guards and RefCell borrow guards use this pattern for
different resources. Here the resource is the open span represented by a
log entry. A guard type implementing Drop cannot also implement Copy.
#[must_use] can help catch an accidentally discarded guard, but it is
not a substitute for managing its ownership.
Share the log handle
The log is Rc<RefCell<Vec<String>>>. Rc lets multiple guards share
ownership of the same log; RefCell allows checked mutable access when
appending entries.
The starter passes log by value to Span::new, moving the caller's
handle. Later uses produce E0382. Change the constructor to accept
&Log and store Rc::clone(log), updating its caller accordingly.
Cloning an Rc clones the ownership handle, not the vector of log entries.
Taking a Log by value is not inherently wrong: a caller could instead supply a cloned handle. The borrowed-constructor design used here keeps that sharing decision inside Span::new.
Keep RefCell borrows short. Holding a shared borrow is not itself an
error, but dropping a Span while that borrow remains active would make
its borrow_mut() panic. A destructor that panics during an existing
unwind can abort the process.
Pop before dropping
Vec::pop removes and returns the last value. It does not itself destroy
a returned Span that you keep in a variable.
In stack.pop();, the returned temporary Option is discarded at the end
of the statement. If it contains a Span, that Span is dropped then. In the
final cleanup loop, explicitly drop each popped value:
while let Some(span) = stack.pop() {
drop(span);
}
This fragment belongs after the event loop. Popping determines the required
innermost-first order. Vec does not guarantee an element destruction order,
so drop(stack) must not be used to establish this ordering.
Do not call Span::drop directly. Implement fn drop(&mut self) through
the Drop trait and use ordinary scope exit or std::mem::drop to destroy
an owned guard.
The required final ordering applies to trace's normal completion. It does not promise that an unwinding vector will drop its elements in reverse order. Keep the distinction between automatic cleanup and ordered cleanup clear when adapting this pattern.
The submission must compile, match the log output and pass Clippy with warnings denied.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.