use std::{
boxed::Box as StdBox, cell::Cell, marker::PhantomData, mem, ptr::NonNull, sync::Mutex,
vec::Vec as StdVec,
};
use crate::{Allocator, Box, Dummy, Vec};
pub trait ReplaceWith<'a>: Dummy<'a> {
#[inline]
fn replace_with(&mut self, replacer: impl FnOnce(Self) -> Self) {
let ptr = NonNull::from(self);
let old = unsafe { ptr.read() };
let guard = PanicGuard::<'a, Self> { ptr, marker: PhantomData };
let new = replacer(old);
mem::forget(guard);
unsafe { ptr.write(new) };
}
}
impl<'a, T: ReplaceWith<'a>> ReplaceWith<'a> for Option<T> {}
impl<'a, T: Dummy<'a>> ReplaceWith<'a> for Box<'a, T> {}
impl<'a, T> ReplaceWith<'a> for Box<'a, [T]> {}
impl<'a, T> ReplaceWith<'a> for Vec<'a, T> {}
impl<'a, T: ReplaceWith<'a>> ReplaceWith<'a> for Cell<T> {}
struct PanicGuard<'a, T: Dummy<'a>> {
ptr: NonNull<T>,
marker: PhantomData<&'a ()>,
}
impl<'a, T: Dummy<'a>> Drop for PanicGuard<'a, T> {
#[inline]
fn drop(&mut self) {
write_dummy(self.ptr);
}
}
#[inline(never)]
fn write_dummy<'a, T: Dummy<'a>>(ptr: NonNull<T>) {
let allocator = fresh_dummy_allocator();
let dummy = T::dummy(allocator);
unsafe { ptr.write(dummy) };
}
#[expect(clippy::vec_box)]
static DUMMY_ALLOCATORS: Mutex<StdVec<StdBox<Allocator>>> = Mutex::new(StdVec::new());
fn fresh_dummy_allocator() -> &'static Allocator {
let mut allocators = DUMMY_ALLOCATORS.lock().unwrap();
let allocator = &**allocators.push_mut(StdBox::new(Allocator::new()));
unsafe { NonNull::from(allocator).as_ref() }
}
#[cfg(test)]
mod test {
use std::{
iter,
panic::{AssertUnwindSafe, catch_unwind},
};
use crate::{Allocator, Dummy, Vec};
use super::ReplaceWith;
#[test]
fn replace_with_wraps_old_value() {
let allocator = Allocator::default();
let allocator = &allocator;
let mut vec = Vec::from_iter_in([1u32, 2, 3], &allocator);
vec.replace_with(|old| {
assert_eq!(&*old, &[1, 2, 3]);
Vec::from_iter_in(iter::once(0).chain(old), &allocator)
});
assert_eq!(&*vec, &[0, 1, 2, 3]);
}
#[test]
fn replace_with_extracts_part_of_old_value() {
let allocator = Allocator::default();
let allocator = &allocator;
let mut vec = Vec::from_iter_in([10u32, 20, 30], &allocator);
vec.replace_with(|old| {
let first = old.into_iter().next().unwrap();
Vec::from_iter_in([first], &allocator)
});
assert_eq!(&*vec, &[10]);
}
#[test]
fn replace_with_identity_leaves_value_unchanged() {
let allocator = Allocator::default();
let allocator = &allocator;
let mut vec = Vec::from_iter_in([7u32], &allocator);
vec.replace_with(|old| old);
assert_eq!(&*vec, &[7]);
}
#[test]
fn replace_with_writes_dummy_on_panic() {
let allocator = Allocator::default();
let allocator = &allocator;
let mut vec = Vec::from_iter_in([1u32, 2, 3], &allocator);
let result = catch_unwind(AssertUnwindSafe(|| {
vec.replace_with(|_old| panic!("boom"));
}));
assert!(result.is_err());
assert_eq!(&*vec, &[] as &[u32]);
vec.push(42);
assert_eq!(&*vec, &[42]);
}
struct RefHolder<'a>(&'a mut u64);
impl<'a> Dummy<'a> for RefHolder<'a> {
fn dummy(allocator: &'a Allocator) -> Self {
RefHolder(allocator.alloc(0u64))
}
}
impl<'a> ReplaceWith<'a> for RefHolder<'a> {}
#[test]
fn replace_with_mutating_through_moved_out_ref() {
let mut value = 10u64;
let mut holder = RefHolder(&mut value);
holder.replace_with(|old| {
*old.0 += 1;
old
});
*holder.0 += 100;
assert_eq!(value, 111);
}
#[test]
fn replace_with_swapping_in_a_different_ref() {
let mut a = 1u64;
let mut b = 2u64;
let mut holder = RefHolder(&mut a);
holder.replace_with(|_old| RefHolder(&mut b));
*holder.0 += 40;
assert_eq!(a, 1);
assert_eq!(b, 42);
}
}