Change:
liballoc's fails when run in Miri. The error occurs in the call: is called with a as argument. contains contains a shared reference to a slice; that slice is thus marked as "must not be mutated for the entire duration of this function call". calls calls calls calls , which eventually drops the . calls to re-arrange stuff (I have not fully understood this yet), and in some cases this will end up writing to memory that the slice in points to. I am not sure what the best way to fix this is. We have to fix holding (indirectly) a shared reference to something that it'll mutate during its . The mutation is likely there for a reason, so I guess the shared reference has to go. (FWIW, this shared reference already caused other trouble, but that was fixed by ) Cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
yeah I guess just change it to and toss in a PhantomData for the borrow. shrug
You mean, change this in as well? Sure, that should work. In principle this reduces the precision of compiler analyses on .
ahh misread. maybe would just not use Iter then. depends on what the impl looks like now
But the first thing it does is which should exhaust the such that its is an empty slice. I suppose its pointer will still be somewhere in the deque's memory, but with length 0 it can't actually read anything. Is that not good enough to appease Miri?
No. This code still matches the pattern There is no notion of "exhausting" or "consuming" a shared reference. When you create a shared reference with a certain lifetime, you promise that until that lifetime ends, no mutation happens. here is mutable so you can change what the slice points to, but that has no effect at all on the guarantees that have been made about the value that was initially passed to . Now, lifetimes are an entirely static concept, so for Stacked Borrows a "lifetime" generally is "until the pointer is used for the last time". However, for the special case of a reference being passed to a function, Stacked Borrows asserts that the "lifetime" will last at least as long as the function the reference is pointed to. (This is enforced by the "barriers" explained .) This is extremely useful for optimizations.
forward to . I wouldn't want to copy those.
Oh, I also didn't realize that doesn't actually shrink down to an empty slice -- it only updates its indexes. It would otherwise have to use two slices for the wrapped head and tail. So that full shared reference does still exist after the iterator is exhausted. (This also raises the validity question of having references to uninitialized data.) Could we base on instead? We could arrange to work through that mutable reference too, so there's no question of mutable aliasing.
That might work. However, must not access in that case -- that would be in conflict with the unique reference in .
Hmm, does need to be covariant over ? I guess would break that.
Actually this idea of "consuming a shared ref" is far from unique to ... the following code also errors in Miri:
As I understand it, Miri and stacked borrows should be "more powerful" than the Rust borrow checker, right? So, is this an example where Miri does not match what Rust allows or is this a bug in Rust that could lead to undefined behaviour in safe code? ()
-- I believe is admitting that example as something that should be allowed. But this issue with involves raw pointers, which rustc doesn't check at all, so miri is in uncharted waters here.
This is up for us all to decide. I can't tell you. ;) Not necessary. I have argued that / are problematic long before Stacked Borrows. The lifetimes there are, strictly speaking, wrong. I think it would be reasonable to rule that they have to use raw pointers.
FWIW here is a stand-alone version of the testcase to reproduce this with Miri (but it needs access to some private internals):
See for the issue about whether Stacked Borrows should allow such code or not. For now, with , Miri accepts this code again. So I am going to close the issue here. It still represents an interesting data point to inform what Stacked Borrows rules should be.
nods wisely just gotta stand still long enough for the spec to bend around you
;-) I don't think the decision is made either way, but for now with several counterexamples in libstd, it is not helpful for Miri to complain about this. Plus, this matches what we formalized in Coq; the basic optimizations that we considered so far are all fine with this change.
See there actually is a bug here right now, because we do emit for passed to a function. So what Miri found is actually UB in the LLVM IR that we are generating right now. Reopening for now, but maybe it makes more sense to treat this as a duplicate of
Smaller reproduction that I found in (a now closed dupe of this issue) that doesn't rely on VecDeque internals, that fails now with
re-enables the part of Stacked Borrows that got disabled (in parts) to work around the issue, before I knew that we actually have here and are risking real miscompilations.
claim

Discussion:
<ins> use core::fmt; </ins> use core::iter::FusedIterator; <ins> use core::marker::PhantomData; use core::mem::{self, MaybeUninit}; </ins> use core::ptr::{self, NonNull}; <del> use core::{fmt, mem}; </del> use crate::alloc::{Allocator, Global}; <del> use super::{count, Iter, VecDeque}; </del> <ins> use super::{count, wrap_index, VecDeque}; </ins> /// A draining iterator over the elements of a `VecDeque`. ///

> { after_tail: usize, after_head: usize, <del> iter: Iter<'a, T>, </del> <ins> ring: NonNull<[T]>, tail: usize, head: usize, </ins> deque: NonNull<VecDeque<T, A>>, <ins> _phantom: PhantomData<&'a T>, </ins> } impl<'a, T, A: Allocator> Drain<'a, T, A> { pub(super) unsafe fn new( after_tail: usize, after_head: usize, <del> iter: Iter<'a, T>, </del> <ins> ring: &'a [MaybeUninit<T>], tail: usize, head: usize, </ins> deque: NonNull<VecDeque<T, A>>, ) -> Self { <del> Drain { after_tail, after_head, iter, deque } </del> <ins> let ring = unsafe { NonNull::new_unchecked(ring as *const [MaybeUninit<T>] as *mut _) }; Drain { after_tail, after_head, ring, tail, head, deque, _phantom: PhantomData } </ins> } }

f.debug_tuple("Drain") .field(&self.after_tail) .field(&self.after_head) <del> .field(&self.iter) </del> <ins> .field(&self.ring) .field(&self.tail) .field(&self.head) </ins> .finish() } }

#[inline] fn next(&mut self) -> Option<T> { <del> self.iter.next().map(|elt| unsafe { ptr::read(elt) }) </del> <ins> if self.tail == self.head { return None; } let tail = self.tail; self.tail = wrap_index(self.tail.wrapping_add(1), self.ring.len()); // Safety: // - `self.tail` in a ring buffer is always a valid index. // - `self.head` and `self.tail` equality is checked above. unsafe { Some(ptr::read(self.ring.as_ptr().get_unchecked_mut(tail))) } </ins> } #[inline] fn size_hint(&self) -> (usize, Option<usize>) { <del> self.iter.size_hint() </del> <ins> let len = count(self.tail, self.head, self.ring.len()); (len, Some(len)) </ins> } }

impl<T, A: Allocator> DoubleEndedIterator for Drain<'_, T, A> { #[inline] fn next_back(&mut self) -> Option<T> { <del> self.iter.next_back().map(|elt| unsafe { ptr::read(elt) }) </del> <ins> if self.tail == self.head { return None; } self.head = wrap_index(self.head.wrapping_sub(1), self.ring.len()); // Safety: // - `self.head` in a ring buffer is always a valid index. // - `self.head` and `self.tail` equality is checked above. unsafe { Some(ptr::read(self.ring.as_ptr().get_unchecked_mut(self.head))) } </ins> } }

// it.  We do not write to `self` nor reborrow to a mutable reference. // Hence the raw pointer we created above, for `deque`, remains valid. let ring = self.buffer_as_slice(); <del> let iter = Iter::new(ring, drain_tail, drain_head); Drain::new(drain_head, head, iter, deque) </del> <ins> Drain::new(drain_head, head, ring, drain_tail, drain_head, deque) </ins> } }