Since none of the RFC (such as 2984) have been implemented yet, I created a simple set of traits to just get the meta data out and back: Rust Playground.
For experimentation it should be good enough, since it doesn't require any modification to the compiler.
I'll take the opportunity to explain the -- perhaps odd -- choice of working entirely in terms of pointers, and therefore implementing the conversion between *const T
and (meta, *const u8)
.
In Rust, &T
and &mut T
have specific guarantees with regard to (1) aliasing and (2) liveness of the pointee. By not materializing a reference, we circumvent the 2 issues: in the absence of reference, reference rules do not apply!
As a bonus we are allowing pointer manipulations to be safe, as usual, with the caveat that it may not be safe to dereference the pointer later, as usual.
The code, which will not win any beauty contest:
#![feature(raw)]
#![feature(specialization)]
#![allow(incomplete_features)]
pub mod meta {
use std::{marker::PhantomData, mem, raw::TraitObject};
//
// SizedMeta
//
pub struct SizedMeta<T>(PhantomData<fn(T) -> T>);
impl<T> Clone for SizedMeta<T> {
fn clone(&self) -> Self { *self }
}
impl<T> Copy for SizedMeta<T> {}
//
// UnsizedMeta
//
// Ideally we'd want a separate item for slices vs traits. Fortunately at the
// moment they both only require one usize worth of data...
//
pub struct UnsizedMeta<T: ?Sized>(usize, PhantomData<fn(T) -> T>);
impl<T: ?Sized> Clone for UnsizedMeta<T> {
fn clone(&self) -> Self { *self }
}
impl<T: ?Sized> Copy for UnsizedMeta<T> {}
//
// MetaData
//
pub trait MetaData<T: ?Sized>: Sized {
fn assemble(&self, data: *const u8) -> *const T;
fn disassemble(ptr: *const T) -> (Self, *const u8);
}
// The trait specialization -- because a slice specialization exists and people
// really shouldn't use that for sized T.
impl<T: ?Sized> MetaData<T> for UnsizedMeta<T> {
default fn assemble(&self, data: *const u8) -> *const T {
assert!(mem::size_of::<*const T>() == mem::size_of::<TraitObject>());
let object = TraitObject { data: data as *mut (), vtable: self.0 as *mut () };
unsafe { mem::transmute_copy(&object) }
}
default fn disassemble(ptr: *const T) -> (Self, *const u8) {
assert!(mem::size_of::<*const T>() == mem::size_of::<(usize, usize)>());
let object: TraitObject = unsafe { mem::transmute_copy(&ptr) };
(UnsizedMeta(object.vtable as usize, PhantomData), object.data as *const u8)
}
}
// The slice specialization. It is dodgy, avoiding the use of from_raw_parts to
// avoid materializing a reference in case the pointer is dangling.
impl<T> MetaData<[T]> for UnsizedMeta<[T]> {
fn assemble(&self, data: *const u8) -> *const [T] {
debug_assert!(mem::size_of::<*const [T]>() == mem::size_of::<(usize, usize)>());
unsafe { mem::transmute_copy(&(data, self.0)) }
}
fn disassemble(ptr: *const [T]) -> (Self, *const u8) {
debug_assert!(mem::size_of::<*const [T]>() == mem::size_of::<(usize, usize)>());
let (data, len): (usize, usize) = unsafe { mem::transmute_copy(&ptr) };
(UnsizedMeta(len, PhantomData), data as *const u8)
}
}
impl<T> MetaData<T> for SizedMeta<T> {
fn assemble(&self, data: *const u8) -> *const T {
data as *const T
}
fn disassemble(ptr: *const T) -> (Self, *const u8) {
(SizedMeta(PhantomData), ptr as *const u8)
}
}
//
// MetaPointee
//
pub trait MetaPointee {
type Meta: MetaData<Self> + Clone + Copy;
fn assemble(meta: Self::Meta, data: *const u8) -> *const Self;
fn disassemble(ptr: *const Self) -> (Self::Meta, *const u8);
}
impl<T: ?Sized> MetaPointee for T {
default type Meta = UnsizedMeta<T>;
default fn assemble(meta: Self::Meta, data: *const u8) -> *const Self {
meta.assemble(data)
}
default fn disassemble(ptr: *const Self) -> (Self::Meta, *const u8) {
<Self::Meta as MetaData<Self>>::disassemble(ptr)
}
}
impl<T> MetaPointee for T {
type Meta = SizedMeta<T>;
}
} // mod meta
use std::fmt::Debug;
use meta::MetaPointee;
fn main() {
let array = [1; 7];
let slice: &[i32] = &array[..];
let slice_pointer: *const [i32] = slice as *const _;
let trait_pointer: *const dyn Debug = &array as &dyn Debug as *const _;
let (slice_meta, slice_data) = MetaPointee::disassemble(slice_pointer);
println!("{:?} / {:?}", slice_data, slice.as_ptr() as *const u8);
let (trait_meta, trait_data) = MetaPointee::disassemble(trait_pointer);
println!("{:?} / {:?}", trait_data, slice.as_ptr() as *const u8);
let reassembled_slice_pointer: *const [i32] =
MetaPointee::assemble(slice_meta, slice_data);
println!("{:?}", unsafe { &*reassembled_slice_pointer });
let reassembled_trait_pointer: *const dyn Debug =
MetaPointee::assemble(trait_meta, trait_data);
println!("{:?}", unsafe { &*reassembled_trait_pointer });
}
This prints:
0x7ffcf955ee04 / 0x7ffcf955ee04
0x7ffcf955ee04 / 0x7ffcf955ee04
[1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1]