Hi!
I’m building an API that is abstracting over the shared pointer family, including the usual shared reference &T
. This means that values may or may not be stored on the heap.
I got something working quite easily using GATs:
trait SharedPtrFamily {
type SharedPtr<'a, T: 'a + ?Sized>: Deref<Target = T> + Unpin + 'a;
fn share<'a, T>(ptr: &Self::SharedPtr<'a, T>) -> Self::SharedPtr<'a, T>
where
T: ?Sized + 'a;
}
struct RcFamily;
impl SharedPtrFamily for RcFamily {
type SharedPtr<'a, T: 'a + ?Sized> = Rc<T>;
fn share<'a, T>(ptr: &Self::SharedPtr<'a, T>) -> Self::SharedPtr<'a, T>
where
T: ?Sized + 'a,
{
Rc::clone(ptr)
}
}
struct RefFamily;
impl SharedPtrFamily for RefFamily {
type SharedPtr<'a, T: 'a + ?Sized> = &'a T;
fn share<'a, T>(ptr: &Self::SharedPtr<'a, T>) -> Self::SharedPtr<'a, T>
where
T: ?Sized + 'a,
{
ptr
}
}
And I’m able to write a generic API on top of this:
struct Holder<'a, T, F>
where
F: SharedPtrFamily,
T: 'a,
{
slot: F::SharedPtr<'a, Option<T>>,
}
struct OtherHolder<'a, T, F>
where
F: SharedPtrFamily,
T: 'a,
{
slot: F::SharedPtr<'a, Option<T>>,
}
// Code generic over the pointer family
fn do_something<'a, F>(holder: &Holder<'a, &'a u8, F>) -> OtherHolder<'a, &'a u8, F>
where
F: SharedPtrFamily,
{
OtherHolder {
slot: F::share(&holder.slot),
}
}
// Helper to create a Holder backed by a Rc pointer; wrap the value into `Option` and move to the heap
fn create_rc_holder<'a>(value: &'a u8) -> Holder<'_, &'a u8, RcFamily> {
Holder {
slot: Rc::new(Some(value)),
}
}
// Helper to create a Holder backed by a reference; just stealing the slot and nothing else
fn create_ref_holder<'a>(slot: &'a Option<&'a u8>) -> Holder<'a, &'a u8, RefFamily> {
Holder { slot }
}
fn main() {
let data = 240;
// With allocation
let rc_holder = create_rc_holder(&data);
let rc_other_holder = do_something(&rc_holder);
assert_eq!(
Rc::as_ptr(&rc_holder.slot),
Rc::as_ptr(&rc_other_holder.slot),
);
// No-alloc code, heapless
let slot = Some(&data); // Put the slot on the stack
let ref_holder = create_ref_holder(&slot);
let ref_other_holder = do_something(&ref_holder); // Using the same generic function as above
assert_eq!(ref_holder.slot as *const _, &slot as *const _);
assert_eq!(ref_other_holder.slot as *const _, &slot as *const _);
}
All is good, it works. Of course the example here is a bit contrived, but the actual program I’m writing does benefit from this approach.
However, I’m wondering if I did it "the right way", or the "the most ergonomic way".
For instance, if I write the same code, but specialized for Rc
, I get something like this:
struct RcHolder<T> {
slot: Rc<Option<T>>,
}
struct RcOtherHolder<T> {
slot: Rc<Option<T>>,
}
fn harcoded_do_something<'a>(holder: &RcHolder<&'a u8>) -> RcOtherHolder<&'a u8> {
RcOtherHolder {
slot: Rc::clone(&holder.slot),
}
}
// This is exactly the same as `create_rc_holder` as seen above, but the signature is simpler
// I would like to keep "specialized" code as simple, while returning something compatible with the generic API
fn harcoded_create_holder<'a>(value: &'a u8) -> RcHolder<&'a u8> {
RcHolder {
slot: Rc::new(Some(value)),
}
}
fn main() {
let data = 240;
let harcoded_holder = harcoded_create_holder(&data);
let harcoded_other_holder = harcoded_do_something(&harcoded_holder);
assert_eq!(
Rc::as_ptr(&harcoded_holder.slot),
Rc::as_ptr(&harcoded_other_holder.slot),
);
}
The question is: is there something I could do to obtain something equivalent to a type alias such that there is no anonymous lifetime to specify when using a heap-based flavor?
Is there something smart to do for achieving something similar to that:
// This is of course not valid Rust, but that’s what I would like to achieve somehow
type RcHolder<T> = Holder<'_, T, RcFamily>;
This type could still be used and consumed by the generic API, but would be less verbose when the family is hardcoded (e.g. create_rc_holder
).
My take is that it’s (currently) not possible to do much better that this:
type RcHolder<'a, T> = Holder<'a, T, RcFamily>;
But I really hope someone can prove me wrong!
Here is the playground link containing all the code: