A question on automatically deref

Hello guys

I met a very confusing situation on automatically dereference.
The Code is shown bellow:

let mut tmp:Arc<RefCell<Instr>> = req.1.clone();
let mut tmp2 = (*tmp).borrow_mut();
let mut tmp3 = tmp.borrow_mut();
let mut tmp4 = tmp.borrow();

and this is what rust-analyzer told me:
image

tmp is a Arc pointer, pointing to a Refcell struct called Instr.
tmp2 is manually derefed tmp. The type is correct (RefMut)
however, tmp3 is automatically derefed tmp. The type is wrong (&mut Arc>RefCell>)
The automatic dereference seems to fail.
However, tmp4 = tmp.borrow(), and automatic deref works again.

It is my second time programming in Rust. I am really confused.

Does someone knows why it behaved like this.

1 Like

I get a different type for tmp3 on Playground:

use std::cell::{RefCell, Ref, RefMut};
use std::sync::Arc;

struct Instr;

fn main() {
    let tmp: Arc<RefCell<Instr>> = Arc::new(RefCell::new(Instr));
    let tmp2: RefMut<'_, Instr> = (*tmp).borrow_mut();
    drop(tmp2);
    let tmp3: RefMut<'_, Instr> = tmp.borrow_mut();
    drop(tmp3);
    let tmp4: Ref<'_, Instr> = tmp.borrow();
    drop(tmp4);
}

(Playground)

Maybe the rust-analyzer does something wrong?

To reproduce the OP you need to have use std::borrow::BorrowMut; in scope.

3 Likes

Okay: Playground.

I suppose this is because every type T implements Borrow<T>, so std::borrow::Borrow::borrow_mut is chosen instead of std::cell::RefCell::borrow_mut.

The exact order of method resolution is specified here in the reference:

Method-call expressions

[…]

When looking up a method call, the receiver may be automatically dereferenced or borrowed in order to call a method. This requires a more complex lookup process than for other functions, since there may be a number of possible methods to call. The following procedure is used:

The first step is to build a list of candidate receiver types. Obtain these by repeatedly dereferencing the receiver expression's type, adding each type encountered to the list, then finally attempting an unsized coercion at the end, and adding the result type if that is successful. Then, for each candidate T, add &T and &mut T to the list immediately after T.

This wording is a bit confusing, but look at the last sentence: "Then, for each candidate T, add &T and &mut T to the list immediately after T."

Thus, the compiler will try borrow_mut on the &mut Arc first, which succeeds (with Borrow::borrow_mut), before dereference kicks in (which would let you use RefCell::borrow_mut).


I would like to add that deref-coercion still works when there is no disambiguity, e.g. as argument to a function:

use std::borrow::BorrowMut;
use std::cell::{RefCell, Ref, RefMut};
use std::sync::Arc;

struct Instr;

fn ref_cell_borrow_mut<'a, T>(ref_cell: &'a RefCell<T>) -> RefMut<'a, T> {
    ref_cell.borrow_mut()
}

fn main() {
    let tmp: Arc<RefCell<Instr>> = Arc::new(RefCell::new(Instr));
    let _: RefMut<'_, Instr> = (*tmp).borrow_mut();
    let _: RefMut<'_, Instr> = ref_cell_borrow_mut(&tmp); // we don't need `*` here
    let _: RefMut<'_, Instr> = ref_cell_borrow_mut(&*tmp); // but we can use it
    let _: Ref<'_, Instr> = tmp.borrow();
}

(Playground)

Here, deref-coercion happens when calling ref_cell_borrow_mut(&tmp).

It’s uncommon you’d want to have borrow::BorrowMut in scope, so that’s the problem here. BorrowMut is most commonly only used for writing generic functions[1] akin to … well, I’d say HashMap, but they use Borrow, not BorrowMut … anyways, the point being that - at least for the standard library impls - the functionality you can get from using it on concrete types is way better expressed with other means.

Looking at the std impls, it can

  • do nothing to &mut T (or borrow T via auto-ref)
    • do nothing or use explicit &mut x instead!
    • (I’m skipping the auto-ref scenarios in the following cases below, but the method-based solutions should still work, and the other approaches work or can be adapted, too)
  • borrow &mut String as &mut str or &mut Vec<T> as &mut [T]
    • use as_mut_str/as_mut_slice or &mut x[..] or the (often implicit) deref, or explicit deref &mut **x) instead!
  • borrow &mut Box<T> as &mut T
    • use the (often implicitly possible) deref instead, explicitly &mut *the_box
  • borrow &mut [N; T] as &mut [T]
    • use as_mut_slice or &mut array[..] or the implit unsizing coercion instead!

  1. in which case you often don’t need to bring borrow::BorrowMut into scope to be able to call foo.borrow_mut() inside of the function anyways; and for the function signatures just use std::borrow;, and then write borrow::BorrowMut ↩︎

Thx, it works. As you said, the problem did happened on std::borrow::Borrow::borrow_mut is chosen instead of std::cell::RefCell::borrow_mut. Thanks!

Thanks to @Hyeonu for figuring out it wasn't in scope in my example.

1 Like