Why does importing Borrow break this code?

Hello all, I’ve been poking at @Gankro’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: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2da83ae40bc3876c9237ea8d7a79bbaa

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: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c43d65193d8133867baa45ee6b479e7d

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 @Gankro 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