How to store a pointer of any type?

I want to store a pointer of any type, but I don't know what ? can be replaced with. Neither *const () nor *const [()] is allowed here due to the existence of trait object. And I don't know how to judge whether a pointer is fat or thin, so I dare not cast *const () or *const [()] to *const T forcedly. Thus, I created this topic.
Creating a struct or enum is acceptable. Thanks.

#![allow(unused)]
use std::any::{Any, TypeId};

struct AnyPointer {
    ptr: *const ?, // What type can `?` be replaced with
    tid: TypeId,
}

impl AnyPointer {
    fn from_ptr<T: ?Sized>(ptr: *const T) -> AnyPointer {
        AnyPointer {
            ptr: ptr as _,
            tid: TypeId::of::<T>(),
        }
    }

    fn try_cast<T: ?Sized>(&self) -> Option<*const T> {
        if TypeId::of::<T>() != self.tid {
            return None;
        }

        Some(self.ptr as _)
    }
}

You might be looking for the nightly pointer::to_raw_parts() method which lets you decompose some *const T pointer into its data (a *const ()) and its metadata (e.g. a pointer to a trait object's vtable). This would make your AnyPointer type a bit more complex though, because you'll need to figure out how to store the metadata without making AnyPointer generic over the T type.

However, is there any reason why we need to allow trait objects in the first place? If you drop the ?Sized requirement then a *const T will always be a plain pointer, which is perfectly fine to cast to *const ().

A while back, I wrote an article where we try to do something similar to your AnyPointer - erase a value's type at runtime and try to use its methods or downcast it back to the original T. It doesn't directly answer your question, but maybe it'll give you some inspiration or tricks you can borrow.

1 Like

Sorry, my library is planning to available in stable rust, so to_raw_parts cannot be adopted, and pointer of trait object needed to be supported, so T cannot be required to be sized. I'm not an English speaker, reading a long article written in English is a bit difficult for me now. :sweat_smile: I'll try to read it later.

I found the length of fat pointers is always the same, and so are thin pointers. So I think I can do this:

use std::mem::size_of;

const FAT_LEN: usize = size_of::<*const [()]>();
const THIN_LEN: usize = size_of::<*const ()>();

enum PointerEnum{
    Fat([u8;FAT_LEN]),
    Thin([u8;THIN_LEN])
}

Then, judge the length of *const T, then forcedly write it to the byte array. This method sounds nice, but I don't know whether it is UB?

There's no guarantees on the layout of wide pointers, or that the layout is the same between different types. Rust may also some day allow wide pointers of arbitrary size / custom DST metadata.

What is UB or not

  • Is going to depend on what you do with the values
  • Is not completely settled yet as far as I know

Well, sounds like the size or layout of the pointer may change with the type. I think can only use Box to wrap it for now.