I try to create an unsafe, but more performant ArrayQueue
implementation. After I added test cases, one of them produces a segmentation fault.
Here is my simple minimal implementation:
use std::mem;
pub struct ArrayQueue<T> {
buff: Vec<T>,
head: usize,
size: usize,
}
impl<T> ArrayQueue<T> {
pub fn new(size: usize) -> Self {
let mut buff = Vec::with_capacity(size);
unsafe {
buff.set_len(size);
}
ArrayQueue {
buff: buff,
head: 0,
size: 0,
}
}
pub fn add(&mut self, elem: T) {
let idx = (self.head + self.size) % self.buff.len();
*unsafe { self.buff.get_unchecked_mut(idx) } = elem;
self.size += 1;
}
pub fn remove(&mut self) -> T {
let idx = self.head;
self.size -= 1;
self.head = (self.head + 1) % self.buff.len();
mem::replace(unsafe { self.buff.get_unchecked_mut(idx) }, unsafe {
mem::uninitialized()
})
}
}
impl<T> Drop for ArrayQueue<T> {
fn drop(&mut self) {
let mut idx = self.head;
for _ in 0..self.size {
// Drop only valid elements of the queue
drop(unsafe { self.buff.get_unchecked_mut(idx) });
idx = (idx + 1) % self.buff.len();
}
unsafe {
// Prevent deallocation of vector elements
// This still dallocates vector's internal buffer
self.buff.set_len(0);
}
}
}
#[cfg(test)]
mod test {
use super::ArrayQueue;
#[test]
fn test0() {
let mut x = ArrayQueue::new(10);
x.add(String::from("K"));
assert_eq!(x.remove(), String::from("K"));
}
#[test]
fn test1() {
let mut x: ArrayQueue<Box<String>> = ArrayQueue::new(10);
x.add(Box::new(String::from("K")));
assert_eq!(x.remove(), Box::new(String::from("K")));
}
}
I believe I am doing proper dropping to prevent any memory leaks.
I've attached two test cases where one works, but the other results in a crash due to invalid memory reference.
It crashes inside the add
method (*unsafe {self.buff.get_unchecked_mut(idx)} = elem;
) and I suspect that happens because I am somehow trying to write to an invalid memory location.
I've specifically used heap allocated objects for vector elements in test but to my surprise String
works properly while Box
doesn't.
I would like to understand if it's possible to make an unsafe implementation like this and why it's currently failing?
Edit
I've fixed the issue by replacing *unsafe {self.buff.get_unchecked_mut(idx)} = elem;
with unsafe {std::ptr::write(self.buff.get_unchecked_mut(idx), elem)};
Now I would like to understand why this works and previous version doesn't