Importing trait breaks usage of already implemented function

Hello, I'm new rustacean here and just learning the language using leetcode problems as a practice.

TL;DR; Introducing use std::borrow::BorrowMut; use std::borrow::Borrow; breaks .borrow() method for Rc<RefCell<T>> type. But it works if I write .as_ref().borrow().

Last couple of days daily leetcode has Trees. And I'm strugling with Rust more than with the problems since I still don't know many of concepts and do not understand borrowing fully.
One of my struggles was an LSP's auto import of std::borrow::Borrow. Every time it happens, part of my code breaks.
Below is one of my functions:


pub fn sum_of_left_leaves_without_copy(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
    let mut result = 0;

    fn go_deeper(root: &Option<Rc<RefCell<TreeNode>>>, is_left: bool, result: &mut i32) {
        if root.is_some() {
            let root = root.clone().unwrap();
            let borrowed_root = root.borrow();

            match (&borrowed_root.left, &borrowed_root.right) {
                (None, None) if is_left => *result += borrowed_root.val,
                (None, None) => {}
                (Some(_), None) => {
                    go_deeper(&borrowed_root.left, true, result);
                }
                (None, Some(_)) => {
                    go_deeper(&borrowed_root.right, false, result);
                }
                (Some(_), Some(_)) => {
                    go_deeper(&borrowed_root.left, true, result);
                    go_deeper(&borrowed_root.right, false, result);
                }
            }
        }
    }

    go_deeper(&root, false, &mut result);

    result
}

It works fine as is. But if I introduce

use std::borrow::BorrowMut;

There will be next errors:

error[E0282]: type annotations needed for `&Borrowed`
  --> src\trees.rs:70:17
   |
70 |             let borrowed_root = root.borrow();
   |                 ^^^^^^^^^^^^^
71 |
72 |             match (&borrowed_root.left, &borrowed_root.right) {
   |                     ------------------ type must be known at this point
   |
help: consider giving `borrowed_root` an explicit type, where the type for type parameter `Borrowed` is specified
   |
70 |             let borrowed_root: &Borrowed = root.borrow();
   |                              +++++++++++
error[E0609]: no field `left` on type `&_`
  --> src\trees.rs:72:35
   |
72 |             match (&borrowed_root.left, &borrowed_root.right) {
   |                                   ^^^^ unknown field

I tried to define type as first error suggested but I can't do it at all. RefCell<TreeNode> doesn't work and other variations that I could think of too.
But surprisingly introducing as_ref() fixes the issue.
So the line

            let borrowed_root = root.as_ref().borrow();

It works as expected, and there is no error.

So I have the next question:

  1. Why does use std::borrow::Borrow; break the code with errors listed above?
  2. Why using as_ref() fixes the issue?
  3. Is it possible to prevent LSP from suggesting options that lead to an import of a trait when autocompliting method?

Big thanks to everyone who read it all and will answer here. I will also be very grateful if you point out any useful further reading for me.

By bringing the trait into scope, its methods are considered during method resolution. And because every T implements Borrow<T>, the Borrow::borrow method will be a candidate for your Rc<_>.[1] You end up calling <Rc<_> as Borrow>::borrow instead of the inherent RefCell::<_>::borrow method.

Calling as_ref() on the Rc<RefCell<_>> returns a &RefCell<_>, and then method resolution finds both <RefCell<_> as Borrow>::borrow and the inherent RefCell::<_>::borrow method. In the case of "ties" like this, the inherent method takes precedence.


Basically, having methods with the same name is a footgun, and this is a misdesign in std. I try to avoid bringing the Borrow trait into scope, especially in modules where I'm also using RefCell<_>.


  1. As it would for any type. ↩ī¸Ž

2 Likes

AFAIK it is not possible. But my IDE differentiates between inherent methods and trait methods in autocomplete by including extra context:

image

The first borrow() option is the RefCell::borrow() inherent method. The second option, borrow() (use std::borrow::Borrow), tells you which trait it is proposing to add, and if it is selected the displayed use statement is also pulled into the module.

Once the trait is already in scope, then the autocomplete options change:

image

The inherent method is no longer an option, but it now shows (as Borrow) indicating which trait the method comes from.

(IDE is Sublime Text 4. Most IDEs should show the same kind of info. The only thing Sublime knows about the types is what Rust-Analyzer tells it.)


You can also use fully-qualified function call syntax as another workaround:

let borrowed_root = RefCell::borrow(root);
2 Likes

Yes, I'm using Visual Studio Code it has (as Borrow) text. So I'm aware of it after some trail and error :sweat_smile:

Also giant thank you for the workaround didn't think about this option.