How to understand DropGuard for ConvertVec in slice.rs

Hello everyone, im learnning rust, Rust very good. But I get a problem when understand the implement for ConvertVec. the code like [ignore some comments].

impl<T: Clone> ConvertVec for T {
    default fn to_vec<A: Allocator>(s: &[Self], alloc: A) -> Vec<Self, A> {
        struct DropGuard<'a, T, A: Allocator> {
            vec: &'a mut Vec<T, A>,
            num_init: usize,
        }
        impl<'a, T, A: Allocator> Drop for DropGuard<'a, T, A> {
            #[inline]
            fn drop(&mut self) {
                //❓ why need set_len ? i think `vec.set_len(s.len());` is enough
                unsafe {
                    self.vec.set_len(self.num_init);
                }
            }
        }
        let mut vec = Vec::with_capacity_in(s.len(), alloc);
        //❓ why need DropGuard ? i think that Vec is enough
        let mut guard = DropGuard { vec: &mut vec, num_init: 0 };
        let slots = guard.vec.spare_capacity_mut();
        for (i, b) in s.iter().enumerate().take(slots.len()) {
            guard.num_init = i;
            slots[i].write(b.clone());
        }
        core::mem::forget(guard);
        unsafe {
            **vec.set_len(s.len());**
        }
        vec
    }
}

one question.

I known core::mem::forget(guard); will tell compile do not "drop" value. but the drop method in DropGuard is only set len for vector , it's duplicate setted in unsafe code. so why we need set len twice ?

other question is why we need DropGuard rather than use directly Vec,i konw like this

impl<T: Clone> ConvertVec for T {
    #[inline]
    default fn to_vec<A: Allocator>(s: &[Self], alloc: A) -> Vec<Self, A> {
        let mut vec = Vec::with_capacity_in(s.len(), alloc);
        let slots = vec.spare_capacity_mut();
        for (i, b) in s.iter().enumerate().take(slots.len()) {
            slots[i].write(b.clone());
        }
        unsafe {
            vec.set_len(s.len());
        }
        vec
    }
}

Can anyone give me some tip or suggestion about it ?

Without DropGuard you could potentially leak memory if your code panicked before set_len is called. Off the top of my head I don't see a lot of opportunities for panics there, though I suppose T's Clone impl could panic arbitrarily.

By keeping track of how many elements have been written to the Vec, the DropGuard ensures that when the Vec is dropped during an unwinding panic all of the items that were successfully cloned into the Vec can also be dropped

2 Likes

Thank you. I get it :smile:.

FWIW, by using something such as:

the above code could be spelled as:

impl<T: Clone> ConvertVec for T {
    #[inline]
    default fn to_vec<A: Allocator>(s: &[Self], alloc: A) -> Vec<Self, A> {
        let mut vec = Vec::with_capacity_in(s.len(), alloc);

        ::unwind_safe::with_state((&mut vec, 0))
            .try_eval(|(vec, i)| {
                let slots = vec.spare_capacity_mut();
                for b in &s {
                    slots[*i].write(b.clone()); // <- may panic!
                    *i += 1;
                }
            })
            .finally(|(vec, num_init)| unsafe {
                vec.set_len(num_init);
            });

        vec
    }
}

which is less noisy, involves less boilerplate, more concise, involves a more human-intuitive order, and does mention unwind_safety as the reason for the whole thing. All in all, more readable, imho :slightly_smiling_face:

2 Likes