Has anyone seen a real AsMut<[T]> implementation where repeated calls to as_mut() return slices with different addresses or lengths, even when no other &mut self method is called between them?
In other words, I expect this property:
As long as no other
&mut selfmethod on the object is called between calls,
as_mut()always returns a slice with the same address and length.
I've looked at https://doc.rust-lang.org/std/convert/trait.AsMut.html and haven't found such a statement.
Here is an example of code that:
- relies on this contract, and
- fails with a valid but surprising
AsMutimplementation.
//! Minimal example: code that is generic over `AsMut<[u8]>` silently //! assumes that `as_mut()` is a pure view of the object's state. use std::panic::{catch_unwind, AssertUnwindSafe}; /// Fills a buffer with the letter `x`, one byte at a time. /// The constructor remembers the length, and `pos` is the next free index. struct Filler<B: AsMut<[u8]>> { buf: B, pos: usize, len: usize, } impl<B: AsMut<[u8]>> Filler<B> { fn new(mut buf: B) -> Self { let len = buf.as_mut().len(); Self { buf, pos: 0, len } } fn is_full(&self) -> bool { self.pos == self.len } fn put_x(&mut self) { // A second `as_mut()` call: the stored `pos` and `len` are applied // to whatever slice it returns now. self.buf.as_mut()[self.pos] = b'x'; self.pos += 1; } /// Fill the whole buffer with `x` and give it back. fn fill(mut self) -> B { while !self.is_full() { self.put_x(); } self.buf } } /// Well-behaved: the same as `Vec<u8>`, `[u8; N]`, `Box<[u8]>`, ... struct Stable([u8; 4]); impl AsMut<[u8]> for Stable { fn as_mut(&mut self) -> &mut [u8] { &mut self.0 } } /// Switches between two buffers: `as_mut()` has a side effect. struct Flip { a: [u8; 4], b: [u8; 4], flip: bool, } impl AsMut<[u8]> for Flip { fn as_mut(&mut self) -> &mut [u8] { self.flip = !self.flip; if self.flip { &mut self.a } else { &mut self.b } } } /// Returns a shorter slice after the first call. struct Shrinking { data: [u8; 4], calls: usize, } impl AsMut<[u8]> for Shrinking { fn as_mut(&mut self) -> &mut [u8] { self.calls += 1; if self.calls == 1 { &mut self.data } else { &mut self.data[..2] } } } fn main() { let mut stable = Filler::new(Stable([0; 4])).fill(); println!("stable: {:?}", stable.as_mut()); // No panic, but the bytes are scattered over two buffers. let flip = Filler::new(Flip { a: [0; 4], b: [0; 4], flip: false }).fill(); println!("flip: a={:?} b={:?}", flip.a, flip.b); // Panics: index out of range, `len` is 4 but the slice has 2 bytes. let result = catch_unwind(AssertUnwindSafe(|| { Filler::new(Shrinking { data: [0; 4], calls: 0 }).fill(); })); println!("shrinking: panicked = {}", result.is_err()); } Running:
stable: [120, 120, 120, 120] flip: a=[0, 120, 0, 120] b=[120, 0, 120, 0] thread 'main' (456197) panicked at adversarial_asmut.rs:27:9: index out of bounds: the len is 2 but the index is 2 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace shrinking: panicked = true 1 post - 1 participant
