use std::{
alloc::{dealloc, handle_alloc_error, Layout},
any::TypeId,
borrow::{Borrow, Cow},
cell::Cell,
mem::{self, ManuallyDrop, MaybeUninit},
num::NonZeroUsize,
ops::Deref,
ptr::{self, addr_of, addr_of_mut, copy_nonoverlapping, drop_in_place, NonNull},
slice,
};
use crate::{
contains_gcs, panic_deref_of_collected_object, ptr::Nullable, Trace, TraceWith, Visitor,
};
use self::collect::{Dfs, DropAlloc, Dumpster, Mark, DUMPSTER};
mod collect;
#[cfg(test)]
mod tests;
#[expect(private_bounds)]
pub(crate) trait TraceUnsync:
TraceWith<Dfs> + TraceWith<Mark> + for<'a> TraceWith<DropAlloc<'a>> + TraceWith<Rehydrate>
{
}
impl<T> TraceUnsync for T where
T: ?Sized
+ TraceWith<Dfs>
+ TraceWith<Mark>
+ for<'a> TraceWith<DropAlloc<'a>>
+ TraceWith<Rehydrate>
{
}
#[derive(Debug)]
pub struct Gc<T: Trace + ?Sized + 'static> {
ptr: Cell<Nullable<GcBox<T>>>,
}
pub fn collect() {
DUMPSTER.with(Dumpster::collect_all);
}
pub struct CollectInfo {
_private: (),
}
pub type CollectCondition = fn(&CollectInfo) -> bool;
#[must_use]
pub fn default_collect_condition(info: &CollectInfo) -> bool {
info.n_gcs_dropped_since_last_collect() > info.n_gcs_existing()
}
pub fn set_collect_condition(f: CollectCondition) {
DUMPSTER.with(|d| d.collect_condition.set(f));
}
#[repr(C)]
#[doc(hidden)]
pub struct GcBox<T: Trace + ?Sized> {
ref_count: Cell<NonZeroUsize>,
value: T,
}
impl<T: Trace + ?Sized> Gc<T> {
pub fn new(value: T) -> Gc<T>
where
T: Sized,
{
DUMPSTER.with(Dumpster::notify_created_gc);
Gc {
ptr: Cell::new(Nullable::new(NonNull::from(Box::leak(Box::new(GcBox {
ref_count: Cell::new(NonZeroUsize::MIN),
value,
}))))),
}
}
pub fn new_cyclic<F: FnOnce(Gc<T>) -> T>(data_fn: F) -> Self
where
T: Sized,
{
#[repr(transparent)]
struct Uninitialized<T>(MaybeUninit<T>);
unsafe impl<V: Visitor, T> TraceWith<V> for Uninitialized<T> {
fn accept(&self, _: &mut V) -> Result<(), ()> {
Ok(())
}
}
struct CleanUp<T: Trace + 'static> {
initialized: bool,
ptr: NonNull<GcBox<T>>,
}
impl<T: Trace + 'static> Drop for CleanUp<T> {
fn drop(&mut self) {
if self.initialized {
DUMPSTER.with(|d| d.mark_dirty(self.ptr));
} else {
unsafe {
dealloc(
self.ptr.as_ptr().cast::<u8>(),
Layout::for_value(self.ptr.as_ref()),
);
}
}
}
}
DUMPSTER.with(Dumpster::notify_created_gc);
let mut gcbox = NonNull::from(Box::leak(Box::new(GcBox {
ref_count: Cell::new(NonZeroUsize::MIN),
value: Uninitialized(MaybeUninit::<T>::uninit()),
})));
let mut cleanup = CleanUp {
ptr: gcbox,
initialized: false,
};
let nilgc = Gc {
ptr: Cell::new(Nullable::new(gcbox.cast::<GcBox<T>>()).as_null()),
};
assert!(Gc::is_dead(&nilgc));
unsafe {
gcbox.as_mut().value = Uninitialized(MaybeUninit::new(data_fn(nilgc)));
}
cleanup.initialized = true;
let gcbox = gcbox.cast::<GcBox<T>>();
let res = unsafe {
gcbox.as_ref().value.accept(&mut Rehydrate {
ptr: Nullable::new(gcbox.cast()),
type_id: TypeId::of::<T>(),
})
};
assert!(
res.is_ok(),
"visitor must be able to access all Gc fields of structure when rehydrating dead Gcs"
);
let gc = Gc {
ptr: Cell::new(Nullable::new(gcbox)),
};
let _ = ManuallyDrop::new(cleanup);
gc
}
pub fn try_deref(gc: &Gc<T>) -> Option<&T> {
(!gc.ptr.get().is_null()).then(|| &**gc)
}
pub fn try_clone(gc: &Gc<T>) -> Option<Gc<T>> {
(!gc.ptr.get().is_null()).then(|| gc.clone())
}
pub fn as_ptr(gc: &Gc<T>) -> *const T {
let ptr = NonNull::as_ptr(gc.ptr.get().unwrap());
unsafe { addr_of_mut!((*ptr).value) }
}
pub fn ptr_eq(this: &Gc<T>, other: &Gc<T>) -> bool {
this.ptr.get().as_option() == other.ptr.get().as_option()
}
pub fn ref_count(gc: &Self) -> NonZeroUsize {
let box_ptr = gc.ptr.get().expect(
"Attempt to dereference Gc to already-collected object. \
This means a Gc escaped from a Drop implementation, likely implying a bug in your code.",
);
let box_ref = unsafe { box_ptr.as_ref() };
box_ref.ref_count.get()
}
pub fn is_dead(gc: &Self) -> bool {
gc.ptr.get().is_null()
}
#[inline]
#[must_use]
fn into_ptr(this: Self) -> *const GcBox<T> {
let this = ManuallyDrop::new(this);
this.ptr.get().as_ptr()
}
#[inline]
#[must_use]
unsafe fn from_ptr(ptr: *const GcBox<T>) -> Self {
Self {
ptr: Cell::new(Nullable::from_ptr(ptr.cast_mut())),
}
}
#[inline]
#[must_use]
#[doc(hidden)]
pub fn __private_into_ptr(this: Self) -> *const GcBox<T> {
Self::into_ptr(this)
}
#[inline]
#[must_use]
#[doc(hidden)]
pub unsafe fn __private_from_ptr(ptr: *const GcBox<T>) -> Self {
Self::from_ptr(ptr)
}
fn kill(&self) {
self.ptr.set(self.ptr.get().as_null());
}
}
pub(super) struct Rehydrate {
ptr: Nullable<GcBox<()>>,
type_id: TypeId,
}
impl Visitor for Rehydrate {
fn visit_sync<T>(&mut self, _: &crate::sync::Gc<T>)
where
T: Trace + Send + Sync + ?Sized,
{
}
fn visit_unsync<T>(&mut self, gc: &Gc<T>)
where
T: Trace + ?Sized,
{
if Gc::is_dead(gc) && TypeId::of::<T>() == self.type_id {
unsafe {
let cell_ptr = (&raw const gc.ptr).cast::<Cell<Nullable<GcBox<()>>>>();
(*cell_ptr).set(self.ptr);
let box_ref = &*self.ptr.as_ptr();
box_ref
.ref_count
.set(box_ref.ref_count.get().saturating_add(1));
DUMPSTER.with(Dumpster::notify_created_gc);
}
}
}
}
impl<T: Trace + Clone> Gc<T> {
#[inline]
pub fn make_mut(this: &mut Self) -> &mut T {
if Gc::is_dead(this) {
panic_deref_of_collected_object();
}
let ptr = unsafe { this.ptr.get().unwrap_unchecked() };
let box_ref = unsafe { ptr.as_ref() };
if box_ref.ref_count.get() == NonZeroUsize::MIN {
DUMPSTER.with(|d| d.mark_cleaned(ptr));
} else {
*this = Gc::new(box_ref.value.clone());
}
unsafe { &mut (*this.ptr.get_mut().as_ptr()).value }
}
}
impl<T: Trace + ?Sized> Gc<T> {
unsafe fn allocate_for_layout(
value_layout: Layout,
mem_to_gc_box: impl FnOnce(*mut u8) -> *mut GcBox<T>,
) -> *mut GcBox<T> {
let layout = Layout::new::<GcBox<()>>()
.extend(value_layout)
.unwrap()
.0
.pad_to_align();
Self::allocate_for_layout_of_box(layout, mem_to_gc_box)
}
unsafe fn allocate_for_layout_of_box(
layout: Layout,
mem_to_gc_box: impl FnOnce(*mut u8) -> *mut GcBox<T>,
) -> *mut GcBox<T> {
let ptr = unsafe { std::alloc::alloc(layout) };
if ptr.is_null() {
handle_alloc_error(layout);
}
let inner = mem_to_gc_box(ptr);
unsafe {
(&raw mut (*inner).ref_count).write(Cell::new(NonZeroUsize::MIN));
}
inner
}
}
impl<T: Trace> Gc<[T]> {
#[inline]
fn allocate_for_slice(len: usize) -> *mut GcBox<[T]> {
unsafe {
Self::allocate_for_layout(Layout::array::<T>(len).unwrap(), |mem| {
ptr::slice_from_raw_parts_mut(mem.cast::<T>(), len) as *mut GcBox<[T]>
})
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! __unsync_coerce_gc {
($gc:expr) => {{
let ptr: *const _ = $crate::unsync::Gc::__private_into_ptr($gc);
unsafe { $crate::unsync::Gc::__private_from_ptr(ptr) }
}};
}
#[doc(inline)]
pub use crate::__unsync_coerce_gc as coerce_gc;
impl<T: Trace + ?Sized> Deref for Gc<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe {
&self.ptr.get().expect("dereferencing Gc to already-collected object. \
This means a Gc escaped from a Drop implementation, likely implying a bug in your code.").as_ref().value
}
}
}
impl<T: Trace + ?Sized> Clone for Gc<T> {
fn clone(&self) -> Self {
let Some(ptr) = self.ptr.get().as_option() else {
return Self {
ptr: self.ptr.clone(),
};
};
unsafe {
let box_ref = ptr.as_ref();
box_ref
.ref_count
.set(box_ref.ref_count.get().saturating_add(1));
}
DUMPSTER.with(|d| {
d.notify_created_gc();
});
Self {
ptr: self.ptr.clone(),
}
}
}
impl<T: Trace + ?Sized> Drop for Gc<T> {
fn drop(&mut self) {
let Some(mut ptr) = self.ptr.get().as_option() else {
return;
};
DUMPSTER.with(|d| {
let box_ref = unsafe { ptr.as_ref() };
match box_ref.ref_count.get() {
NonZeroUsize::MIN => {
d.mark_cleaned(ptr);
unsafe {
drop_in_place(addr_of_mut!(ptr.as_mut().value));
dealloc(ptr.as_ptr().cast::<u8>(), Layout::for_value(ptr.as_ref()));
}
}
n => {
box_ref
.ref_count
.set(NonZeroUsize::new(n.get() - 1).unwrap());
if contains_gcs(&box_ref.value).unwrap_or(true) {
d.mark_dirty(ptr);
}
}
}
d.notify_dropped_gc();
});
}
}
impl<T> PartialEq<Gc<T>> for Gc<T>
where
T: Trace + ?Sized + PartialEq,
{
fn eq(&self, other: &Gc<T>) -> bool {
self.as_ref() == other.as_ref()
}
}
impl<T> Eq for Gc<T> where T: Trace + ?Sized + PartialEq {}
impl CollectInfo {
#[must_use]
pub fn n_gcs_dropped_since_last_collect(&self) -> usize {
DUMPSTER.with(|d| d.n_ref_drops.get())
}
#[must_use]
pub fn n_gcs_existing(&self) -> usize {
DUMPSTER.with(|d| d.n_refs_living.get())
}
}
unsafe impl<V: Visitor, T: Trace + ?Sized> TraceWith<V> for Gc<T> {
fn accept(&self, visitor: &mut V) -> Result<(), ()> {
visitor.visit_unsync(self);
Ok(())
}
}
impl<T: Trace + ?Sized> AsRef<T> for Gc<T> {
fn as_ref(&self) -> &T {
self
}
}
impl<T: Trace + ?Sized> Borrow<T> for Gc<T> {
fn borrow(&self) -> &T {
self
}
}
impl<T: Trace + Default> Default for Gc<T> {
fn default() -> Self {
Gc::new(T::default())
}
}
impl<T: Trace + ?Sized> std::fmt::Pointer for Gc<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Pointer::fmt(&addr_of!(**self), f)
}
}
#[cfg(feature = "coerce-unsized")]
impl<T, U> std::ops::CoerceUnsized<Gc<U>> for Gc<T>
where
T: std::marker::Unsize<U> + Trace + ?Sized,
U: Trace + ?Sized,
{
}
impl<T: Trace> From<T> for Gc<T> {
fn from(value: T) -> Self {
Gc::new(value)
}
}
impl<T: Trace, const N: usize> From<[T; N]> for Gc<[T]> {
#[inline]
fn from(v: [T; N]) -> Gc<[T]> {
coerce_gc!(Gc::<[T; N]>::from(v))
}
}
impl<T: Trace + Clone> From<&[T]> for Gc<[T]> {
#[inline]
fn from(slice: &[T]) -> Gc<[T]> {
struct Guard<T> {
mem: *mut u8,
layout: Layout,
elems: *mut T,
n_elems: usize,
}
impl<T> Drop for Guard<T> {
fn drop(&mut self) {
unsafe {
let slice = slice::from_raw_parts_mut(self.elems, self.n_elems);
ptr::drop_in_place(slice);
dealloc(self.mem, self.layout);
}
}
}
unsafe {
let value_layout = Layout::array::<T>(slice.len()).unwrap();
let layout = Layout::new::<GcBox<()>>()
.extend(value_layout)
.unwrap()
.0
.pad_to_align();
let ptr = Self::allocate_for_layout_of_box(layout, |mem| {
ptr::slice_from_raw_parts_mut(mem.cast::<T>(), slice.len()) as *mut GcBox<[T]>
});
let elems = (&raw mut (*ptr).value).cast::<T>();
let mut guard = Guard {
mem: ptr.cast::<u8>(),
layout,
elems,
n_elems: 0,
};
for (i, item) in slice.iter().enumerate() {
ptr::write(elems.add(i), item.clone());
guard.n_elems += 1;
}
mem::forget(guard);
DUMPSTER.with(Dumpster::notify_created_gc);
Self {
ptr: Cell::new(Nullable::from_ptr(ptr)),
}
}
}
}
impl<T: Trace + Clone> From<&mut [T]> for Gc<[T]> {
#[inline]
fn from(value: &mut [T]) -> Self {
Gc::from(&*value)
}
}
impl From<&str> for Gc<str> {
#[inline]
fn from(v: &str) -> Self {
let bytes = Gc::<[u8]>::from(v.as_bytes());
unsafe { Gc::from_ptr(Gc::into_ptr(bytes) as *const GcBox<str>) }
}
}
impl From<&mut str> for Gc<str> {
#[inline]
fn from(v: &mut str) -> Self {
Gc::from(&*v)
}
}
impl From<Gc<str>> for Gc<[u8]> {
#[inline]
fn from(value: Gc<str>) -> Self {
unsafe { Gc::from_ptr(Gc::into_ptr(value) as *const GcBox<[u8]>) }
}
}
impl From<String> for Gc<str> {
#[inline]
fn from(value: String) -> Self {
Self::from(&value[..])
}
}
impl<T: Trace> From<Box<T>> for Gc<T> {
#[inline]
fn from(src: Box<T>) -> Self {
unsafe {
let layout = Layout::for_value(&*src);
let gc_ptr = Gc::allocate_for_layout(layout, <*mut u8>::cast::<GcBox<T>>);
ptr::copy_nonoverlapping(
(&raw const *src).cast::<u8>(),
(&raw mut (*gc_ptr).value).cast::<u8>(),
layout.size(),
);
let bptr = Box::into_raw(src);
let src = Box::from_raw(bptr.cast::<mem::ManuallyDrop<T>>());
drop(src);
DUMPSTER.with(Dumpster::notify_created_gc);
Self::from_ptr(gc_ptr)
}
}
}
impl<T: Trace> From<Vec<T>> for Gc<[T]> {
#[inline]
fn from(vec: Vec<T>) -> Self {
let mut vec = ManuallyDrop::new(vec);
let vec_cap = vec.capacity();
let vec_len = vec.len();
let vec_ptr = vec.as_mut_ptr();
let gc_ptr = Self::allocate_for_slice(vec_len);
unsafe {
let dst_ptr = (&raw mut (*gc_ptr).value).cast::<T>();
ptr::copy_nonoverlapping(vec_ptr, dst_ptr, vec_len);
let _ = Vec::from_raw_parts(vec_ptr, 0, vec_cap);
DUMPSTER.with(Dumpster::notify_created_gc);
Self::from_ptr(gc_ptr)
}
}
}
impl<'a, B: Trace> From<Cow<'a, B>> for Gc<B>
where
B: ToOwned + ?Sized,
Gc<B>: From<&'a B> + From<B::Owned>,
{
#[inline]
fn from(cow: Cow<'a, B>) -> Gc<B> {
match cow {
Cow::Borrowed(s) => Gc::from(s),
Cow::Owned(s) => Gc::from(s),
}
}
}
impl<T> FromIterator<T> for Gc<[T]>
where
T: Trace,
{
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut t_vec = iter.into_iter().collect::<Vec<_>>();
let n = t_vec.len();
let box_ptr = Gc::allocate_for_slice(n);
let gc = unsafe {
copy_nonoverlapping(t_vec.as_ptr(), (*box_ptr).value.as_mut_ptr(), n);
t_vec.set_len(0); Gc::from_ptr(box_ptr)
};
DUMPSTER.with(Dumpster::notify_created_gc);
gc
}
}