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.
This mechanism is called ‘ Deref coercion’. In mutable contexts, DerefMut is used. Implementing Deref for smart pointers makes accessing the data behind them convenient, which is why they implement Deref. On the other hand, the rules regarding Deref and DerefMut were designed specifically to accommodate smart pointers.
Implementing Deref for smart pointers makes accessing the data behind them convenient, which is why they implement Deref. On the other hand, the rules regarding Deref and DerefMut were designed specifically to accommodate smart pointers. Because of this, Deref should only be implemented for smart pointers to avoid confusion.
In mutable contexts, DerefMut is used. Implementing Deref for smart pointers makes accessing the data behind them convenient, which is why they implement Deref. On the other hand, the rules regarding Deref and DerefMut were designed specifically to accommodate smart pointers.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With