Skip to content

← Ownership I: Moves, Copy, Clone, Drop step 11 of 20

Hard Primitives

Take a payload while keeping its destructor valid

Repair harvest so it transfers each payload out of its session without cloning the payload. Use the supplied Session type and let its destructor record that the payload is absent. Keep the function signature unchanged:

pub fn harvest(sessions: Vec<(i64, String)>) -> Vec<String>

Expected log

First construct every session in input order, keeping them all alive. Each constructor records "open {id}". Then process the sessions in that same order:

  1. Take the payload, leaving the session's payload field as None.
  2. Record "got {id} {payload}" using note.
  3. Let that session drop before processing the next one. Its destructor records "close {id} payload=absent".

Return the log after all sessions have dropped. For [(1, "a"), (2, "b")]:

open 1
open 2
got 1 a
close 1 payload=absent
got 2 b
close 2 payload=absent

Preserve payload text exactly, including Unicode, whitespace and empty strings. An empty payload produces a trailing space, as in "got 9 ". IDs can be any i64 value; repeated IDs are separate sessions. For N inputs the result has 3N entries. Empty input returns an empty vector.

Use the real constructors and destructors for open and close entries; do not manufacture those entries with note. Use safe Rust and transfer the payload without cloning it. Cloning the shared log handle is allowed.

Supplied helpers

The grader provides these items; do not redefine them:

Item Behavior
Log A shared log handle, implemented as Rc<RefCell<Vec<String>>>.
new_log() -> Log Creates an empty log.
note(log: Log, line: String) Appends one entry. Pass a cloned log handle if you still need yours.
snapshot(log: Log) -> Vec<String> Returns a copy of the current log entries.
RawSession::new(id, payload, log) Records open and stores id: i64, payload: String and a log handle. Its destructor always records payload=present.
Session::new(id, payload, log) Records open and stores id: i64 and payload: Option<String>, initially Some(payload), plus a log handle. Its destructor reports present for Some and absent for None.

Your function can access the supplied fields directly. Both constructors take an i64, an owned String and a Log handle. Use Session for the required absent state. In particular, Some(String::new()) is still present: an empty string and an absent payload are different states.

Why the starter fails with E0509

The starter uses RawSession and attempts let payload = s.payload;. String is not Copy, so this would move the field out and leave it uninitialized. RawSession implements Drop, whose method receives &mut self and may access the fields. Rust rejects that partial move even if this particular destructor does not read the field. See the E0509 explanation.

This does not prevent all field access. You can borrow a field, copy a Copy field such as id, or replace a field while leaving a valid value behind. Borrowing the payload alone would not mark it absent for this exercise.

Replace the value with an explicit empty state

Option::take returns the previous option and leaves None in its place. The owned String moves out inside the returned Some; it is not cloned. The session remains fully initialized, so its destructor can safely inspect the None field.

This complete example shows the same pattern:

struct Packet {
    payload: Option<String>,
}

impl Drop for Packet {
    fn drop(&mut self) {
        assert!(self.payload.is_none());
    }
}

fn main() {
    let mut packet = Packet { payload: Some("hello".to_string()) };
    let payload = packet.payload.take().expect("payload is present");
    assert_eq!(payload, "hello");
    assert!(packet.payload.is_none());
    drop(packet);
    assert_eq!(payload, "hello");
}

take needs mutable access to the option. In harvest, for mut s in built lets you mutably borrow each owned session's payload. Taking an already-None option returns None; each freshly constructed Session starts with Some.

You could also write std::mem::replace(&mut s.payload, None). Clippy's mem_replace_option_with_none lint recommends the shorter take form; with warnings denied, that lint makes the longer form fail the style check.

Why not keep RawSession?

A bare String can also be safely replaced: std::mem::take(&mut s.payload) returns the old String and leaves an empty String. That is legal even when the containing type implements Drop. However, this exercise's RawSession destructor always reports present, so it cannot produce the required absent entries. Session models the state the destructor needs to observe.

The logs check the destruction order and reported payload state. They do not prove that no payload was cloned; avoiding that clone is also a source-code requirement. No unsafe code or manual destructor suppression is needed.

Passing requires the expected logs, successful compilation and Clippy with warnings treated as errors.