Reverse unsizing coercions

Inspired by this question, I have the following questions: what about reversing unsizing coercions?

Say T: CoerceUnsized<U>, so we can do an unsizing coercion from T to U.
In many cases we know how to test at runtime if a value of type U actually fits / came from a value of type T. These cases are unsizing an array into a slice, and unsizing T into dyn Trait where Trait is a subtrait of Any and T : Any.

Now, IMO the main questions are:

  1. Is there a way of converting back that I've missed? slices have a TryInto<[T; K]> implementation, and dyn Any has downcast methods.
    But, these only work in one layer of indirection, and don't work for example for Rc<[T]> into Rc<[T; K]>.

  2. Is the conversion actually generally safe? For example, if we have some trait

    pub trait Component: Any {
        ...
    }
    

    Is this function safe?

    /// Try to perform the opposite of an unsizing corecion.
    /// Fails if the inner type is different than `T`.
    /// Note: should work with any trait that extends `Any`
    fn try_resize<T: 'static + Sized>(cell: &RefCell<dyn Component>) -> Option<&RefCell<T>> {
        let id = (*cell.borrow()).type_id();
        if id != TypeId::of::<T>() {
            None
        } else {
            // SAFETY:
            // We already checked that the inner type of `dyn Component` is `T` by checking the type id's.
            //
            // We also know that `RefCell<dyn Component>` with an actual value of `T` and
            // `RefCell<T>` have the same memory representation, since unsizing coercions can coerce
            // `RefCell<T>` into `RefCell<dyn Component>`.
            //
            // We also know that these two types are allowed to be punned, again since unsizing coercions
            // can coerce the other way around.
            unsafe { Some(&*(cell as *const RefCell<dyn Component> as *const RefCell<T>)) }
        }
    }
    
  3. If this is safe, what about actually implementing this generally?
    I think this is feasible, by adding a new special trait Resize<U>, where U: TryResize<T> holds iff T: Unsize<U> and the coercion from T to U arises from converting an array into a slice, or some type into a dyn Trait where Any is a supertrait of Trait.
    Then another TryCoerceInto will be derived from that, parallel to CoerceUnsized.

    Since it's quite similar to Unsize it probably shouldn't be too difficult to implement?

1 Like

A generalization of this to any subtrait of Any (but still restricted to RefCell specifically) might be as simple as

fn try_resize<T: 'static>(cell: &RefCell<impl ?Sized + Any>) -> Option<&RefCell<T>> {
    let id = (*cell.borrow()).type_id();
    if id != TypeId::of::<T>() {
        None
    } else {
        unsafe { Some(&*(cell as *const RefCell<_> as *const RefCell<T>)) }
    }
}

I haven’t thought too deeply about soundness yet (on first glance it looks okay) – nor the more general questions you asked.

1 Like

Nice.
At first I thought this wouldn't work because dyn Trait can implement Any on its own even if Trait isn't a subtrait of Any.

Instead, it just means that you can compile code that looks like it would work but would never work:

let cell: RefCell<Box<dyn Any>> = RefCell::new(Box::new(5u32));
let _ = try_resize::<Box<u32>>(&cell).expect("didn't convert");

Because Box<dyn Any> has its own Any implementation that gets a different type id than its "actual" type.

But it seems sound still.