2

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.

Nathan Ringo
  • 973
  • 2
  • 10
  • 30
  • 2
    There is a dissonance here: `Option` *own* the `T`, whilst `Rc` is only one of the *co-owners* of the object. I can see two ways of handling this: returning `None` if there are multiple owners or converting the function to convert from `&Rc` to `Option<&T>`. Is the first one really what you want? – Matthieu M. Jan 12 '17 at 16:25
  • That's what the [`try_unwrap`](https://doc.rust-lang.org/std/rc/struct.Rc.html#method.try_unwrap) is for -- it ensures that the given `Rc` is the only owner. "`concretify`" should return `None` if either there are multiple owners or if the `Any` is not `T`. – Nathan Ringo Jan 12 '17 at 16:27
  • Just checking :) In this case, are you okay with `None` or would you rather return a `Result` which specify in its `Err` field whether the issue is a mismatched type or multiple owners? – Matthieu M. Jan 12 '17 at 16:31
  • If I were a better person, I'd want the `Result` :) If it's only possible with a `Result`, I'll certainly accept that, but I'm relatively certain that the semantics of the problem are the same, regardless of whether I use `Option`, `Result`, or `panic!("something bad happened guys")` – Nathan Ringo Jan 12 '17 at 16:32
  • Yes, it's only a cosmetic change :) – Matthieu M. Jan 12 '17 at 17:10

2 Answers2

3

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));
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
2

I don't think it's possible to implement your concretify function if you're expecting it to move the original value back out of the Rc; see this question for why.

If you're willing to return a clone, it's straightforward:

fn concretify<T: Any+Clone>(rc: Rc<Any>) -> Option<T> {
    rc.downcast_ref().map(Clone::clone)
}

Here's a test:

#[derive(Debug,Clone)]
struct Foo(u32);

#[derive(Debug,Clone)]
struct Bar(i32);

fn main() {
    let rc_foo: Rc<Any> = Rc::new(Foo(42));
    let rc_bar: Rc<Any> = Rc::new(Bar(7));
    
    let foo: Option<Foo> = concretify(rc_foo);
    println!("Got back: {:?}", foo);
    let bar: Option<Foo> = concretify(rc_bar);
    println!("Got back: {:?}", bar);
}

This outputs:

Got back: Some(Foo(42))

Got back: None

Playground

If you want something more "movey", and creating your values is cheap, you could also make a dummy, use downcast_mut() instead of downcast_ref(), and then std::mem::swap with the dummy.

Community
  • 1
  • 1
Chris Emerson
  • 13,041
  • 3
  • 44
  • 66
  • 2
    I'm afraid I'm not sure what part of the linked question prevents going from `Box` to `T`; that's what [`Box::downcast()`](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast) does. I'm just trying (at this point) to convert `Rc` to `Box`. (Editing question to clarify this) – Nathan Ringo Jan 12 '17 at 17:41
  • Hmm, I always forget that part of the `Any` docs are under `Box`! Good point, it isn't clear and now I'm less sure; I'll see if I can improve my answer later. – Chris Emerson Jan 12 '17 at 18:12