Custom pointer types and fat pointers, specialization required?

Imagine you want to implement your own pointer-like type. In as many respects as possible it should behave exactly like the built in raw pointer types. There are a bunch of possible different reasons to do this, but say for the sake of example you know some data will always be within the first 4GB of address space. That means you can get away with using a 32-bit usize to store your "pointer" instead of a real 64-bit pointer (assuming a platform where *const T is 64-bit). So you do something like this:

struct SmallPointer<T>(u32, PhantomData<*const T>);

However, this will only work for T: Sized. For DSTs you need something like:

struct SmallDstPointer<T>(u32, u32, PhantomData<*const T>);

But this isn't ergonomic because generic code that just wants to think about SmallPointer<T> needs to be conscious of what kind of T is being dealt with and have redundant paths to deal with the DST case. The built in raw pointers are in effect specialized for DSTs.

Until specialization lands (the tracking issue is 6 years old so I am assuming it's not going to be a short wait) I'm not sure what the best work around is? Maybe with const generics I can do something like:

struct SmallPointer<T>([u32; size_of::<*const T>() / size_of::<*const u8>()], PhantomData<*const T>);

Then I could do runtime checks on the array size being 1 or 2 that should be optimized out by the compiler. But that also seems to require incomplete features (const_generics, const_evaluatable_checked).

It also occurs to me maybe I could be taking the pointer type I am trying to emulate as the generic argument instead of the type that is pointed to:

struct SmallPointer<T, P: RawPointerTo<T>>(u32, PhantomData<P>);

Assuming there is a RawPointerTo trait that is only implemented for raw pointers. But I don't know how to leverage this into determining how many u32s to store.

Is there a best way to do this in stable rust? Failing that what is the best way to do this in nightly rust that has the shortest track to stabilization? I tried googling around for rust and compressed pointers but didn't find any examples to work off of.

1 Like

On nightly, it's #![feature(ptr_metadata)]. I don't know how much more baking the feature needs, but there's still active effort pushing the feature right now, to unlock further experimentation with (custom) DSTs.

On stable, it'd be a manually implemented subset of the feature, for pointer-to-sized and slices only. Roughly,

trait Ptr: Copy {
    type Metadata;
    fn split(self) -> (*mut (), Metadata);
    fn unsplit(*mut (), Metadata) -> Self;
}

impl<T: Sized> Ptr for *const T { ... }
impl<T> Ptr for *const [T] { ... }
3 Likes

Ah, I think the key thing I was missing is that by explicitly impling on T: Sized I can make an impl that doesn't overlap with one on slices. So then Metadata can be either u32 or a ZST.

With this approach I still can't just deal with one struct SmallPointer<T>, I'll need to define a trait that covers it and SmallDstPointer<T>. But it should work :smiley: Thanks!

Everytime I think of something more obscure it's always already being worked on :grin: