Crates.io | tracing-rc |
lib.rs | tracing-rc |
version | 0.1.3 |
source | src |
created_at | 2021-12-12 23:03:38.31667 |
updated_at | 2021-12-28 22:43:34.694132 |
description | Cycle-aware reference-counted pointers with a safe, simple api |
homepage | |
repository | https://github.com/Jesterhearts/tracing-rc |
max_upload_size | |
id | 496745 |
size | 111,086 |
Cycle collecting reference counted pointers for Rust with a safe, simple api.
The Gc
type implemented by this crate provides a cycle-aware smart pointer in the style of
std::rc::Rc
, the major difference being that you cannot have weak references to a Gc
. This is
useful if you have some number of interlinked data structures - e.g. GraphNode
, LuaTable
, etc. -
which don't have clear lifetimes or ownership.
Gc
is probably the wrong choice for many usecases:
std::rc::Rc
and Weak
.The library has two usages of unsafe where it drops the inner data of the Gc
and Agc
types. In
the case of Gc
, this unsafe is protected by a borrow_mut
call to a RefCell
which is
intentionally leaked after dropping the data, making the inner (dropped) data inaccessible. Agc
similarly leaks a WriteMutexGuard
to its inner data post-drop.
This library has experimental support for concurrent collection behind the sync
flag. Enabling the
sync
flag will provide access to an atomic Agc
type as well as concurrent collect
implementations.
Because any implementation of the Trace
trait and custom Drop
implementations for objects
owned by a garbage collected pointer can run arbitrary code, they may attempt to create new copies or
drop existing traced objects (even already traced ones) in the middle of collection. In addition,
due to bugs in client code, Trace
may report more items as children than it actually owns
(reporting fewer is trivially safe, as it will simply leak).
In order for this crate to be sound and present a safe Trace
trait, the collector must not
cause undefined behavior in any of the scenarios outlined. In order to accomplish this, the
collector does the following:
Trace
implementation may cause the collector to believe a dead value is still alive
(causing a leak) or a live value is dead (making it inaccessible, but leaving the gc pointer
valid). Safe code is unable to access dead values (it will panic or return Option::None), and
cannot restore the live state of a dead object. Values are never temporarily dead, the collector
only marks them dead after it has fully determined that it is a valid candidate for collection
(all strong refs are from members of its cycle).Gc
value, and its memory will be cleaned up. If there are remaining
references, its memory will be cleaned up when the oustanding references fall out of scope.There are a decent number number of tests designed to exercise each of these scenarios included, and all of these tests pass miri (barring leaks for intentionally misbehaved code).
use tracing_rc::{
rc::{
collect_full,
Gc,
},
Trace,
};
#[derive(Trace)]
struct GraphNode<T: 'static> {
#[trace(ignore)]
data: T,
edge: Option<Gc<GraphNode<T>>>,
}
fn main() {
{
let node_a = Gc::new(GraphNode {
data: 10,
edge: None,
});
let node_b = Gc::new(GraphNode {
data: 11,
edge: None,
});
let node_c = Gc::new(GraphNode {
data: 12,
edge: Some(node_a.clone()),
});
node_a.borrow_mut().edge = Some(node_b.clone());
node_b.borrow_mut().edge = Some(node_c);
let a = node_a.borrow();
let b = a.edge.as_ref().unwrap().borrow();
let c = b.edge.as_ref().unwrap().borrow();
assert_eq!(a.data, c.edge.as_ref().unwrap().borrow().data);
// all of the nodes go out of scope at this point and would normally be leaked.
}
// In this simple example, we always have cycles and our program is complete after this,
// so we can't take advantage of the young generation picking up acyclic pointers without
// tracing.
collect_full();
// All leaked nodes have been cleaned up!
}
There are a number of excellent implementations of garbage collection in Rust, some of which inspired this crate.
Trace
trait which
is used for cycle tracing. It was very helpful in understanding how the original paper works,
although this crate takes a very different approach.