#ownership #self-referential #data-structures #no-std #memory

no-std pair

Safe API for generic self-referential pairs of owner and dependent

2 unstable releases

Uses new Rust 2024

0.2.0 Mar 15, 2025
0.1.0 Feb 8, 2025

#460 in Rust patterns

Download history 122/week @ 2025-02-05 15/week @ 2025-02-12 3/week @ 2025-02-19 119/week @ 2025-03-12 10/week @ 2025-03-19

129 downloads per month

MIT/Apache

45KB
218 lines

Pair

GitHub crates.io docs.rs License

Safe API for generic self-referential pairs of owner and dependent.

You define how to construct a dependent type from a reference to an owning type, and Pair will carefully bundle them together in a safe and freely movable self-referential struct.

Example Usage

A typical use case might look something like this:

use pair::{Dependent, HasDependent, Owner, Pair};

// Let's say you have some buffer type that contains a string
#[derive(Debug)]
pub struct MyBuffer {
    data: String,
}

// And you have some borrowing "parsed" representation, containing string slices
#[derive(Debug)]
pub struct Parsed<'a> {
    tokens: Vec<&'a str>,
}

// And you have some expensive parsing function you only want to run once
fn parse(buffer: &MyBuffer) -> Parsed<'_> {
    let tokens = buffer.data.split_whitespace().collect();
    Parsed { tokens }
}



// You would then implement HasDependent and Owner for MyBuffer:

// Defines the owner/dependent relationship between MyBuffer and Parsed<'_>
impl<'owner> HasDependent<'owner> for MyBuffer {
    type Dependent = Parsed<'owner>;
}

// Define how to make a Parsed<'_> from a &MyBuffer
impl Owner for MyBuffer {
    type Context<'a> = (); // We don't need any extra args to `make_dependent`
    type Error = std::convert::Infallible; // Our example parsing can't fail

    fn make_dependent(&self, _: ()) -> Result<Dependent<'_, Self>, Self::Error> {
        Ok(parse(self))
    }
}



// You can now use MyBuffer in a Pair:
fn main() {
    // A Pair can be constructed from an owner value (MyBuffer, in this example)
    let mut pair = Pair::new(MyBuffer {
        data: String::from("this is an example"),
    });

    // You can obtain a reference to the owner via a reference to the pair
    let owner: &MyBuffer = pair.owner();
    assert_eq!(owner.data, "this is an example");

    // You can access a reference to the dependent via a reference to the pair,
    // but only within a provided closure.
    // See the documentation of `Pair::with_dependent` for details.
    let kebab = pair.with_dependent(|parsed: &Parsed<'_>| parsed.tokens.join("-"));
    assert_eq!(kebab, "this-is-an-example");

    // However, if the dependent is covariant over its lifetime (as our example
    // Parsed<'_> is) you can trivially extract the dependent from the closure.
    // This will not compile if the dependent is not covariant.
    let parsed: &Parsed<'_> = pair.with_dependent(|parsed| parsed);
    assert_eq!(parsed.tokens, ["this", "is", "an", "example"]);

    // You can obtain a mutable reference to the dependent via a mutable
    // reference to the pair, but only within a provided closure.
    // See the documentation of `Pair::with_dependent_mut` for details.
    pair.with_dependent_mut(|parsed| parsed.tokens.pop());
    assert_eq!(pair.with_dependent(|parsed| parsed.tokens.len()), 3);

    // If you're done with the dependent, you can recover the owner.
    // This will drop the dependent.
    let my_buffer: MyBuffer = pair.into_owner();
}

How it Works

Note: the implementation details described in this section are not part of the crate's public API, and are subject to change.

Under the hood, Pair moves the owner onto the heap, giving it a stable memory address. It is then borrowed and used to construct the dependent, which is also moved onto the heap. The dependent is type-erased, so that its inexpressible self-referential lifetime goes away. All exposed APIs are careful to ensure type and aliasing rules are upheld, regardless of anything safe user code could do. When the owner needs to be dropped or recovered, the dependent will first be recovered and dropped, ending the borrow of the owner. At that point, the owner can safely be recovered and the Pair deconstructed.

Related Projects

Crate Macro free No alloc Maintained Soundness
pair No known issues
ouroboros ⚠️ Unsound ⚠️
self_cell No known issues
yoke ⚠️ Unsound ⚠️
selfie ⚠️ Unsound ⚠️
nolife No known issues
owning_ref ⚠️ Unsound ⚠️
rental ⚠️ Unsound ⚠️
fortify No known issues
loaned No known issues
selfref No known issues
self-reference ⚠️ Unsound ⚠️
zc No known issues

License

Licensed under either of:

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

No runtime deps