Crate selfref

source ·
Expand description

Pain-free self-referential/recursively-referential pinned types.

Because this crate optimizes for recursive references, it’s much nicer to use for porting/copying C code than alternatives. However, the cost is that it requires generic associated types (GATs). Further, this is the only crate for self-referential types which supports additional lifetimes than the self-referential one (with the small ergonomic caveat that closures don’t support where bounds for now, so a proper trait has to be used).

When using this crate, all you have to do is define your struct like you would in C, and then tell selfref about it. However, do watch out; the following will not be usable, despite compiling:

use selfref::opaque;

// This is the actual self-referential struct.
struct MySelfRefStruct<'this> {
    this: &'this MySelfRefStruct<'this>,
}

// This is a "marker" type, used to tell `selfref` about the type.
// This is actually the "type family" GAT pattern.
struct MySelfRefStructKey;

// This tells `selfref` about our type.
opaque! {
    impl Opaque for MySelfRefStructKey {
        type Kind<'this> = MySelfRefStruct<'this>;
    }
}

That’s because there’s no way to create the reference &'this MySelfRefStruct<'this> before constructing the struct! That is, you cannot do this:

struct MySelfRefStruct<'this> {
    this: &'this MySelfRefStruct<'this>,
}

fn main() {
    let x = MySelfRefStruct { this: &x };
}

But then, you cannot do this in C either. Instead, to make it usable, we use an Option and, to be able to set it, a Cell, like so:

use std::cell::Cell;

use selfref::opaque;

struct MySelfRefStruct<'this> {
    this: Cell<Option<&'this MySelfRefStruct<'this>>>,
}

struct MySelfRefStructKey;

opaque! {
    impl Opaque for MySelfRefStructKey {
        type Kind<'this> = MySelfRefStruct<'this>;
    }
}

and then we can use it:

// lines from the above example have been omitted

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:

#![feature(pin_macro)]

use core::cell::Cell;
use core::marker::PhantomData;
use core::pin::Pin;
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 = 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! for T: ?Sized is provided by Box.
  • nightly - selfref::opaque! for T: ?Sized is provided by a wrapper around PhantomData, 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 practical difference since the generated UB check is dead code anyway, but PhantomDrop should be lighter on compile times.

If not using either feature, T: ?Sized support requires unsafely 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).

Macros

Creates an opaqueified self-referential struct “key”.

Structs

Helper for creating a Holder.
Holds an “opaqueified” T::Kind.
Wrapper around a Pin<&'k T::Kind<'k>> for implied bounds.

Traits

An opaqueified self-referential struct “key”.