Open a scope and then freeze it in time for future access.
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache%202%20-green)](#License)
[![Crates.io](https://img.shields.io/crates/v/nolife)](https://crates.io/crates/nolife)
[![Docs](https://docs.rs/nolife/badge.svg)](https://docs.rs/nolife)
[![dependency status](https://deps.rs/repo/github/dureuill/nolife/status.svg)](https://deps.rs/repo/github/dureuill/nolife)
[![Build](https://github.com/dureuill/nolife/actions/workflows/rust.yml/badge.svg)](https://github.com/dureuill/nolife/actions/workflows/rust.yml)
This crate allows constructing structs that contain references and keeping them alive alongside the data they reference,
without a lifetime.
This is especially useful for zero-copy parsers that construct elaborate (and possibly costly) representations that borrow
the source data.
This crate achieves that by leveraging `async` functions. At their core, `async` functions are self-referential structs. this crate simply provides a way to ex-filtrate references outside of the async function, in a controlled manner.
# Using this crate
After you identified the data and its borrowed representation that you'd like to access without a lifetime, using this crate will typically encompass a few steps:
```rust
// Given the following types:
struct MyData(Vec);
struct MyParsedData<'a>(&'a mut MyData, /* ... */);
// 1. Define a helper type that will express where the lifetimes of the borrowed representation live.
struct MyParsedDataFamily; // empty type, no lifetime.
impl<'a> nolife::Family<'a> for MyParsedDataFamily {
type Family = MyParsedData<'a>; // Indicates how the type is tied to the trait's lifetime.
// you generally want to replace all lifetimes in the struct with the one of the trait.
}
// 2. Define a function that setups the data and its borrowed representation:
fn my_scope(
data_source: Vec, // 👈 all parameters that allow to build a `MyData`
) -> impl nolife::TopScope // 👈 use the helper type we declared
{
nolife::scope!({
let mut data = MyData(data_source);
let mut parsed_data = MyParsedData(&mut data); // imagine that this step is costly...
freeze_forever!(&mut parsed_data) // gives access to the parsed data to the outside.
/* 👆 reference to the borrowed data */
})
}
// 3. Open a `BoxScope` using the previously written async function:
let mut scope = nolife::BoxScope::::new_dyn(my_scope(vec![0, 1, 2]));
// 4. Store the `BoxScope` anywhere you want
struct ContainsScope {
scope: nolife::BoxScope,
/* other data */
}
// 5. Lastly, enter the scope to retrieve access to the referenced value.
scope.enter(|parsed_data| { /* do what you need with the parsed data */ });
```
# Kinds of scopes
This crate only provide a single kind of scope at the moment
|Scope|Allocations|Moveable after opening|Thread-safe|
|-----|-----------|----------------------|-----------|
|[`BoxScope`]|1 (size of the contained Future + 1 pointer to the reference type)|Yes|No|
An `RcScope` or `MutexScope` could be future extensions
# License
Licensed under either of [Apache License](./LICENSE-APACHE), Version 2.0 or [MIT license](./LICENSE-MIT) at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
# Alternative
[`yoke`] serves a similar use case as this crate, albeit it is expressed in terms of a self-referential struct rather than as an async scope, which is less natural if the intent is to borrow some data.
[`yoke`]: https://crates.io/crates/yoke