2

Given a Box holding a dyn Trait object, is there any way to construct a Boxed unsized type?

For example, suppose I have a (potentially) unsized type:

pub struct IdAndData<T: ?Sized> { id: i32, data: T, } 

Is there any function that implements this signature?

pub fn construct_from_box<T: ?Sized>(id: i32, data: Box<T>) -> Box<IdAndData<T>> { todo!() } 

This would allow something like:

fn main() { let anything: Box<dyn Any> = Box::new(123); let tagged_anything = construct_from_box(1, anything); } 

Playground

I know it would have to consume data, allocate enough space for the id and data together, then move data to the new allocation. This seems possible (we know all of the sizes at run-time), but the Rustonomicon suggests that it can only be done with an unsizing coercion with slices.

Is there any safe way to do this operation?

2
  • Interesting question. I think, although there's no obvious reason this shouldn't work, DST representation is left unspecified deliberately so making it work in the obvious way might rule out desirable future extensions (custom DSTs). So... maybe not? Commented Apr 20, 2021 at 1:34
  • I suspected that might have been the answer. The nomicon calls custom DSTs half-baked, and I suspect that's still current. I guess there isn't the demand (I'm getting away with just boxing what I need), plus the uncertainty over DST representation. Commented Apr 20, 2021 at 3:06

1 Answer 1

2

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

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.