15 unstable releases (3 breaking)
0.4.3 | Sep 5, 2023 |
---|---|
0.4.2 | Jan 25, 2023 |
0.4.0 | Dec 12, 2022 |
0.3.0 | Dec 4, 2022 |
0.1.4 | Jun 23, 2022 |
#1264 in Rust patterns
81 downloads per month
Used in srce
30KB
177 lines
selfref
Please refer to the top-level documentation for this crate instead.
lib.rs
:
An experimental approach to self-referential structs in Rust.
This crate provides an alternative approach to self-referential structs, where instead of providing you with a macro or framework where you define a self-referential struct and it handles all of the details for you, we try to expose the abstractions and building blocks for making self-referential structs work well in safe Rust.
For example, a Holder
is a safe wrapper around a self-referential
struct, providing safe APIs for constructing and manipulating a
self-referential struct. However, and unlike other self-referential crates,
it does not dictate the backing storage of the struct. The Opaque
trait
is used to identify a self-referential struct for use with a Holder
-
since Rust does not support higher kinded types (HKTs), this crate uses
generic associated types (GATs) as a workaround.
To use the crate, first define a self-referential struct in plain Rust:
use std::cell::Cell;
// Your self-referential struct.
struct MySelfRefStruct<'this> {
// Rust uses RAII-like struct construction, as a result this must be
// somehow initialized after the struct. We can use an Option in a Cell
// for this.
this: Cell<Option<&'this MySelfRefStruct<'this>>>,
}
Then, define a type to implement the Opaque
. This can be done
automatically with the opaque
macro:
use selfref::opaque;
// A "marker type" that implements `Opaque`.
// This follows the "type family" GAT pattern.
struct MySelfRefStructKey;
opaque! {
impl Opaque for MySelfRefStructKey {
type Kind<'this> = MySelfRefStruct<'this>;
}
}
// Alternatively, it is possible to implement `Opaque` on, for example,
// `MySelfRefStruct<'static>`, but the added lifetime adds verbosity which
// may be considered unnecessary/undesired.
Now you can construct a Holder
and pick a storage for it. For example,
in a Box
:
use selfref::Holder;
fn main() {
// first, construct the struct
let holder = Box::pin(Holder::<'_, MySelfRefStructKey>::new_with(
|foo| foo.build({
MySelfRefStruct {
this: Cell::new(None)
}
})
));
// then, build the self-reference
holder.as_ref().operate_in(
|this| {
this.this.set(Some(this.get_ref()));
}
);
}
Examples
This is a more complex example borrowing from an external lifetime:
use core::cell::Cell;
use core::marker::PhantomData;
use core::pin::Pin;
use selfref::Holder;
use selfref::opaque;
struct Foo<'a, 'b: 'a> {
foo: Cell<Option<&'a Foo<'a, 'b>>>,
t: &'b str,
}
struct FooKey<'b>(PhantomData<&'b str>);
opaque! {
impl['b] Opaque for FooKey<'b> {
type Kind<'a> = Foo<'a, 'b>;
}
}
fn main() {
// a non-'static &str
let stack_array: [u8; 5] = *b"hello";
let stack_str = core::str::from_utf8(&stack_array).unwrap();
// construct the struct
let holder = Box::pin(Holder::<'_, FooKey<'_>>::new_with(|foo| {
foo.build(Foo {
foo: Default::default(),
t: stack_str,
})
}));
holder.as_ref().operate_in(|foo| {
foo.foo.set(Some(foo.get_ref()));
});
}
Features
Due to PhantomData is unsound
we currently require the following features for T: ?Sized
support in
selfref::opaque!
:
alloc
-selfref::opaque!
forT: ?Sized
is provided byBox
.nightly
-selfref::opaque!
forT: ?Sized
is provided by a wrapper aroundPhantomData
, which works around the above issue. we call this "PhantomDrop".
When enabling both features, nightly
takes over and we use the wrapper
always. This doesn't make a significant difference since the generated UB
check is dead code anyway, but PhantomDrop
doesn't depend on alloc
and
can be used in no_std
environments.
If not using either feature, T: ?Sized
support requires unsafe
ly
implementing Opaque
.
Note that we do not enable any features by default! We assume most
folks aren't coming to this crate for its T: ?Sized
support, so these are
the best defaults for crates to depend on. If they do need the ?Sized
support they can just enable one of these (probably alloc
).
Dependencies
~48KB