Using Ref::map()

I have a data type containing a RefCell<_> that contains a complex structure; I'd like to return a smart pointer of some kind to a specific thing within the structure. At present I'm saving the specific thing in the structure as an Rc<T> so that I can clone and return the Rc<T>. However, I'd like to see if there's a more efficient way to do it. That is, given
a RefCell<T> where T includes an inner S, can I convert the borrowed Ref<T> into a Ref<S>?

The type in question, Value represents a data value that can be returned as a number of different types. Computing one of these results can be expensive; and usually code will stick to one of the result types for long periods of time. Consequently, I use a RefCell and interior mutability to cache the value.

The type looks something like this (after I remove the irrelevant portions):

#[derive(Clone, Debug)]
pub struct Value {
    inner: Rc<InnerValue>,
}

#[derive(Debug)]
struct InnerValue {
    data_rep: RefCell<DataRep>,
    // ...
}

// The data representation for Values.
#[derive(Clone, Debug)]
enum DataRep {
    Int(i64),
    List(Rc<Vec<Value>>),
    None,
}

The DataRep enum holds the cached data representation. The List option stores the list in an Rc<_> so that I can clone and return a smart pointer, Rc<Vec<Value>> rather than Ref<DataRep>:

    pub fn as_list(&self) -> Result<Rc<Vec<Value>>, ResultCode> {
        // FIRST, if we have the desired type, return it.
        if let DataRep::List(list) = &*self.inner.data_rep.borrow() {
            return Ok(list.clone());
        }

        // NEXT, try to compute and cache the list
        ...
    }

Is there a way to return Ref<Vec<Value>> (or something similar) in this case without wrapping the Vec<Value> in an Rc<T>?

Aha! Ref::map.

1 Like

Still having trouble. I've now got this, which isn't quite what I want, since I want to get rid of the Rc, but it's a step on the way.

   pub fn as_list2(&self) -> Result<Ref<Rc<Vec<Value>>>, ResultCode> {
        // FIRST, if we have the desired type, return it.
        let iref = self.inner.data_rep.borrow();
        if let DataRep::List(list) = &*iref {
           return Ok(Ref::map(iref, |t| list));
        }
        ...
    }

I get an error on the let iref ... line: " cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements".

I'm not at all sure how to navigate lifetimes in this case.

You're creating a reference (list) to data in iref in the line with if let, then you're trying to change iref on the next line. This is not allowed it would invalidate the first reference. You should do the transformation inside the call to map, something like this:

        if let DataRep::List(_) = *iref {
           return Ok(Ref::map(iref, |t| match t {
               DataRep::List(list) => list,
               _ => unreachable!(),
           }));
        }

OK, that did the trick. Which is to say that it compiles, and returns the Ref<_> I was looking for.

Now, of course, my code is panicking with BorrowMutError for obscure reasons, and I think I'll go back to leaving the Ref<_> safely inside the module rather than exposing it. Because life's too short to be tracking down BurrowMutErrors at runtime, and the clients of my API shouldn't have to think that hard. :slight_smile:

But I had to give it a try, so thanks very much!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.