kin
kin

Reputation: 51

What is the difference between `&mut retval` and `retval as *const T as *mut T`?

I am having a hard time trying to understand why the following code has 2 different behaviors:

pub fn get(&self, idx: usize) -> &T {
    let arr = unsafe { core::slice::from_raw_parts(self.elements, self.count) };
    &arr[idx]
}

when I call:

unsafe { ptr::drop_in_place(self.get(i) as *const T as *mut T) };

it works and the value is dropped in place, however changing the cast to &mut does not:

unsafe { ptr::drop_in_place(&mut self.get(i)) };

I expected the compiler to yield an error since T does not support clone/copy, but it's not the case. What is the explanation?

Minimal Example:

use core::*;

pub struct Vec<T> {
    elements: *mut T,
    count: usize,
    capacity: usize,
}

pub fn alloc_array<T>(count: usize) -> *mut T {
    let size = mem::size_of::<T>() * count;
    let addr = unsafe { libc::memalign(mem::size_of::<usize>(), size) as *mut T };
    unsafe { libc::memset(addr as *mut libc::c_void, 0, size) };
    addr
}

pub fn free_array<T>(arr: *mut T) {
    unsafe { libc::free(arr as *mut libc::c_void) };
}

impl<T> Vec<T> {
    pub fn new() -> Self {
        Self {
            elements: ptr::null_mut(),
            count: 0,
            capacity: 0,
        }
    }

    pub fn len(&self) -> usize {
        self.count
    }

    pub fn pushBack(&mut self, t: T) {
        if self.count >= self.capacity {
            let newSize = if self.capacity == 0 {
                16
            } else {
                self.capacity * 2
            };
            let old = self.elements;
            self.elements = alloc_array(newSize);
            self.capacity = newSize;

            let oldArr = unsafe { core::slice::from_raw_parts_mut(old, self.count) };
            let newArr = unsafe { core::slice::from_raw_parts_mut(self.elements, self.count + 1) };

            for i in 0..self.count {
                let v = unsafe { ptr::read(&oldArr[i] as *const _) };
                newArr[i] = v;
            }
        }
        let arr = unsafe { core::slice::from_raw_parts_mut(self.elements, self.count + 1) };

        arr[self.count] = t;
        self.count += 1
    }

    pub fn pop(&mut self) -> Option<T> {
        if self.count == 0 {
            None
        } else {
            self.count -= 1;
            Some(unsafe { ptr::read(self.get(self.count) as *const _) })
        }
    }

    #[inline]
    pub fn get(&self, idx: usize) -> &T {
        let arr = unsafe { core::slice::from_raw_parts(self.elements, self.count) };
        &arr[idx]
    }
}

impl<T> Drop for Vec<T> {
    fn drop(&mut self) {
        println!("Dropped");
        for i in 0..self.count {
            //unsafe { ptr::drop_in_place(self.get(i) as *const T as *mut T) };   // Works
            unsafe { ptr::drop_in_place(&mut self.get(i)) }; // Doesn't Works
        }
        if self.capacity != 0 {
            free_array(self.elements);
        }
    }
}

fn main() {
    let mut v = Vec::<Vec<i32>>::new();
    for i in 0..10 {
        let mut vj = Vec::<i32>::new();
        for j in 0..10 {
            vj.pushBack(j);
        }
        v.pushBack(vj);
    }
}

Valgrind:

Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
==6887== 
==6887== HEAP SUMMARY:
==6887==     in use at exit: 640 bytes in 10 blocks
==6887==   total heap usage: 30 allocs, 20 frees, 4,433 bytes allocated
==6887== 
==6887== Searching for pointers to 10 not-freed blocks
==6887== Checked 107,320 bytes
==6887== 
==6887== 640 bytes in 10 blocks are definitely lost in loss record 1 of 1
==6887==    at 0x4C320A6: memalign (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6887==    by 0x10CB3F: minimal_test::alloc_array (main.rs:11)
==6887==    by 0x10CDDC: minimal_test::Vec<T>::pushBack (main.rs:35)
==6887==    by 0x10E1BB: minimal_test::main (main.rs:85)
==6887==    by 0x10C2DF: std::rt::lang_start::{{closure}} (rt.rs:67)
==6887==    by 0x1165B2: {{closure}} (rt.rs:52)
==6887==    by 0x1165B2: std::panicking::try::do_call (panicking.rs:305)
==6887==    by 0x117D16: __rust_maybe_catch_panic (lib.rs:86)
==6887==    by 0x116F3F: try<i32,closure-0> (panicking.rs:281)
==6887==    by 0x116F3F: catch_unwind<closure-0,i32> (panic.rs:394)
==6887==    by 0x116F3F: std::rt::lang_start_internal (rt.rs:51)
==6887==    by 0x10C2B8: std::rt::lang_start (rt.rs:67)
==6887==    by 0x10E259: main (in minimal-test/target/debug/minimal-test)
==6887== 
==6887== LEAK SUMMARY:
==6887==    definitely lost: 640 bytes in 10 blocks
==6887==    indirectly lost: 0 bytes in 0 blocks
==6887==      possibly lost: 0 bytes in 0 blocks
==6887==    still reachable: 0 bytes in 0 blocks
==6887==         suppressed: 0 bytes in 0 blocks
==6887== 
==6887== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
==6887== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Upvotes: 2

Views: 179

Answers (1)

trent
trent

Reputation: 27895

Pay close attention to types.

pub fn get(&self, idx: usize) -> &T {

self.get(i) has type &T. So &mut self.get(i) has type &mut &T. Calling drop_in_place will coerce &mut &T to *mut &T and drop a &T, which (because shared references do not implement Drop) does nothing.

self.get(i) as *const _ as *mut _ casts &T to *const T and then to *mut T. Calling drop_in_place will invoke undefined behavior when it calls <T as Drop>::drop, which accepts a &mut T. This is bad.

You cannot mutate a value (including dropping it) through a shared reference. Casting through raw pointers doesn't make it possible. See Is there a way to make an immutable reference mutable?

The Nomicon contains a section on implementing Vec<T> from scratch; I suggest reading it.

Upvotes: 4

Related Questions