How to do a std::any::Any::downcast(Box<dyn MyTrait>) or return Box<dyn MyTrait>?

How to handle downcasting such that in case of failure i can return argument that was passed in function call?

Code available in playground.

struct Obj;

fn cast_or_return(initial_obj: Box<dyn MyTrait>) -> Result<Box<Obj>, Box<dyn MyTrait>> {
    let any_obj: Box<dyn Any> = initial_obj;

    match any_obj.downcast::<Obj>() {
        | Ok(done) => {
            Ok(done)
        }

        | Err(another_obj) => {
            // How to upcast back to initial_object?
            let cast_back: Box<dyn MyTrait> = another_obj;
            Err(cast_back)

            //Ok(Box::new(Obj))
        }
    }

}

fn main() {
    let o: Box<dyn MyTrait> = Box::new(Obj);
    cast_or_return(o).ok();
}


use std::any::Any;
trait MyTrait: Any {}
impl MyTrait for Obj {}

I just can't come up with any sane way to solve this. Any ideas would be appreciated.

1 Like
fn cast_or_return(initial_obj: Box<dyn MyTrait>) -> Result<Box<Obj>, Box<dyn MyTrait>> {
    if (&*initial_obj as &dyn Any).downcast_ref::<Obj>().is_some() {
        (initial_obj as Box<dyn Any>)
            .downcast::<Obj>()
            .map_err(|_| unreachable!())
    } else {
        Err(initial_obj)
    }
}

it's ugly, it does the chack twice (that will be optimized away in release mode), it has a very sus unreachable!, but it works and i don't think there is anything better

1 Like

actually it seems it can't because of vtable sheninigans

Be it as it may, but for now this is the only solution that is not unsafe and works.

The unsafe version would be

    if (&*initial_obj as &dyn Any).type_id() == TypeId::of::<Obj>() {
        let ptr = Box::into_raw(initial_obj) as *mut Obj;
        // SAFETY: We just confirmed the `TypeId`
        unsafe {
            Ok(Box::from_raw(ptr))
        }
    } else {
        Err(initial_obj)
    }
1 Like

This looks neat. How does Box::into_raw deal with fat pointer? Just forgets trait info and keeps pointer to struct?

dyn raw pointers are wide (include the vtable pointer still). That's what into_raw returns. The cast to *mut Obj is what discards the vtable pointer.

because you need a Result

1 Like

oh yeah my bad