fn main() {
use core::fmt::Debug;
let i = 1;
let ptr = &i as * const i32;
let ptr = ptr as *const dyn Debug; // #1
unsafe {
println!("{:?}", &*ptr);
}
}
What is the result of #1? The Rust reference is unclear at this point. Is the result similar to what with_metadata_of defines?
This operation is semantically the same as creating a new pointer with the data pointer value of self but the metadata of meta
That is, the resulting fat pointer will have its data address &i as * const i32 and metadata of dyn Debug?
What else would it have? A dyn pointer always is composed of the address of the data and the address of the vtable. So in this case it would be a pointer to the variable i and the vtable of the i32 implementation of Debug.
The comparison with with_metadata_of is a bit misleading i think, as it doesn't take the metadata of another pointer. The metadata didn't exist before, the compiler just creates a pointer to the vtable. This is what unsized coercion means. The compiler creates some metadata that is correct for this usecase. Be it a size or a vtable pointer.
I recall a topic here where it was stated that vtables for a type T are not unique but might get duplicated per codegen unit, so two instances of T might have different vtable pointers, if I recall correctly. I'll try to see if I can find the topic again.
the casting of raw pointers with as is (mostly) the same as casting safe, borrowed pointers, i.e., unsize-coercing &i32 to &dyn Debug: the compiler will create the metadata for the fat pointer based on the type information of the source pointer. this has some implications, e.g.:
for thin-to-fat pointer casts, this is same as coercion of "normal" references, the source pointee type (i32 in this example) must implement Unsize<DST>, (DST is dyn Debug in this example).
for fat-to-fat pointer casts, there's no equivalent in reference coercions, only for raw pointers. the the source and target types must have the same metadata, e.g. a struct use another DST as the last field.
an example
here's an example to manually allocate memory for DST:
struct Foo {
// other fields omitted
payload: [u8],
}
fn allocate_foo(payload_len: usize) -> *mut Foo {
let header: Layout = todo!();
let payload = Layout::array::<u8>(payload_len);
let foo_layout = header.extend(payload);
// allocate memory, this is only the address, i.e. thin, no metadata
let addr = alloc_zeroed(foo_layout);
// manually assemble a fat pointer of byte slice, `payload_len` is the metadata
// slices are special case of DST, which can be done in stable
// other DSTs needs nightly to use the pointer metadata API
let ptr: *mut [u8] = std::ptr::slice_from_raw_parts(addr, payload_len);
// then cast it to `*mut Foo`, this is a fat-to-fat cast
ptr as *mut Foo
}
if neither of the above condition are met, the cast would be rejected by the type checker.
with_metadata_of takes the metadata of another pointer that you pass to the function. That is, you already need to have a pointer with the value of the metadata you need.
On the other hand dyn Debug is a type, not a pointer, and it does not define a value for its metadata (what would be the point of the metadata if it was always the same after all). Instead, it defined the type of its metadata, which is std::ptr::DynMetadata<dyn Debug>.
The actual value used for the metadata is mostly an implementation detail, but is dependent on the type of the pointer that you're converting. In this case the pointer points to a i32, so the value of std::ptr::DynMetadata<dyn Debug> for the i32 type will be used.
If you want to see an analogous of this operation in the stdlib there is std::ptr::from_raw_parts, but that does not include the process of creating the required metadata value (you'll need to provide it as an input).