Reputation: 51
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
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