Dropping linked list without growing stack

⚓ Rust    📅 2025-12-29    👤 surdeus    👁️ 12      

surdeus

Warning

This post was published 113 days ago. The information described in this article may have changed.

This can be implemented with tail calls (unstable feature as of now) but with some difficulty. Presented is the full solution.

#![expect(incomplete_features)]
#![feature(explicit_tail_calls)]


struct ScreamOnDrop(i32);
impl Drop for ScreamOnDrop {
    fn drop(&mut self) {
        println!("Dropping #{}", self.0);
    }
}



// The list is actually defined as usual.
struct LinkedList {
    now: ScreamOnDrop,
    // any data fields can be added here...
    next: Option<Box<LinkedList>>
}

impl LinkedList {
    // Now, we get to something unusual.
    // We are going to tail call dropping the next list element.
    //   It must have signature of Fn(LinkedList) -> ().
    //   Therefore, we cannot call it from anything but Fn(LinkedList) -> ();
    //     this mandates a `Self::drop` inherent function.

    fn drop(mut self) {
        if let Some(tail) = self.next.take() {
            // We got a Box<LinkedList>. It does not have inherent `drop` so
            //   we must dereference it.
            let tail: LinkedList = *tail;

            // Before the call starts, every other field in `self` is dropped.
            become tail.drop()
        }
        // else { panic!("uncomment to see that stack does not grow") }
    }
}

// We need to route Drop implementation to the inherent function.
// `Drop::drop` only gives us a mutable reference, so only elements from
//   the 2nd onwards will be dropped through inherent `LinkedList::drop`.
impl Drop for LinkedList {
    fn drop(&mut self) {
        // Checking if there is any list tail.
        // Other fields will be handled by the compiler automatically.
        if let Some(tail) = self.next.take() {
            (*tail).drop();  // tail call conditions are not met here
        }
    }
}


fn main() {
    let mut list = None;
    for i in 0..5 {
        let next_node = LinkedList { now: ScreamOnDrop(5 - i), next: list };
        list = Some(Box::new(next_node));
    }
}

It has somewhat unusual drop order:

Dropping #1
Dropping #2
Dropping #3
Dropping #4
Dropping #0

3 posts - 3 participants

Read full topic

🏷️ Rust_feed