When one needs to "forget" a type of the pointer it's can be safely cast to some other pointer. Usually *const () or *const u8 is used. Or NonNull if null pointer are out of question and niche is nice to have.
Later pointer can be cast back to original type and then safely dereferenced.
Typical use case for this is to perform downcasts at runtime when TypeId is available externally so no dyn Any is required.
This still works for pointers to slices and DST structs with slice field. Except that now the pointer have to be cast to arbitrary slice pointer like *const [()].
And now this doesn't work for dyn Trait and generic T: ?Sized. Cast between *const dyn Trait and *const dyn OtherTrait is not allowed. Same as cast between *const dyn Trait and *const [Type].
The reason behind this is (as I understand it) that the layout of metadata can be different.
So I guess that transmuting those pointers may be UB.
Now I have a problem. I try to make a function signature for function that takes thin pointer to value and TypeId and returns pointer to type with that TypeId or None. The TypeId can be of inner type or of dyn trait.
What are my options?
This is a question about using Rust, not about evolving the language, so is a better fit for the users forum.
To go from (*mut (), TypeId) to *mut impl ?Sized, the best option is to actually just hold onto *mut dyn Any; it's the same size (and perhaps smaller, once TypeId is strengthened against collisions), and will automatically avoid soundness issues.
If splitting into two parts is necessary (e.g. to cross FFI), you want to look at the ptr_metadata APIs to split *mut dyn Any into (*mut (), DynMetadata<dyn Any>).
I do not believe you can soundly directly downcast from dyn Any to dyn Trait, no matter the language features. You need to know the concrete type in order to select the correct dyn Trait vtable, and the dyn Any vtable does not and cannot contain this information.
In my use case I don't carry TypeId.
At some point I have a NonNull<T> that points to an array of T and turn it to type-erased NonNull<u8> to store in heterogeneous data structure.
At later point I again have T, look for a correct NonNull<u8> and cast it. Then I can iterate over &T.
Now I want to add a layer of abstraction.
During setup a few functions with signature fn(&T) -> &U where U: ?Sized can be registered.
And wrapped to fn(NonNull<u8>) -> ??? to store them heterogeneously.
Now receiver would only know about U: ?Sized, has pairs of NonNull<u8> and fn(NonNull<u8>) -> ??? and knowledge that that pointer can be used with that function and that function output was NonNull<U> cast into ???.
The problem is, what ??? type should be?
I tried transmuting NonNull<U> into NonNull<[u8]> and back and it worked, but I guess it's UB if U is dyn trait.
Also works with any dummy dyn trait object type. And probably UB again if U is slice.
Currently I rewrote the thing to cast function pointer instead of its output. i.e. I transmute fn(NonNull<u8>) -> NonNull<U> to fn(NonNull<u8>) to store it and then back to fn(NonNull<u8>) -> NonNull<U> to call it.
I wonder if any of the methods I tried could theoretically be not only safe and sound, but defined such.
This is an important detail. In this case you necessarily have T: Sized (to construct a [T]), and can cast between *mut () and *mut T freely.
It doesn't. I don't know what you transmuted; probably fn(…) -> …, which does work. Transmuting between function pointer types is AIUI guaranteed to be sound.
What you probably want is an opaque function pointer type, though. std doesn't have one, but it's possible to polyfill, kinda.