Change:
<!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --This is my first bug report, so please correct me s if I miss anything :-) I use VecDeque<u8in a toy application () which is handling external reads in a loop, and does possible partial handling of the input. My pattern is I call reserve(N) before pushback() N times, then makecontiguous(), then doing some popfront(). I noticed the app was behaving badly, and was able to generate a stand-alone test case which shows the bug. The code is pretty boring and repetitive, so I put it at to avoid spamming here. Just issue "cargo run" after cloning out that code, and look for the word "BUG" within the terminal output: According to the docs, popfront() on an empty VecDeque should return None. Instead, this happens: If I set the boolean "doreserve" to be false, thus getting rid of all the reserve() calls, I get the expected behavior: The same happens if I set "domake_contiguous" to false as well - so calling both functions is a prerequisite to trigger the bug. The same thing happens in debug and release builds. <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --The below is the version I found it in, but thanks a lot to Steve Klabnik for also testing it on nightly and getting the same buggy behavior. :
I created a minimal sample:
Hmm, if it double-frees then why does Miri not catch it?
Assigning and removing as in the prioritization working group.
Some time ago I've read somewhere that Rust stdlib VecDeque is cursed. I didn't believe them. I'm changing opinion... -.-
This specific case doesn't double free (i32::drop is a noop), using a might have been a better example...
Still, this is memory-unsafe, right?
Yes, It's still a use-after-free regardless
claim
Based on my conversation with lcnr, jonas-schievink, and others , it seems that using with copy types is not UB (it still exhibits a bug though!) because copy types aren't freed. It is UB with non-copy types though (e.g. ).

Discussion:
#[inline] fn is_contiguous(&self) -> bool { <ins> // FIXME: Should we consider `head == 0` to mean // that `self` is contiguous? </ins> self.tail <= self.head }

if self.is_contiguous() { let tail = self.tail; let head = self.head; <del> return unsafe { &mut self.buffer_as_mut_slice()[tail..head] }; </del> <ins> return unsafe { RingSlices::ring_slices(self.buffer_as_mut_slice(), head, tail).0 }; </ins> } let buf = self.buf.ptr();

self.tail = 0; self.head = len; } <del> } else if free >= self.head { </del> <ins> } else if free > self.head { // FIXME: We currently do not consider ....ABCDEFGH // to be contiguous because `head` would be `0` in this // case. While we probably want to change this it // isn't trivial as a few places expect `is_contiguous` // to mean that we can just slice using `buf[tail..head]`. </ins> // there is enough free space to copy the head in one go, // this means that we first shift the tail forwards, and then // copy the head to the correct position.

// ...ABCDEFGH. self.tail = self.head; <del> self.head = self.tail + len; </del> <ins> self.head = self.wrap_add(self.tail, len); </ins> } } else { // free is smaller than both head and tail,

let tail = self.tail; let head = self.head; <del> unsafe { &mut self.buffer_as_mut_slice()[tail..head] } </del> <ins> unsafe { RingSlices::ring_slices(self.buffer_as_mut_slice(), head, tail).0 } </ins> } /// Rotates the double-ended queue `mid` places to the left.

let len = other.len(); let cap = other.cap(); <del> if other.head != 0 { </del> <ins> if other.tail != 0 { </ins> ptr::copy(buf.add(other.tail), buf, len); } Vec::from_raw_parts(buf, len, cap)

} #[test] <ins> fn make_contiguous_head_to_end() { let mut dq = VecDeque::with_capacity(3); dq.push_front('B'); dq.push_front('A'); dq.push_back('C'); dq.make_contiguous(); let expected_tail = 0; let expected_head = 3; assert_eq!(expected_tail, dq.tail); assert_eq!(expected_head, dq.head); assert_eq!((&['A', 'B', 'C'] as &[_], &[] as &[_]), dq.as_slices()); } #[test] </ins> fn test_remove() { // This test checks that every single combination of tail position, length, and // removal position is tested. Capacity 15 should be large enough to cover every case.