use std::mem::{MaybeUninit, replace, transmute}; use std::ops::Deref; use crate::error::*; pub struct Stack { items: [MaybeUninit; CAP], top: usize, } macro_rules! into { ($e:expr) => { // SAFETY: This macro is used to transmute `MaybeUninit>` to `Value<'vm>` // or `&MaybeUninit>` to `&Value<'vm>`. // This is safe because the `Stack` ensures that only initialized values are accessed // within the `0..top` range. unsafe { transmute($e) } }; } impl Stack { pub fn new() -> Self { Stack { items: [const { MaybeUninit::uninit() }; CAP], top: 0, } } pub fn push(&mut self, item: T) -> Result<()> { self.items .get_mut(self.top) .map_or_else( || Err(Error::EvalError("stack overflow".to_string())), |ok| Ok(ok), )? .write(item); self.top += 1; Ok(()) } pub fn pop(&mut self) -> T { self.top -= 1; let item = self.items.get_mut(self.top).unwrap(); // SAFETY: `item` at `self.top` was previously written and is initialized. // We replace it with `MaybeUninit::uninit()` and then `assume_init` // on the original value, which is safe as it was initialized. unsafe { replace(item, MaybeUninit::uninit()).assume_init() } } pub fn tos(&self) -> Result<&T> { if self.top == 0 { panic!("stack empty") } else { Ok(into!(&self.items[self.top - 1])) } } pub fn tos_mut(&mut self) -> Result<&mut T> { if self.top == 0 { panic!("stack empty") } else { Ok(into!(&mut self.items[self.top - 1])) } } } impl Deref for Stack { type Target = [T]; fn deref(&self) -> &Self::Target { into!(&self.items[0..self.top]) } } impl Drop for Stack { fn drop(&mut self) { self.items.as_mut_slice()[0..self.top] .iter_mut() // SAFETY: Items in the range `0..self.top` are guaranteed to be initialized. // `assume_init_drop` is called to correctly drop these initialized `Value`s. .map(|item| unsafe { item.assume_init_drop() }) .for_each(drop) } }