为什么添加第二个实现会防止参数的解引用强制转换?

12

当我试图将Add<char> for String添加到标准库时,我遇到了这个问题。但是我们可以轻松地复制它,而不需要操作符的花招。我们从这个开始:

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
    }
}

很简单。使用此方法,以下代码将被编译:

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

请注意,在本例中,第二个参数表达式 (&b) 的类型是 &String。然后将其解引用强制转换为 &str 并使函数调用工作。

然而,让我们尝试添加以下实现:

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

(Playground 上的所有内容)

现在,上面的 MyAdd::add(a, &b) 表达式会导致以下错误:

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>>
为什么会这样? 在我看来,当只有一个函数候选时才会进行deref-coercion。但是这似乎是错误的。为什么规则会是这样呢?我试图查看规范文件,但我没有找到有关参数deref coercion的内容。

这让我想起了我写的这个答案。编译器通常会泛化特征,并且当只有一个适用的impl时,它可以通过选择在该impl中使用的类型参数来消除歧义。在另一个问题和答案中,我利用了这种能力,使编译器(表面上)在调用站点选择一个impl,这是它通常无法做到的。大概在这种情况下,这就是允许它进行解引用强制转换的原因。但这只是一个猜测。 - trent
2
这里有一条评论,指出如果只找到一个impl,则编译器会“急切地确认”它,这允许发生解引用强制转换(以及其他事情)。对于多个impl候选者,这种情况不会发生。所以我想这就是答案,但我仍然想知道更多。Rustc书中的这一章节可能有所帮助,但据我所知,它并没有明确说明这一点。 - Lukas Kalbertodt
1个回答

1

正如您自己所解释的那样,编译器会特殊处理只有一个有效的impl的情况,并且可以利用这一点来驱动类型推断:

这里是一条评论指出,如果只找到一个impl,编译器会“急切地确认”它,这允许发生defer coercions(等等)。对于多个impl候选者,这种情况不会发生。

第二部分是,deref coercion只会在期望类型已知的位置发生,不会进行猜测。请参见引用中的coercion sites。Impl选择和类型推断必须首先明确找到MyAdd::add(&str)将被期望,才能尝试将参数强制转换为&str

如果在这种情况下需要解决方法,可以使用类似于&*b&b[..]b.as_str()的表达式作为第二个参数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接