pub struct Gc<T: Trace + ?Sized + 'static> { /* private fields */ }Expand description
A garbage-collected pointer.
This garbage-collected pointer may be used for data which is not safe to share across threads
(such as a std::cell::RefCell).
It can also be used for variably sized data.
§Examples
use dumpster::unsync::Gc;
let x: Gc<u8> = Gc::new(3);
println!("{}", *x); // prints '3'
// x is then freed automatically!§Interaction with Drop
While collecting cycles, it’s possible for a Gc to exist that points to some deallocated
object.
To prevent undefined behavior, these Gcs are marked as dead during collection and rendered
inaccessible.
Dereferencing or cloning a Gc during the Drop implementation of a Trace type could
result in the program panicking to keep the program from accessing memory after freeing it.
If you’re accessing a Gc during a Drop implementation, make sure to use the fallible
operations Gc::try_deref and Gc::try_clone.
Implementations§
Source§impl<T: Trace + ?Sized> Gc<T>
impl<T: Trace + ?Sized> Gc<T>
Sourcepub fn new(value: T) -> Gc<T>where
T: Sized,
pub fn new(value: T) -> Gc<T>where
T: Sized,
Construct a new garbage-collected allocation, with value as its value.
§Examples
use dumpster::unsync::Gc;
let gc = Gc::new(0);Sourcepub fn new_cyclic<F: FnOnce(Gc<T>) -> T>(data_fn: F) -> Selfwhere
T: Sized,
pub fn new_cyclic<F: FnOnce(Gc<T>) -> T>(data_fn: F) -> Selfwhere
T: Sized,
Construct a self-referencing Gc.
new_cyclic first allocates memory for T, then constructs a dead Gc pointing to the
allocation. The dead Gc is then passed to data_fn to construct a value of T, which
is stored in the allocation. Finally, new_cyclic will update the dead self-referential
Gcs and rehydrate them to produce the final value.
§Panics
If data_fn panics, the panic is propagated to the caller.
The allocation is cleaned up normally.
Additionally, if, when attempting to rehydrate the Gc members of F, the visitor fails to
reach a Gc, this function will panic and reserve the allocation to be cleaned up
later.
§Notes on safety
Incorrect implementations of data_fn may have unusual or strange results.
Although dumpster guarantees that it will be safe, and will do its best to ensure correct
results, it is generally unwise to allow dead Gcs to exist for long.
If you implement data_fn wrong, this may cause panics later on inside of the collection
process.
§Examples
use dumpster::{unsync::Gc, Trace};
#[derive(Trace)]
struct Cycle {
this: Gc<Self>,
}
let gc = Gc::new_cyclic(|this| Cycle { this });
assert!(Gc::ptr_eq(&gc, &gc.this));Sourcepub fn try_deref(gc: &Gc<T>) -> Option<&T>
pub fn try_deref(gc: &Gc<T>) -> Option<&T>
Attempt to dereference this Gc.
This function will return None if self is a “dead” Gc, which points to an
already-deallocated object.
This can only occur if a Gc is accessed during the Drop implementation of a
Trace object.
For a version which panics instead of returning None, consider using Deref.
§Examples
For a still-living Gc, this always returns Some.
use dumpster::unsync::Gc;
let gc1 = Gc::new(0);
assert!(Gc::try_deref(&gc1).is_some());The only way to get a Gc which fails on try_clone is by accessing a Gc during its
Drop implementation.
use dumpster::{unsync::Gc, Trace};
use std::cell::OnceCell;
#[derive(Trace)]
struct Cycle(OnceCell<Gc<Self>>);
impl Drop for Cycle {
fn drop(&mut self) {
let maybe_ref = Gc::try_deref(self.0.get().unwrap());
assert!(maybe_ref.is_none());
}
}
let gc1 = Gc::new(Cycle(OnceCell::new()));
gc1.0.set(gc1.clone());Sourcepub fn try_clone(gc: &Gc<T>) -> Option<Gc<T>>
pub fn try_clone(gc: &Gc<T>) -> Option<Gc<T>>
Attempt to clone this Gc.
This function will return None if self is a “dead” Gc, which points to an
already-deallocated object.
This can only occur if a Gc is accessed during the Drop implementation of a
Trace object.
For a version which panics instead of returning None, consider using Clone.
§Examples
For a still-living Gc, this always returns Some.
use dumpster::unsync::Gc;
let gc1 = Gc::new(0);
let gc2 = Gc::try_clone(&gc1).unwrap();The only way to get a Gc which fails on try_clone is by accessing a Gc during its
Drop implementation.
use dumpster::{unsync::Gc, Trace};
use std::cell::OnceCell;
#[derive(Trace)]
struct Cycle(OnceCell<Gc<Self>>);
impl Drop for Cycle {
fn drop(&mut self) {
let cloned = Gc::try_clone(self.0.get().unwrap());
assert!(cloned.is_none());
}
}
let gc1 = Gc::new(Cycle(OnceCell::new()));
gc1.0.set(gc1.clone());Sourcepub fn as_ptr(gc: &Gc<T>) -> *const T
pub fn as_ptr(gc: &Gc<T>) -> *const T
Provides a raw pointer to the data.
Panics if self is a “dead” Gc,
which points to an already-deallocated object.
This can only occur if a Gc is accessed during the Drop implementation of a
Trace object.
§Examples
use dumpster::unsync::Gc;
let x = Gc::new("hello".to_owned());
let y = Gc::clone(&x);
let x_ptr = Gc::as_ptr(&x);
assert_eq!(x_ptr, Gc::as_ptr(&x));
assert_eq!(unsafe { &*x_ptr }, "hello");Sourcepub fn ptr_eq(this: &Gc<T>, other: &Gc<T>) -> bool
pub fn ptr_eq(this: &Gc<T>, other: &Gc<T>) -> bool
Determine whether two Gcs are equivalent by reference.
Returns true if both this and other point to the same value, in the same style as
std::ptr::eq.
§Examples
use dumpster::unsync::Gc;
let gc1 = Gc::new(0);
let gc2 = Gc::clone(&gc1); // points to same spot as `gc1`
let gc3 = Gc::new(0); // same value, but points to a different object than `gc1`
assert!(Gc::ptr_eq(&gc1, &gc2));
assert!(!Gc::ptr_eq(&gc1, &gc3));Sourcepub fn ref_count(&self) -> NonZeroUsize
pub fn ref_count(&self) -> NonZeroUsize
Get the number of references to the value pointed to by this Gc.
This does not include internal references generated by the garbage collector.
§Panics
This function may panic if the Gc whose reference count we are loading is “dead” (i.e.
generated through a Drop implementation). For further reference, take a look at
Gc::is_dead.
§Examples
use dumpster::unsync::Gc;
let gc = Gc::new(());
assert_eq!(gc.ref_count().get(), 1);
let gc2 = gc.clone();
assert_eq!(gc.ref_count().get(), 2);
drop(gc);
drop(gc2);Sourcepub fn is_dead(&self) -> bool
pub fn is_dead(&self) -> bool
Determine whether this is a dead Gc.
A Gc is dead if it is accessed while the value it points to has been destroyed; this only
occurs if one attempts to interact with a Gc during a structure’s Drop implementation.
However, this is not always guaranteed - sometime the garbage collector will leave Gcs
alive in differing orders, so users should not rely on the destruction order of Gcs to
determine whether it is dead.
§Examples
use dumpster::{unsync::Gc, Trace};
use std::cell::OnceCell;
#[derive(Trace)]
struct Cycle(OnceCell<Gc<Self>>);
impl Drop for Cycle {
fn drop(&mut self) {
assert!(self.0.get().unwrap().is_dead());
}
}
let gc1 = Gc::new(Cycle(OnceCell::new()));
gc1.0.set(gc1.clone());Source§impl<T: Trace + Clone> Gc<T>
impl<T: Trace + Clone> Gc<T>
Sourcepub fn make_mut(this: &mut Self) -> &mut T
pub fn make_mut(this: &mut Self) -> &mut T
Makes a mutable reference to the given Gc.
If there are other Gc pointers to the same allocation, then make_mut will
clone the inner value to a new allocation to ensure unique ownership. This is also
referred to as clone-on-write.
§Panics
This function may panic if the Gc whose reference count we are loading is “dead” (i.e.
generated through a Drop implementation). For further reference, take a look at
Gc::is_dead.
§Examples
use dumpster::unsync::Gc;
let mut data = Gc::new(5);
*Gc::make_mut(&mut data) += 1; // Won't clone anything
let mut other_data = Gc::clone(&data); // Won't clone inner data
*Gc::make_mut(&mut data) += 1; // Clones inner data
*Gc::make_mut(&mut data) += 1; // Won't clone anything
*Gc::make_mut(&mut other_data) *= 2; // Won't clone anything
// Now `data` and `other_data` point to different allocations.
assert_eq!(*data, 8);
assert_eq!(*other_data, 12);Trait Implementations§
Source§impl<T: Trace + ?Sized> Clone for Gc<T>
impl<T: Trace + ?Sized> Clone for Gc<T>
Source§fn clone(&self) -> Self
fn clone(&self) -> Self
Create a duplicate reference to the same data pointed to by self.
This does not duplicate the data.
§Panics
This function will panic if the Gc being cloned points to a deallocated object.
This is only possible if said Gc is accessed during the Drop implementation of a
Trace value.
For a fallible version, refer to Gc::try_clone.
§Examples
use dumpster::unsync::Gc;
use std::sync::atomic::{AtomicU8, Ordering};
let gc1 = Gc::new(AtomicU8::new(0));
let gc2 = gc1.clone();
gc1.store(1, Ordering::Relaxed);
assert_eq!(gc2.load(Ordering::Relaxed), 1);The following example will fail, because cloning a Gc to a deallocated object is wrong.
use dumpster::{unsync::Gc, Trace};
use std::cell::OnceCell;
#[derive(Trace)]
struct Cycle(OnceCell<Gc<Self>>);
impl Drop for Cycle {
fn drop(&mut self) {
let _ = self.0.get().unwrap().clone();
}
}
let gc1 = Gc::new(Cycle(OnceCell::new()));
gc1.0.set(gc1.clone());1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl<T: Trace + ?Sized> Deref for Gc<T>
impl<T: Trace + ?Sized> Deref for Gc<T>
Source§fn deref(&self) -> &Self::Target
fn deref(&self) -> &Self::Target
Dereference this pointer, creating a reference to the contained value T.
§Panics
This function may panic if it is called from within the implementation of std::ops::Drop
of its owning value, since returning such a reference could cause a use-after-free.
It is not guaranteed to panic.
For a version which returns None instead of panicking, consider Gc::try_deref.
§Examples
The following is a correct time to dereference a Gc.
use dumpster::unsync::Gc;
let my_gc = Gc::new(0u8);
let my_ref: &u8 = &my_gc;Dereferencing a Gc while dropping is not correct.
// This is wrong!
use dumpster::{unsync::Gc, Trace};
use std::cell::RefCell;
#[derive(Trace)]
struct Bad {
s: String,
cycle: RefCell<Option<Gc<Bad>>>,
}
impl Drop for Bad {
fn drop(&mut self) {
println!("{}", self.cycle.borrow().as_ref().unwrap().s)
}
}
let foo = Gc::new(Bad {
s: "foo".to_string(),
cycle: RefCell::new(None),
});Source§impl<T> PartialEq for Gc<T>
impl<T> PartialEq for Gc<T>
Source§fn eq(&self, other: &Gc<T>) -> bool
fn eq(&self, other: &Gc<T>) -> bool
Test for equality on two Gcs.
Two Gcs are equal if their inner values are equal, even if they are stored in different
allocations.
Because PartialEq does not imply reflexivity, and there is no current path for trait
specialization, this function does not do a “fast-path” check for reference equality.
Therefore, if two Gcs point to the same allocation, the implementation of eq will still
require a direct call to eq on the values.
§Panics
This function may panic if it is called from within the implementation of std::ops::Drop
of its owning value, since returning such a reference could cause a use-after-free.
It is not guaranteed to panic.
Additionally, if this Gc is moved out of an allocation during a Drop implementation, it
could later cause a panic.
For further details, refer to the main documentation for Gc.
§Examples
use dumpster::unsync::Gc;
let gc = Gc::new(6);
assert!(gc == Gc::new(6));