A type-safe, heterogeneous collection with zero-cost add and borrow.
https://crates.io/crates/borrow-bag
Initially created to solve a problem in another project I'm collaborating on, but generic enough to be released on its own. It's my first adventure in type-level programming, and Rust made it very pleasant. It solves the problem of having a heterogeneous collection of owned values, with an easy / type-safe way to borrow them later but still retain the ability to move them in the interim. Structurally similar to frunk's HList, but with a tiny API:
use borrow_bag::new_borrow_bag;
struct X(u8);
struct Y(u8);
fn main() {
let bag = new_borrow_bag();
let (bag, x_handle) = bag.add(X(1));
let (bag, y_handle) = bag.add(Y(2));
let x: &X = bag.borrow(x_handle);
assert_eq!(x.0, 1);
let y: &Y = bag.borrow(y_handle);
assert_eq!(y.0, 2);
}
Feedback / review welcome.
2 Likes
Is the let x: &X
required or can it be just let x
?
Type inference works fine with this API, so the following example works exactly as you'd expect:
use borrow_bag::new_borrow_bag;
struct X(u8);
struct Y(u8);
fn main() {
let bag = new_borrow_bag();
let (bag, x_handle) = bag.add(X(1));
let (bag, y_handle) = bag.add(Y(2));
let x = bag.borrow(x_handle);
assert_eq!(x.0, 1);
let y = bag.borrow(y_handle);
assert_eq!(y.0, 2);
}
I only annotated the types in the original example for clarity.
Could you please explain me what's a possible use case for this?
That's a really good question. It solves my particular problem, so I can only really speak about what my problem was.
I won't speak in specifics, since we're not ready to announce our project yet, but I've come up with the following contrived example which has a request/result scenario with support for an "around" hook:
Apologies for the complexity of this example. In spite of my efforts to simplify it, this structure of traits is necessary to demonstrate the problem I ran into, particularly in that the AroundHook
can't be made into a trait object because call
takes a closure.
In this example where we have only one Dispatcher
, the ownership is straightforward because we can store the hook where it's going to be used. There are a few complicating factors as this grows in scope:
- The
Registry
will have multiple Dispatcher
values, and will need to determine which to use based on some field(s) of the Input
- We want to define the hooks only once, and potentially use them in many places, so our options are either to clone them or borrow them
- If we opt to clone hooks, duplicating
AroundHook
values for every dispatcher requires a lot of clone()
calls as the Registry
grows, but perhaps worse imposes a need for the AroundHook
to be Clone
. Not every type can be Clone
, so that's too limiting
- If we opt to store a reference instead, we run into the limitation that
AroundHook
can't be made into a trait object, because of the generic function which takes a closure, so we can only store a reference to the concrete type.
I couldn't get the ownership to work correctly when using references. One example:
BorrowBag
solves this by allowing a BorrowBag<V>
owning all the hooks to be stored in Registry
, and passed through the dispatch
call. The DispatcherImpl
struct can store as many Handle<T, _>
values as it needs to describe all the hooks it will use, and then during dispatch
can use those handles to borrow the hook values and dispatch.