Callum Rogers
Callum Rogers

Reputation: 15829

Is it possible to map an Rc<T> to get an Rc<Subpart-of-T>?

I have an Rc<Option<T>> but need to get an Rc<T> from it. Something like:

let rc_option: Rc<Option<T>> = Rc::new(Ok(value));
let ok_value: Rc<T> = rc_option.map(|option| option.unwrap());

Is this even remotely possible? It seems like something that should make sense, as the Rc could just increment the counter it has internally for the new mapped value, but I can't find any docs for it.

Upvotes: 4

Views: 255

Answers (3)

Matthieu M.
Matthieu M.

Reputation: 299790

Not with Rc.

This is not possible with Rc simply because of its memory layout:

//  Equivalence:
struct RcBox<T> {
    strong: AtomicUsize,
    weak: AtomicUsize,
    data: T,
};

struct Rc<T> {
    ptr: *const RcBox<T>,
};

Therefore, the counters are expected to be right next to T, and so you cannot share counters between two distinct elements.


Alternatives are likely possible.

From a memory-layout point of view, it is perfectly acceptable to create an alternative FlexRc:

struct Counters {
    strong: AtomicUsize,
    weak: AtomicUsize, // if support for FlexWeak is desired.
    ptr: *mut (),
    drop: fn(*mut ()),
}

struct FlexRc<T> {
    counters: *mut Counters,
    ptr: *const T,
}

And this one could in theory allow mapping... however creating a safe interface over it may not be easy.

How do you prevent the user from returning an unrelated lifetime in map? Is guaranteeing the lifetime of the return reference exceeds that of flex sufficient to be safe?

fn fool(flex: FlexRc<Option<i32>>) -> FlexRc<i32> {
    let i = 3;
    flex.map(|_| &i)
}

Upvotes: 2

Chronial
Chronial

Reputation: 70663

As ThatOneDeveloper stated in their answer, that is not supported by the standard library. You could implement such a feature yourself:

use std::ops::Deref;

#[derive(Clone)]
struct RcSome<T>(Rc<Option<T>>);

impl<T> RcSome<T> {
    fn from(rc: &Rc<Option<T>>) -> RcSome<T> {
        RcSome(rc.clone())
    }
}

impl<T> Deref for RcSome<T> {
    type Target = T;
    fn deref(&self) ->  &T {
        self.0.as_ref().as_ref().unwrap()
    }
}

Then you can do this

let rc_option: Rc<Option<T>> = Rc::new(Some(value));
let ok_value: RcSome<T> = RcSome::from(&rc_option);

Note that this will panic if rc_option contains None. But ok_value will now behave like a Rc<T> – i.e. you can clone() it and do ok_value.some_method_of_T(). ok_value does also not share a lifetime with rc_option, so it can outlive it.

Upvotes: 1

snowsignal
snowsignal

Reputation: 466

Short Answer

No, it's not possible to create a Rc<T> from an Rc<Option<T>> that leaves the latter still existing. It is possible to create an Rc<&T> however, from a Rc<Option<T>>, while still leaving the latter variable existing.

Long Answer

If you're trying to create a new Rc<T> that owns the T inside the Rc<Option<T>>, you will have to consume the original Rc<Option<T>>. You also can't have multiple instances of the Rc<Option<T>>, because then you're moving the shared value while pointers still exist, which is very unsafe.

But there is a way to do this safely! Using Rc::try_unwrap, you can attempt to move the value out, but this will return an error if multiple instances of the original Rc exist. Keep in mind you also have to handle the scenario where Option<T> ends up being None.

Here's an example of this:

let rc_option: Rc<Option<T>> = Rc::new(Some(value));

match Rc::try_unwrap(rc_option) {
    Ok(option) => {
        match option {
            Some(t) => {
                let ok_value: Rc<T> = Rc::new(t);
                // Do something with ok_value
            }
            None => {
                // Do something else here
            }
        }
    }
    Err(rc_option) => {
        // There are multiple owners, do something else here
    }
}

If you wanted to preserve the original, you could do this:

match &*rc_option {
    Some(ref t) => {
        let ok_ref: Rc<&T> = Rc::new(t);
    }
    None => { /* Do something else, there's no internal value */ }
}

EDIT: As Chronial mentioned, do note that the ok_ref cannot outlive rc_option (because it's a reference to rc_option), which may not be what you want to happen.

Upvotes: 7

Related Questions