Why does importing Borrow break this code?

Hello all, I've been poking at @Gankra's excellent Learn Rust by writing Entirely Too Many linked lists and ran into a bug I can't get my head around.

I was following along and got to this code: Rust Playground

This throws the exception:

error[E0308]: mismatched types
  --> src/lib.rs:95:22
   |
95 |             Ref::map(node.borrow(), |node| &node.elem)
   |                      ^^^^^^^^^^^^^ expected struct `std::cell::Ref`, found reference
   |
   = note: expected type `std::cell::Ref<'_, _>`
              found type `&_`

error[E0609]: no field `elem` on type `&_`
  --> src/lib.rs:95:50
   |
95 |             Ref::map(node.borrow(), |node| &node.elem)
   |                                                  ^^^^

error[E0308]: mismatched types
   --> src/lib.rs:101:22
    |
101 |             Ref::map(node.borrow(), |node| &node.elem)
    |                      ^^^^^^^^^^^^^ expected struct `std::cell::Ref`, found reference
    |
    = note: expected type `std::cell::Ref<'_, _>`
               found type `&_`

error[E0609]: no field `elem` on type `&_`
   --> src/lib.rs:101:50
    |
101 |             Ref::map(node.borrow(), |node| &node.elem)
    |                                                  ^^^^

error: aborting due to 4 previous errors

Some errors occurred: E0308, E0609.

However if we remove the import line of use core::borrow::Borrow; and change nothing else, it successfully compiles and runs: Rust Playground

What's going on with this import and why is it causing this error? Should importing Borrow directly be avoided?

it looks like Rust is using &_'s implementation of Borrow and not doing deref coercion. This means that Rust inferred the wrong thing and gave an error. When the Borrow trait is not imported, Rust is not allowed to use it's &_'s Borrow implementation, so it derefs the reference and uses RefCell's borrow method.

If you rewrite node.borrow() to RefCell::borrow(node) it will compile with or without importing the Borrow trait.

1 Like

Yeah RefCell::borrow is a completely unrelated thing to Borrow::borrow. Because of a big blanket implementation basically everything always implements Borrow::borrow, but you can't actually see trait methods unless the trait is in scope, so this isn't a problem unless it's imported. This is also supposed to be handled by the fact that inherent methods are still supposed to dominate trait methods if there's a conflict.

But in this case even &RefCell implements Borrow, so the compiler finds that implementation "first" before applying auto-deref to find RefCell::borrow.

Generally Borrow is only really supposed to be used in generic code that explicitly requires T: Borrow, where this isn't an issue.

1 Like

Thank you both! (And thank you @Gankra for the book :slight_smile:) that adds a lot of clarity here.

This one is... really subtle. At my level, I was really only able to identify the issue because I had a reference implementation to look at, and literally diffed my code against it until I found the offending change. If this is the intended behavior, I wonder if there's a better way to bubble up this behavior as a warning or error (maybe at the editor level?)

1 Like

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