Understanding this unsafe block in the stdlib's Vec::truncate function


#1

In the spirit of learning idiomatic Rust, I’m taking a look at the Vec functionality in the standard library.

This particular block stood out at me from https://doc.rust-lang.org/stable/src/collections/vec.rs.html#438-461

    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn truncate(&mut self, len: usize) {
        unsafe {
            // drop any extra elements
            while len < self.len {
                // decrement len before the read(), so a panic on Drop doesn't
                // re-drop the just-failed value.
                self.len -= 1;
                ptr::read(self.get_unchecked(self.len));
            }
        }
    }

ptr::read is not returning any value, so what does it do? The comment seems to mean that we’re trying to panic if the user truncated too far.

Why not explicitly drop each value instead?


#2

I’m not sure reading unsafe code in the standard library could really be considered “idiomatic.” It can be informative, but in general idiomatic Rust code should use unsafe as little as possible, while the standard library uses it much more often as it is providing the basic safe abstractions that most code needs. In addition, the standard library has been around for a long time, and many parts since pre-1.0, when the language was changing a lot. There are some parts that may be oddly designed by today’s standards due to refactorings as features changed, or not yet having been adapted to using newer features that make the code simpler.

That said, in this case, I believe that ptr::read is being used to dereference the raw pointer to the last element of the vector, so that we will have a value that will be dropped. We don’t need to call mem::drop on it explicitly, since just having it in an unused temporary will mean that it will be dropped after that line is executed. ptr::read does return a value, but by ignoring it we’re causing it to be dropped immediately.

You could call mem::drop on the result of ptr::read to achieve the same end. It would make the code a little bit more verbose but more explicit. It’s possible that that would introduce a an extra copy of the value if it’s a Copy type, though I suspect that that would just be optimized away (although that extra copy may still be relevant in unoptimized builds used in debug mode).

I think that in general, the convention is only to use mem::drop if you need to change when exactly an object’s Drop::drop method is called. An unused return value will be dropped immediately; a bound variable will be dropped when it goes out of scope. If that is the correct time to drop a value, then there’s no need to write an explicit mem::drop.

I’ll note that all mem::drop actually does is take an object by value, and does nothing with it. This causes it to be automatically dropped at the end of the scope. The implementation is:

pub fn drop<T>(_x: T) { }

So simply doing nothing with the result of ptr::read is exactly equivalent.


#3

Excellent, thank you.