2

I am a big fan of BorrowMut as it allows me to provide APIs that allow to take ownership of arguments, or to take references as well. This makes them easier to use, but a little harder to implement for me - an acceptable tradeoff as the needs of the many outweigh the needs of the few :).

Now I am trying to use BorrowMut with RefCell and fail as borrow_mut() is implemented both by RefMut as well as BorrowMut. However, RefMut will take precedence, preventing my to drill down to the actual value contained within BorrowMut.

Example

The following code allows you to reproduce the issue - the goal is to call doit() on the Client type:

use std::cell::RefCell;
use std::borrow::BorrowMut;

struct Hub<C> {
    client: RefCell<C>
}

impl<C> Hub<C> 
    where C: BorrowMut<Client> {

    fn new(client: C) -> Hub<C> {
        Hub {
            client: RefCell::new(client)
        }
    }

    fn builder<'a>(&'a self) -> Builder<'a, C> {
        Builder {
            hub: self
        }
    }
}

struct Builder<'a, C: 'a> {
    hub: &'a Hub<C>
}

impl<'a, C> Builder<'a, C>
    where C: BorrowMut<Client> {
    fn use_client(self) {
        // 1: borrow_mut() of RefCell
        // 2: borrow_mut() of BorrowMut()
        // but doesn't work, as RefMut returned by 1) always yields RefMut
        self.hub.client.borrow_mut().borrow_mut().doit()
    }
}

struct Client;
impl Client {
    fn doit(&mut self) {
        println!("DID IT!!")
    }
}


// HUB USAGE
{
    let h = Hub::new(Client);
    h.builder().use_client();
}

{
    let mut c = Client;
    let h = Hub::new(&mut c);
    h.builder().use_client();
}

This produces the following error:

tests/lang.rs:1076:55: 1076:61 error: type `&mut core::cell::RefMut<'_, C>` does not implement any method in scope named `doit`
tests/lang.rs:1076             self.hub.client.borrow_mut().borrow_mut().doit()

Can you point out how I would make this call ? Is it possible at all ?

Meta

✗ rustc --version --verbose
rustc 1.0.0-nightly (3e4be02b8 2015-03-13) (built 2015-03-13)
binary: rustc
commit-hash: 3e4be02b80a3dd27bce20870958fe0aef7e7336d
commit-date: 2015-03-13
build-date: 2015-03-13
host: x86_64-apple-darwin
release: 1.0.0-nightly
Byron
  • 3,908
  • 3
  • 26
  • 35

1 Answers1

5

You’re running into an unfortunate side-effect of auto-borrowing rules where what is almost always what is wanted (and almost always unambiguous) has wound up being ambiguous for this particular case and it’s chosen what to you is the wrong way. If you pull apart the expression and inspect the type at each step it becomes more obvious.

The first borrow_mut is interpreted as a method on RefCell which yields the core::cell::RefMut<'_, C> that is desired. (The fact that it has such an intrinsic method overrides the fact that it could otherwise construct a borrow_mut call by automatically taking a reference to self.hub.client, if self was in a mutable slot, as is shown later in this answer.) The problem is that the second one isn’t calling the borrow_mut of the BorrowMut implementation that you want.

There are two things that can happen at this stage when you want to call a method: automatic taking of references and automatic dereferencing. In this particular case, both of these yield a borrow_mut method that you can call:

  • If it takes a mutable reference to the RefMut, then it has &mut RefMut<'_, C>, and &mut T implements the in-scope trait BorrowMut which provides a method borrow_mut, and so you just get another &mut RefMut<'_, C>, which solidifies this as the choice to use.

  • If it dereferences the RefMut then it can get at a C, which it can then use the BorrowMut<Client> implementation of to satisfy the requested borrow_mut method call, yielding a &mut Client.

Which does it take? I’m not sure if the rules are defined anywhere (though they definitely will need to be soon if they are not), but what one can observe happening is that the first path is taken: it tries autoreferencing before it tries dereferencing, and so ref_cell_borrow.borrow_mut() returns a &mut RefMut<'_, C> rather than a &mut Client.

If you wish to get it to use the other behaviour, you need to explicitly dereference the RefMut; then the automatic taking of a mutable reference can only get at the C, which is what you need.

Here’s the rough expansion with type annotations of what it needs to be; you can play around with things like assigning the type () to examine the error messages when you compile it:

let mut ref_cell_borrow: std::cell::RefMut<C> = self.hub.client.borrow_mut();
let client: &mut Client = (*ref_cell_borrow).borrow_mut();
client.doit();

Back in compact form, it’s (*self.hub.client.borrow_mut()).borrow_mut().doit().

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • Thank you ! I have fixed my comment ('RefCell' -> 'RefMut') as well. Also I think I should have figured this out myself, but on the other hand, it's probably good to have the question here too. – Byron Mar 16 '15 at 10:56
  • Autoref and autoderef can certainly get painful to figure out in extreme situations like this one of yours. Fortunately it’s almost always not an issue. – Chris Morgan Mar 16 '15 at 10:58