Search code examples
rusttrait-objects

Rc<Trait> to Option<T>?


I'm trying to implement a method that looks like:

fn concretify<T: Any>(rc: Rc<Any>) -> Option<T> {
    Rc::try_unwrap(rc).ok().and_then(|trait_object| {
        let b: Box<Any> = unimplemented!();
        b.downcast().ok().map(|b| *b)
    })
}

However, try_unwrap doesn't work on trait objects (which makes sense, as they're unsized). My next thought was to try to find some function that unwraps Rc<Any> into Box<Any> directly. The closest thing I could find would be

if Rc::strong_count(&rc) == 1 {
    Some(unsafe {
        Box::from_raw(Rc::into_raw(rc))
    })
} else {
    None
}

However, Rc::into_raw() appears to require that the type contained in the Rc to be Sized, and I'd ideally not like to have to use unsafe blocks.

Is there any way to implement this?

Playground Link, I'm looking for an implementation of rc_to_box here.


Solution

  • Unfortunately, it appears that the API of Rc is lacking the necessary method to be able to get ownership of the wrapped type when it is !Sized.

    The only method which may return the interior item of a Rc is Rc::try_unwrap, however it returns Result<T, Rc<T>> which requires that T be Sized.

    In order to do what you wish, you would need to have a method with a signature: Rc<T> -> Result<Box<T>, Rc<T>>, which would allow T to be !Sized, and from there you could extract Box<Any> and perform the downcast call.

    However, this method is impossible due to how Rc is implemented. Here is a stripped down version of Rc:

    struct RcBox<T: ?Sized> {
        strong: Cell<usize>,
        weak: Cell<usize>,
        value: T,
    }
    
    pub struct Rc<T: ?Sized> {
        ptr: *mut RcBox<T>,
        _marker: PhantomData<T>,
    }
    

    Therefore, the only Box you can get out of Rc<T> is Box<RcBox<T>>.

    Note that the design is severely constrained here:

    • single-allocation mandates that all 3 elements be in a single struct
    • T: ?Sized mandates that T be the last field

    so there is little room for improvement in general.


    However, in your specific case, it is definitely possible to improve on the generic situation. It does, of course, require unsafe code. And while it works fairly well with Rc, implementing it with Arc would be complicated by the potential data-races.

    Oh... and the code is provided as is, no warranty implied ;)

    use std::any::Any;
    use std::{cell, mem, ptr};
    use std::rc::Rc;
    
    struct RcBox<T: ?Sized> {
        strong: cell::Cell<usize>,
        _weak: cell::Cell<usize>,
        value: T,
    }
    
    fn concretify<T: Any>(rc: Rc<Any>) -> Option<T> {
        //  Will be responsible for freeing the memory if there is no other weak
        //  pointer by the end of this function.
        let _guard = Rc::downgrade(&rc);
    
        unsafe {
            let killer: &RcBox<Any> = {
                let killer: *const RcBox<Any> = mem::transmute(rc);
                &*killer 
            };
    
            if killer.strong.get() != 1 { return None; }
    
            //  Do not forget to decrement the count if we do take ownership,
            //  as otherwise memory will not get released.
            let result = killer.value.downcast_ref().map(|r| {
                killer.strong.set(0);
                ptr::read(r as *const T)
            });
    
            //  Do not forget to destroy the content of the box if we did not
            //  take ownership
            if result.is_none() {
                let _: Rc<Any> = mem::transmute(killer as *const RcBox<Any>);
            }
    
            result
        }
    }
    
    fn main() {
        let x: Rc<Any> = Rc::new(1);
        println!("{:?}", concretify::<i32>(x));
    }