为什么添加第二个impl可以防止对参数进行反引用强制转换?


10

尝试将impl添加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
    }
}

操场上的一切

现在,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强制上未找到任何内容。


这使我想起了这个答案(我写道)。编译器一般都知道该特性,并且当只有一个特性impl适用时,可以通过选择该特性中的类型参数来消除歧义impl。在其他Q&A中,我使用了此功能,使编译器(似乎)impl在调用站点选择了一个,通常这是做不到的。大概在这种情况下,这就是允许它执行反强制的原因。但这只是一个猜测。
trentcl

2
这是一条评论,指出如果仅找到一个隐含代码,编译器就会“急切地确认”该隐含代码,从而使deref强制(除其他外)发生。对于多个潜在候选人而言,这不会发生。所以我想这是答案,但是我仍然乐于了解更多。rustc书中的这一章可能会有所帮助,但是据我所知,它并没有对此特别说明。
Lukas Kalbertodt

Answers:


0

正如您自己所解释的,编译器impl专门处理只有一个有效值的情况,并且可以使用它来驱动类型推断:

这是一条评论,指出如果仅找到一个隐含代码,编译器就会“急切地确认”该隐含代码,从而使deref强制(除其他外)发生。对于多个潜在候选人而言,这不会发生。

第二部分是,反强制强制只会在已知预期类型的​​站点上发生,而不会以推测的方式发生。请参阅参考资料中的强制位置。Impl选择和类型推断必须首先明确地找到MyAdd::add(&str)期望的值,以尝试将参数强制为&str

如果需要在这种情况下一种变通方法,使用表达式像&*b&b[..]b.as_str()用于第二个参数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.