Crates.io | internode |
lib.rs | internode |
version | 1.0.0 |
source | src |
created_at | 2023-07-15 04:30:00.481949 |
updated_at | 2023-07-15 04:30:00.481949 |
description | Smart references to your graph nodes. |
homepage | |
repository | https://github.com/yuhr/internode |
max_upload_size | |
id | 916967 |
size | 42,356 |
internode
provides a hassle-free way to manage ownership of graph structures.
genawaiter
until generators stabilize)At first, define the shape of your nodes. In this example, we'll use a simple implementation that holds a pair of lists that represents in- and out-neighbors. The important point here is, inter-node references should be held through Internode
:
use internode::*;
#[derive(Default)]
struct Entity {
succs: Vec<Internode<Entity>>,
preds: Vec<Internode<Entity>>,
}
impl Entity {
fn add_edge(from: &Internode<Entity>, to: &Internode<Entity>) {
// Accessing the content of node is done by `lock`.
from.lock().unwrap().succs.push(to.clone());
to.lock().unwrap().preds.push(from.clone());
}
}
impl Neighbors for Entity {
type Iter<'a> = std::iter::Cloned<std::slice::Iter<'a, Internode<Entity>>>;
fn outgoing(&self) -> Self::Iter<'_> { self.succs.iter().cloned() }
fn incoming(&self) -> Self::Iter<'_> { self.preds.iter().cloned() }
}
Then, create nodes by Node::new
:
let (a_weak, b) = {
// `Node` is owning reference, while `Internode` is non-owning.
let a = Node::new(Entity::default());
let b = Node::new(Entity::default());
// `Node` implements `Deref` to `Internode`.
Entity::add_edge(&*a, &*b);
// Downgrading `Node` yields `Internode`.
let a_weak = a.downgrade();
(a_weak, b)
// `a` is dropped here.
};
// Downgrading `Internode` yields `Option<Node>`.
assert!(a_weak.upgrade().is_some());
// Dropping the last owning reference to the graph drops all nodes.
drop(b);
assert!(a_weak.upgrade().is_none());
Cyclic references do work out of the box without memory leaks:
let (a_weak, b_weak, c_weak, c) = {
let a = Node::new(Entity::default());
let b = Node::new(Entity::default());
let c = Node::new(Entity::default());
Entity::add_edge(&*a, &*b);
Entity::add_edge(&*b, &*c);
Entity::add_edge(&*c, &*a);
let a_weak = a.downgrade();
let b_weak = b.downgrade();
let c_weak = c.downgrade();
(a_weak, b_weak, c_weak, c)
// `a` and `b` are dropped here.
};
assert!(a_weak.upgrade().is_some());
assert!(b_weak.upgrade().is_some());
assert!(c_weak.upgrade().is_some());
drop(c);
assert!(a_weak.upgrade().is_none());
assert!(b_weak.upgrade().is_none());
assert!(c_weak.upgrade().is_none());
So you don't need to scratch your head about managing cycles anymore.
As a bonus for implementing Neighbors
, methods to perform depth- and breadth-first searching are provided:
let a = Node::new(Entity::default());
let b = Node::new(Entity::default());
let c = Node::new(Entity::default());
let d = Node::new(Entity::default());
Entity::add_edge(&*a, &*b);
Entity::add_edge(&*a, &*c);
Entity::add_edge(&*b, &*d);
Entity::add_edge(&*c, &*d);
Entity::add_edge(&*d, &*a);
assert!(a.dfs_outgoing().eq([&*a, &*b, &*d, &*c].into_iter().cloned()));
assert!(a.dfs_incoming().eq([&*a, &*d, &*b, &*c].into_iter().cloned()));
assert!(a.bfs_outgoing().eq([&*a, &*b, &*c, &*d].into_iter().cloned()));
assert!(a.bfs_incoming().eq([&*a, &*d, &*b, &*c].into_iter().cloned()));
This crate is inspired by dendron
(especially the concept that “reference to any node preserves entire tree”), which is limited to tree structures to ensure good properties, has a bunch of useful methods to manipulate, and also has defensive programming features like freezing nodes against edits. Such advanced functionalities are out of scope of internode
and left to users, since requirements vary. For example, if you want your nodes to be frozen, then frozen
or more stringent implementation is nice to have.