pub trait Trace {
// Required method
fn visit_children(&self, visitor: &mut GcVisitor<'_>);
}
Expand description
Must be implemented for any value which will be stored inside of a Gc
.
While this is implemented for many of Rust’s basic types, it’s not
recommended that you store them in a Gc
unless they contain possibly cyclic
references as there is still a real cost to doing so. You’re probably better off using
std::rc::Rc
in cases where you know a type can’t participate in cycles.
Visit the gc pointers owned by this type.
Required Methods§
Sourcefn visit_children(&self, visitor: &mut GcVisitor<'_>)
fn visit_children(&self, visitor: &mut GcVisitor<'_>)
It is recommended that you simply call visit_children(visitor)
on each value owned by the
implementor which may participate in a reference cycle. The default implementation for
Gc
will appropriately notify the collector when it is visited.
Improper implementation of this trait will not cause undefined behavior; however, if you fail to report a value you may leak memory and if you report a value you don’t own (or report a value more than once), you may cause the collector to clean it up prematurely. Attemting to access a value which has been cleaned up will cause a panic, but will not cause undefined behavior.
§Example
struct GraphNode<T: 'static> {
neighbors: Vec<Gc<GraphNode<T>>>,
data: T,
}
impl<T: 'static> Trace for GraphNode<T> {
fn visit_children(&self, visitor: &mut GcVisitor) {
self.neighbors.visit_children(visitor);
}
}
empty_trace!(NodeId);
struct Graph<T: 'static> {
nodes: HashMap<NodeId, Gc<GraphNode<T>>>,
}
impl<T: 'static> Trace for Graph<T> {
fn visit_children(&self, visitor: &mut GcVisitor) {
self.nodes.visit_children(visitor);
}
}
// Usage:
{
{
let graph: Graph<usize> = build_graph();
operate_on_graph(&graph);
}
// None of the graph nodes will remain allocated after this call.
collect_full();
}
§Examples of improper implementations
- You should not report Gcs owned by the inner contents of Gcs.
struct MyStruct {
ptr: Gc<MyStruct>,
other_ptr: Gc<MyStruct>,
}
impl Trace for MyStruct {
fn visit_children(&self, visitor: &mut GcVisitor) {
// This is normal and ok.
self.ptr.visit_children(visitor);
// This is also acceptable
visitor.visit_node(&self.other_ptr);
// This will not cause undefined behavior, but it is wrong and may cause panics if
// it causes the collector to believe the node is dead, and the program later
// attempts to access the now dead value.
self.ptr.borrow().ptr.visit_children(visitor);
}
}
- You should not report a unique Gc instance twice.
struct MyStruct {
ptr: Gc<usize>,
}
impl Trace for MyStruct {
fn visit_children(&self, visitor: &mut GcVisitor) {
// This is normal and ok.
visitor.visit_node(&self.ptr);
// This is wrong and may cause panics.
visitor.visit_node(&self.ptr);
}
}
- You should not report Gcs that are not owned by your object.
- It is acceptable skip reporting, although doing so will result in memory leaks.
thread_local! { static GLOBAL_PTR: Gc<usize> = Gc::new(10)}
struct MyStruct {
ptr: Gc<MyStruct>,
leaks: Gc<usize>,
}
impl Trace for MyStruct {
fn visit_children(&self, visitor: &mut GcVisitor) {
// This is normal and ok.
visitor.visit_node(&self.ptr);
// Leaving this line commented out will leak, which is safe.
// Uncommenting it is safe and will allow leaks to be cleaned up.
// visitor(self.leaks.node());
// This is wrong and will cause GLOBAL_PTR to be cleaned up. If anything tries to
// access GLOBAL_PTR without checking if it is still alive, a panic will occur.
GLOBAL_PTR.with(|ptr| visitor.visit_node(ptr));
}
}