(Regulars here will know me from IRLO, but this is actually my first time on URLO!)
I'm building a generic arena-ey data structure, and I've run into a place where I don't know how to avoid breaking the aliasing rules by mistake.
I want to write something morally like the following struct (let's pretend for five seconds that we're writing C++ and there's no borrow checker):
struct Arena<Mem: AsMut<[u8]>> {
mem: Mem,
cursor: usize,
}
I will then provide the following inherent methods:
fn alloc(&self, len: usize) -> Result<&mut [u8]> {
if len + cursor > self.slice.len() { return Err(...); }
// This line below is sketchy.
let slice = &mut self.mem.as_mut()[self.cursor..self.cursor+len];
self.cursor += len;
Ok(slice)
}
fn reset(&mut self) {
self.cursor = 0;
}
Note that it is impossible for any of the references returned by alloc()
to be held when we call reset()
, because reset()
requires &mut self
, so from a caller's perspective, you can't create aliasing references.
Obviously, this implementation makes the borrow checker extremely unhappy, especially since, when Mem = &mut [u8]
, we have an obvious aliasing violation, so the borrow checker correctly rejects this program.
My question is: what is the most expedient way to correctly write this program, such that:
- is
no_std
/no_alloc
. - uses preferably only one
unsafe
block. (I believecore
does not have the tools do do this fully in safe Rust.) My project is very allergic tounsafe
.
I also want to generalize alloc
and reset
into a single trait, which complicates how I can split up the lifetimes across types. My first thought was to use MaybeUninit
to hide bytes
from the compiler, but I'm pretty sure that if I don't ask here I'm going to accidentally materialize two aliasing unique references and get extremely sad (I'm fairly certain that it's not possible to break returned references by moving the arena, even when Mem = [u8; N]
, since every slice in active use pins the arena in place).