How long does a reference remain valid after the object it references is dropped?

Viewed 266

I'm reading Common Rust Lifetime Misconceptions to figure out how lifetime works, one of the example (posted below) really shocked me, I can't convince myself why byte_1 and byte_2 still live well after the std::mem::drop(bytes); is executed.

From my point of view, the references which byte_1 and byte_2 hold should be invalid after the iterator bytes is dropped because the remainder array is dropped as well, the compiler should treat the drop action as an error, but in fact it pass the compiler and runs without problem...

Hope someone can give a reasonable explanation for a programmer coming from C++/C#, thanks!

struct ByteIter<'remainder> {
    remainder: &'remainder [u8]
}

impl<'remainder> ByteIter<'remainder> {
    fn next(&mut self) -> Option<&'remainder u8> {
        if self.remainder.is_empty() {
            None
        } else {
            let byte = &self.remainder[0];
            self.remainder = &self.remainder[1..];
            Some(byte)
        }
    }
}

fn main() {
    let mut bytes = ByteIter { remainder: b"1123" };
    let byte_1 = bytes.next();
    let byte_2 = bytes.next();
    std::mem::drop(bytes); // we can even drop the iterator now!
    if byte_1 == byte_2 { // compiles
        // do something
    }
}

(I've been learning Rust for a month, but still confused by lots of new concepts)

2 Answers

bytes does not own b"1123". It only has a reference to it. Because there is no ownership, dropping bytes doesn't drop b"1123". Dropping bytes's reference doesn't affect other references with the same lifetime.

Now you could invalidate byte_1 and byte_2 if you were somehow able to drop the referent b"1123". But that's impossible. b"1123" has a 'static lifetime, and 'static objects can't be dropped.

The key to understanding why this works is looking at the way Iterator is defined:

trait Iterator{
    type Item;
    fn next(&mut self)-> Option<Self::Item>;
}

Notice how the lifetime of the return value has no connection to the lifetime of the Iterator itself. This means that the returned item must be able to live without the original Iterator. In this case, the indexing operation reborrows the original object that was borrowed for the same length of time as the borrow by the iterator*. This means that so long as you don't try to drop the underlying data you're borrowing from while the values returned by Iterator::next are in scope, the compiler will not throw an error. In your example, that can't happen because the thing you're iterating over is embedded in the binary itself. In a circumstance that you can drop the underlying data, it results in a compiler error if you try.

*This means the anti-aliasing rules require the use of unsafe code in many iterators returning mutable references.

Related