Crate persian_rug
source ·Expand description
This crate provides a framework for managing arbitrary mutable graphs of objects that link to one another. This is a pattern which is difficult to replicate in Rust, because of the ownership model, but is common in the wider software ecosystem.
§Overview and motivation
In the case where you have truly arbitrary graphs, most of an object’s
dependencies cannot be usefully represented as being owned by that object.
For example, consider two types, Foo
and Bar
:
struct Foo {
bars: Vec<Bar>
}
struct Bar {
my_special_foo: Option<Foo>
}
If no one Foo
is the sole owner of a Bar
(each Foo
holds a
subset of available Bar
s), and no one Bar
is the sole owner of
any given Foo
(the same Foo
could be special for multiple
Bar
s), then we end up with multiple copies of every object in
this representation, and maintaining consistency is likely to be
difficult.
We might next try to use reference counting smart pointers for the
links between objects, but neither Rc
nor
Arc
gives us mutability on its own. If we then
consider using Mutex
to provide mutability, we
end up with something like this:
use std::sync::{Arc, Mutex};
struct Foo {
bars: Vec<Arc<Mutex<Bar>>>
}
struct Bar {
my_special_foo: Option<Arc<Mutex<Foo>>>
}
in which each object is individually lockable to permit mutation. But there is no meaningful lock order here, and deadlock is all but assured.
The approach taken in this crate is to store everything inside one
container (a Context
) which gives a single location for locking.
Only the Context
has ownership of data, everything else is
granted a Proxy
which can be resolved using the Context
into a
reference to the real object. We use attribute macros to
remove most of the boilerplate: contextual
matches a type to
its owner, and persian_rug
builds a suitable owner.
That means the example using this crate looks like this:
use persian_rug::{contextual, persian_rug, Proxy};
#[contextual(MyRug)]
struct Foo {
bars: Vec<Proxy<Bar>>
}
#[contextual(MyRug)]
struct Bar {
my_special_foo: Option<Proxy<Foo>>
}
#[persian_rug]
struct MyRug(#[table] Foo, #[table] Bar);
We will need to have an instance of MyRug
available whenever we
want to read the contents of a Foo
or a Bar
. If we have a
mutable reference to the context, we can change any Foo
or Bar
we wish.
§The Persian Rug
A Context
provides the ability to insert, retrieve and iterate
over items by type. It can only support one collection of items
per type.
Please note: this crate does not support deletion of objects at present.
use persian_rug::{contextual, persian_rug, Context, Proxy, Table};
#[contextual(C)]
struct Foo<C: Context> {
_marker: core::marker::PhantomData<C>,
pub a: i32,
pub friend: Option<Proxy<Foo<C>>>
}
impl<C: Context> Foo<C> {
pub fn new(a: i32, friend: Option<Proxy<Foo<C>>>) -> Self {
Self { _marker: Default::default(), a, friend }
}
}
#[persian_rug]
struct Rug(#[table] Foo<Rug>);
let mut r = Rug(Table::new());
let p1 = r.add( Foo::new(1, None) );
let p2 = r.add( Foo::new(2, Some(p1)) );
let p3 = r.add( Foo::new(3, Some(p2)) );
r.get_mut(&p1).friend = Some(p3);
Context read access is provided to implementations of Accessor
whose context matches. Shared references to the context are
accessors, as are Arc
s,
MutexGuard
s and
RwLockReadGuard
s.
Write access is provided to implementations of Mutator
whose
context matches. Exclusive references to the context are
mutators, as are MutexGuard
s and
RwLockWriteGuard
s. If you enable
the clone-replace
feature, you can also use
MutateGuard
s for this.
To prevent accidental misuse, each participating type must declare
its context by implementing Contextual
, and can only belong to
one context. This apparent restriction is easily lifted by making
the context a generic parameter of the participating type. The
constraints
attribute can help with the boilerplate needed to
use generic parameters in this way.
Structs§
- A handle to an item stored in some context.
- A dense set of
Proxy
objects - A holder for
Contextual
objects. - An
Iterator
over references toContextual
objects. - An
Iterator
over exclusive references toContextual
objects.
Traits§
- A convenient way to handle
Context
read access. - A holder for
Contextual
types. - Something that is associated to a context
- A convenient way to handle
Context
write access. - A type that owns (is the exclusive holder of) a
Contextual
type.
Attribute Macros§
- Add the type constraints necessary for an impl using persian-rug.
- Provide a implementation of
Contextual
for a type. - Convert an annotated struct into a
Context