I'm currently working on a custom DSTs. In my opinion, std lacks some APIs for working with fat pointers which makes coding DSTs hard.
e.g. the one way to create *const Dst from the data part (*const ()) and the meta (vtable/length) part (usize) is to use core::slice::from_raw_parts and then cast it's result (the idea is stolen from dtolnay):
let slice = unsafe { core::slice::from_raw_parts(data, meta) };
slice as *const [()] as *const Dst
This works, though
using slice seems like a hack
while the layout of *const [T] seems to be defined, the layout of *const Dst doesn't seem to be defined, so the code doesn't seem right
it doesn't allow to construct ptr to DST which contain dyn Trait
Another thing that I personally miss is extracting meta from a fat pointer.
More precisely I'm looking for similar APIs:
impl<T: ?Sized> *const T {
// I'm bad at naming but hopefully you've got the idea :)
/// ignores meta field for `T: Sized`
pub fn fat_from_parts(data: *const (), meta: usize) -> Self;
/// returns None for `T: Sized`
pub fn meta(self) -> Option<usize>;
}
This could make my code a lot clearer
Though, design like this has some downsides:
this more or less disallows fat pointers with meta that is not an usize (or pointer)
this approach is much simpler/more dump/less typed than RFC for custom DSTs that I could find:
There is also the (still unstable / nightly-only) option of transmuting to/from TraitObject which probably doesn't solve all your problems, but it is a way to create pointers to dyn types and you didn't mention it.
The layout of *const [T] is unspecified. The layout of *const Tfor T: Sized is what is guaranteed by the language.
However, given Dst that is unsized due to having a slice tail, *const [T] and *const Dst are defined to, while not necessarily be the same layout, to be compatible and cast correctly via as.
You can get the length of an arbitrary pointer-to-slice-tailed-dst by casting to *const [()] and asking for the length of that. So long as the pointer is nonnull, it's sound to call <[()]>::len on it, and iirc there's a pointer version of slice::len on nightly.
At this point I need to mention my crate, slice-dst, which is designed to make working with slice-tailed DSTs a bit easier.
Working with arbitrary DSTs is difficult, because the type of the metadata on fat pointers may not match up. The size of a slice is usize, the vtable for dyn trait objects is &'static TraitVtable, there's certainly interest in pointers-to-dsts with no metadata ("fixing CStr"), and vague interest in user-defined fat pointers with arbitrary metadata (e.g. &MatrixSlice -- width, height, and stride (width of the full thing for row-major)).
In particular I think it would be good to keep the possibility for other kinds of non-Sized types than ones with two pointers (e.g. metadata shouldn't always be assumed to be either None or Some(usize). IMO it's better to just avoid defining this at all if possible, so it can be addressed later).
IMO the functionality you're describing should be done for concrete cases: e.g:
We need raw parts APIs for constructing/deconstructing *const [T]/*mut [T].
Trait objects need an unsafe API for constructing/deconstructing them (to/from *const dyn Foo/*mut dyn Foo).
Structs which have a unsized trailing member should guarantee that any metadata they have is equivalent to the metadata the trailing member would have on its own:
In particular, code exists in the wild which assumes that given struct MySlice(Stuff, [T]), &*(slice::from_raw_parts(pointer_to_myslice_instance, length_of_trailing_slice) as *const MySlice) is legal. In practice this works, but I believe it's not guaranteed (although it seems hard to imagine it wouldn't be implemetned some other way since other ways would mean as on pointers would need to make updates to len, I think).
It would be good if this were guaranteed, since then the APIs in 1 and 2 + casting would be enough for these cases.
I'd really prefer we didn't cut off the ability to do something better here in the future, there are a lot of really great use cases for custom DSTs, and so we should try not to block that path off with whatever we do here.
I think this is actually guaranteed. If you try to as cast between cases where the metadata isn't guaranteed to be the same, you get a "pointer metadata may not be the same" error.