Reference Decisions

I have a couple of situations where it's not clear to me which way is best ( and maybe it just doesn't matter.... and that's mostly what bugging me ).

(1) If I have an Rc pointer, say type FooPtr = Rc<Foo>; which may need to be cloned, which is being passed to a function. Should I declare the parameter as FooPtr or &FooPtr? Cloning an Rc is a cheap operation, maybe passing by reference might mean the Rc pointer has to be stored in memory ( so it has an address, so may be more expensive than cloning ). I cannot really decide which is likely to be better. I tend to pass it by reference, but maybe this isn't best.

(2) Is it best to always take a reference immediately after calling borrow() or borrow_mut() on a RefCell? I think so, but again, it's not really clear to me.

In other words which to prefer from

let p = &pp.borrow_mut(); if p.something { p.somethingelse() }

or

let p = pp.borrow_mut(); if p.something{ p.somethingelse() }

Clippy doesn't seem to have an opinion on this. I know it probably doesn't really matter, but I find it hard when there is a choice to be made and I don't know the best thing to do!

Help! :slight_smile:

`

the latter has the advantage of being more concise, and also it enables you to prematurely end the borrow by calling drop(p) if you need to

Regarding (1): Not sure what's best in your particular scenario, but generally I believe an interface should best suit the callee. If it doesn't fit the caller, then the caller may perform a clone. I.e. if a function works on a value destructively, it should consume it. However, if a function sometimes works destructively, it should accept a reference and do the cloning when needed. In your case: if the clone is always needed, then consume the Rc; and if only sometimes a clone is needed, then pass a reference to the Rc and clone inside your function when needed.

Regarding (2): RefCell::borrow_mut returns a RefMut, which implements Deref and DerefMut. Thus you can make method calls directly on the value returned by borrow_mut. Thus the following code makes sense:

let p = pp.borrow_mut(); if p.something{ p.somethingelse() }

Making another borrow (&pp.borrow_mut()) doesn't make sense to me. If you really need a reference (instead of RefMut), I think you can do that with let p = pp.borrow_mut(); let r = &*p; (or let r = p.deref();). But I don't think that's necessary in a lot of cases.

My understanding is that borrow_mut returns a value of type cell::RefMut. So the type of p is either RefMut or &T where T is the type in the RefCell. But both are the same pointer I guess, just wrapped differently I think.

Well my thinking was that I don't want to concern myself with RefMut, I don't want variables with type RefMut, so I get rid of the RefMut as soon as possible. steffhahn has pointed out the RefMut can be useful in that it can be dropped to release the borrow ( which I had not considered ). All-in-all I think this is a case of "it doesn't matter at all", and I think I have now satisfied myself about this, which is good, thanks everyone :slight_smile:

On (1), I think I tend to agree with your observations, although I do still wonder if the expense of passing the parameter by reference could outweigh the cost of premature ( but cheap ) cloning. For example, I think it might mean a function has to set up a stack frame in order to pass the Rc by reference. I guess the thing to do is to look at what code the compiler generates in specific cases. Maybe I will do that shortly.

Actually let p = &pp. borrow_mut() will result in p: &RefMut<'_, T>. But &RefMut<'_, T> can be implicitly coerced into &T, that’s why it might have felt to you as if p: &T.

2 Likes

So actually doing an extra borrow can make sense and you don't need the deref as I suggested. I made a small example:

struct SmartPtr<'a, T>(&'a T); 
 
impl<'a, T> std::ops::Deref for SmartPtr<'a, T> { 
    type Target = T; 
    fn deref(&self) -> &Self::Target { 
        self.0 
    } 
} 
 
fn takes_i32_ref(arg: &i32) { 
    println!("Got: {}", arg); 
} 
 
fn main() { 
    let i: i32 = 1; 
    let r: &i32 = &i; 
    let s: SmartPtr<i32> = SmartPtr(r); 
    takes_i32_ref(r); 
    takes_i32_ref(&s); // borrowing is required here 
}

(Playground)

In case of calling methods of the value, you don't need the borrow, but in case of passing the value as an argument (as in the example above), you may need to explicitly borrow.

Like @steffahn said, it's not a &T then, but a &SmartPtr<T> (or &RefMut<'_, T> in your example). But that is fine as Rust will automatically do the rest.

Side note: Whether a method or function expects a reference or consumes a certain type of smart pointer as argument may differ from case to case. Some functions may demand an Arc<T> to be passed as argument, for example.

Ah, so my understanding was wrong in any case. I need &* to get what I was intending. Not that it seems to make any difference, unless you try to drop it or something.

Note that it may be necessary to use & (or &* if you really want a plain reference) when passing the value as an argument (instead of calling methods on it). But the compiler will give you a very easy to understand error message in that case:

19 |     takes_i32_ref(s);
   |                   ^
   |                   |
   |                   expected `&i32`, found struct `SmartPtr`
   |                   help: consider borrowing here: `&s`

(Example taken from Playground above.)

But you don't need &* but just &.

Note that this is the same as you can't pass a String to a function that expects a &String. Thus this problem (when passing a value as argument) isn't just limited to smart pointers.

(Edit: Replaced "Not" with "Note", sorry about that typo!)

Regarding borrowing from RefMuts and smart-pointers, I stumbled upon some more complications. See: When locks or borrows are released (i.e. dropped)

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.