Understanding the Drop implementation for std::vec::IntoIter

I m currently trying to understand the Drop implementation for std::vec::IntoIter. I have a few questions there:

  • What is the #[may_dangle] attribute good for? I tried to read it's documentation, but that didn't help me understand what it actually does.
  • Why is the .by_ref() needed? As far as i can tell, it's only use is to get a mutable reference to the iterator while chaining iterator combinators, but here, self already is of type &mut IntoIter<T>, so it shouldn't make a difference?
  • Why do the remaining elements still get dropped if the Drop implementation of one of the Ts panics? I understand why this makes sense, but looking at the implementation, it seems to me that that shouldn't happen. I know that values that are in scope get dropped when unwinding, but here, aren't the only values that get dropped the pointers? I tried to copy the relevant parts, but in my own version, the remaining elements weren't dropped. For the Drop implementation of LinkedList, a DropGuard is used to drop the remaining elements on an unwind, but i can't find anything like that here.

The Nomicon has a chapter about may_dangle.

Due to some advanced interactions regarding the drop checker, I have explained it here: PhantomData and dropck confusion - #2 by Yandros Basically by saying #[may_dangle] we are promising Rust that this Drop impl will not dereference any borrows that T may be holding, except through a recursive call to drop glue. For instance, if T = &'a i32, it is safe to drop the IntoIter even past the 'a lifetime / when 'a dangles, since because &'a i32 has no drop glue, there is no way the (dangling) reference will be dereferenced (which would be unsound).

I guess for readability, here it plays no role indeed.

EDIT: it is actually used to reborrow self, as in for _ in &mut *self {, given that otherwise self would be unusable, c.f., @mbrubeck's response pointing that out (I always forget about that annoying mechanic of for sugar)

They don't. If some element's recursive drop glue panics, then the remaining elements will not be dropped, and leaks may happen. "Hell", even the whole buffer allocation will be leaked:

The #[may_dangle] attribute can be thought of a way to say "if the type T contains any references, I will not look inside them". This will not compile:

struct PrintOnDrop<T: Debug> {
    a: T
}
impl<T: Debug> Drop for PrintOnDrop<T> {
    fn drop(&mut self) {
        println!("{:?}", self.a);
    }
}

fn main() {
    let mut a = None;
    
    let s = "abc".to_string();
    
    a = Some(PrintOnDrop { // here T is &str
        a: s.as_str(), // borrowed value does not live long enough
    })
}

By using #[may_dangle], we promise not to look inside any references stored in T. So by doing this, the above will compile (playground).

unsafe impl<#[may_dangle] T: Debug> Drop for PrintOnDrop<T> {
    fn drop(&mut self) {
        println!("{:?}", self.a);
    }
}

Of course, in this case I am breaking the promise, so the code will fail with a segmentation fault or UTF-8 error, as it exhibits undefined behaviour.

As for by_ref, well it's mostly for clarity that the iterator is not consumed. As for panics in the destructor of IntoIter, the remaining items will not be dropped, and the memory will not be freed.

1 Like

The code won't compile without by_ref because a for loop consumes any non-Copy iterator that you give it, including an &mut I. Playground.

1 Like

I realized that this only happens on nightly (playground). On nightly, you will see the messages of the two non-panicking Droppers, while on stable you won't. What's the reason for this?

You're right, thank you.

Because nightly now uses a different implementation for their Drop, using both a DropGuard and a drop_in_place::<[_]> over the whole slice (rather than manually iterating to drop each element of the slice). The former ensures that the allocation is freed no matter what, and the latter ensure that a panic of on the drop of the first element does not prevent dropping the others:


Also, when running my above playground on nightly, we can see that the vec (Layout { size_: 3, align_: 1}) is well deallocated: Playground

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.