12

I came across this issue when trying to add the impl Add<char> for String to the standard library. But we can replicate it easily, without operator shenanigans. We start with this:

trait MyAdd<Rhs> {
    fn add(self, rhs: Rhs) -> Self;
}

impl MyAdd<&str> for String {
    fn add(mut self, rhs: &str) -> Self {
        self.push_str(rhs);
        self
    }
}

Simple enough. With this, the following code compiles:

let a = String::from("a");
let b = String::from("b");
MyAdd::add(a, &b);

Note that in this case, the second argument expression (&b) has the type &String. It is then deref-coerced into &str and the function call works.

However, let's try to add the following impl:

impl MyAdd<char> for String {
    fn add(mut self, rhs: char) -> Self {
        self.push(rhs);
        self
    }
}

(Everything on Playground)

Now the MyAdd::add(a, &b) expression above leads to the following error:

error[E0277]: the trait bound `std::string::String: MyAdd<&std::string::String>` is not satisfied
  --> src/main.rs:24:5
   |
2  |     fn add(self, rhs: Rhs) -> Self;
   |     ------------------------------- required by `MyAdd::add`
...
24 |     MyAdd::add(a, &b);
   |     ^^^^^^^^^^ the trait `MyAdd<&std::string::String>` is not implemented for `std::string::String`
   |
   = help: the following implementations were found:
             <std::string::String as MyAdd<&str>>
             <std::string::String as MyAdd<char>>

Why is that? To me it seems like deref-coercion is only done when there is only one function candidate. But this seems wrong to me. Why would the rules be like that? I tried looking through the specification, but I haven't found anything on argument deref coercion.

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • This reminds me of [this answer](https://stackoverflow.com/a/52692592/3650362) (I wrote). The compiler knows the trait generically, and when there's only one `impl` that applies, it can disambiguate by choosing the type argument used in that `impl`. In the other Q&A I used this ability to make the compiler (appear to) choose an `impl` at the call site, which is something it can't usually do. Presumably in *this* case that's what allows it to do deref coercion. But that's only a guess. – trent Nov 16 '19 at 14:27
  • 2
    [Here is a comment](https://github.com/rust-lang/rust/pull/66215#issuecomment-554750813) which states that if only one impl is found the compiler "eagerly confirms" it, which allows deref coercions (among other things) to happen. That does not happen for multiple impl candidates. So I guess that's kind of the answer, but I'd stil love to know more. [This chapter](https://rust-lang.github.io/rustc-guide/traits/resolution.html) in the rustc book might help, but as far as I can tell, it doesn't specifically say something about this. – Lukas Kalbertodt Nov 17 '19 at 16:54

1 Answers1

1

As you yourself explained, the compiler treats the case where there is only one valid impl specially, and can use this to drive type inference:

Here is a comment which states that if only one impl is found the compiler "eagerly confirms" it, which allows deref coercions (among other things) to happen. That does not happen for multiple impl candidates.

The second part is that deref coercion will only happen at sites where the expected type is known, it doesn't happen speculatively. See coercion sites in the reference. Impl selection and type inference must first explicitly find that MyAdd::add(&str) would be expected, to attempt to coerce the argument to &str.

If a workaround is needed in this situation, use an expression like &*b or &b[..] or b.as_str() for the second argument.

ramslök
  • 145
  • 4