Can placement new/box ever work with DSTs?


#1

So, the placement/box RFC assumes that one might eventually box/place DSTs. However, unlike sized types, it’s impossible to know the size of a DST without knowing the value. This means that, with DSTs, we’d need to compute the value before placing which would violate LTR execution order (and also kind of defeats the point of placement new/box).

In code, sized type placement desugars to:

let p = PLACE;
let mut place = Placer::make_place(p);
let raw_place = Place::pointer(&mut place);
let value = EXPR;
unsafe {
    std::ptr::write(raw_place, value);
    Place::finalize(place)
}

But, for DSTs, we can’t call Placer::make_place(p) until we know value because we need the value to get the size and alignment.

let p = PLACE;
let value = EXPR; // pretend this works.
let mut place = Placer::make_place(p, mem::align_of_val(&value), mem::size_of_val(&value));
let raw_place = Place::pointer(&mut place);
unsafe {
    std::ptr::write(raw_place, value);
    Place::finalize(place)
}

#2

We basically punted on this question for the short term.

I had some brief discussions on the topic, but no obvious “right answer” arose from them.

Maybe we will end up extending the language with some new syntax to enable placement-in/box to work nicely with DST’s, rather than attempt to shoe-horn it onto the existing syntactic sugar.


#3

I’m mostly wondering if I should try to support DSTs in the placement traits given that this appears to be an independent problem (it’s not placement new, it’s DST by value).

Unless I’m mistaken, the protocol will be significantly different and I don’t really think it even needs it’s own syntax or even placer/place traits.

I’m imagining something like:

mod protocol {
    unsafe trait DerefMove: DerefMut {
        /// This method will be called when inner value has been moved out and should be forgotten.
        fn finalize(self);
    }
}

fn main() {
    let zeros = box iter::repeat(0) as Box<Iterator>;
    let dst_zeros: Iterator = *zeros;
    let shared_zeros: Rc<Iterator> = Rc::new(dst_zeros);
    // desugars to:
    // ```
    // {
    //     let dst_zeros: &mut Iterator = &mut zeros;
    //     let shared_zeros: Rc<Iterator> = Rc::new(dst_zeros);
    // }
    // // Late drop. To be eager, we'd need to carry a pointer to zeros with dst_zeros.
    // DerefMove::finalize(zeros);
    // ```
}

impl<T> Rc<T: ?Sized> {
    pub fn new(value: T) -> Self {
        unsafe {
            let ptr: *mut T = HEAP::alloc(Kind::from_val(&value));
            // assume that ptr::write accepts DSTs
            ptr::write(ptr, value);
            /* Everything else */
    }
}

Regardless of the actual implementation, I don’t think we need any special traits or syntax for placement.