The page you linked says:
Currently the only properly supported way to create a custom DST is by making your type generic and performing an unsizing coercion: …
(Yes, custom DSTs are a largely half-baked feature for now.)
The thing you're asking for is entirely reasonable in context — it just doesn't exist yet. The unsizing coercion requires that the size is known at the coercion site.
I thought it might be interesting to try to make it work anyway by liberally applying unsafe. Don't use this for anything — I am not really familiar with writing good unsafe Rust code and just tinkered with things until it worked. There are probably even ways to get the same thing done with fewer hazards.
This code is for entertainment purposes only.
#![feature(layout_for_ptr)] #![feature(ptr_metadata)] use std::alloc; use std::alloc::Layout; use std::any::Any; use std::mem::size_of_val; use std::ptr::addr_of_mut; use std::ptr::Pointee; pub fn construct_from_box<T: ?Sized>(id: i32, data: Box<T>) -> Box<IdAndData<T>> { // Decompose the `data` pointer. let data_size = size_of_val(&*data); let (data_ptr, metadata): (*const (), <T as Pointee>::Metadata) = (&*data as *const T).to_raw_parts(); // UNSOUNDLY assume that the metadata must surely be the same, and // reinterpret so the types match. let coerced_metadata: <IdAndData<T> as Pointee>::Metadata = unsafe { *(&metadata as *const _ as *const <IdAndData<T> as Pointee>::Metadata) }; // Figure out what we need to allocate. // Safety: Layout::for_value_raw doesn't say the pointer per se needs to be // valid, just that its metadata does. let result_layout = unsafe { Layout::for_value_raw::<IdAndData<T>>(std::ptr::from_raw_parts_mut( &mut () as *mut (), // Not valid, but not used. coerced_metadata, )) }; // Actually allocate the memory for our future Box, // and attach the metadata. let result_mem = unsafe { alloc::alloc(result_layout) }; if result_mem.is_null() { alloc::handle_alloc_error(result_layout); } let result_ptr: *mut IdAndData<T> = std::ptr::from_raw_parts_mut( result_mem as *mut (), coerced_metadata, ); // Copy each field into the new allocation. unsafe { std::ptr::write(addr_of_mut!((*result_ptr).id), id); std::ptr::copy_nonoverlapping( data_ptr as *const u8, addr_of_mut!((*result_ptr).data) as *mut u8, data_size, ); } // Free the old `data` box, without running drop on its contents which we // just copied. (Is there a better way to do this, maybe with ManuallyDrop?) let data_layout = Layout::for_value(&*data); unsafe { alloc::dealloc(Box::into_raw(data) as *mut u8, data_layout); }; // result_ptr is now initialized so we can let Box manage it. unsafe { Box::from_raw(result_ptr) } }
Playground link