如果`&String`没有实现`Into<String>`,为什么这些实现会产生冲突?

5

我曾经问过一个有关为什么没有为 String 实现 From<&String> 的问题,链接如下:relevant question。现在我想创建自己的 trait,代码如下:

#[derive(Debug)]
struct MyStruct(String);

impl MyStruct {
    fn new<T>(t: T) -> MyStruct
    where
        T: MyIntoString,
    {
        MyStruct(t.my_into())
    }
}

trait MyIntoString {
    fn my_into(self) -> String;
}

impl<'a> MyIntoString for &'a String {
    fn my_into(self) -> String {
        self.clone()
    }
}

impl<I> MyIntoString for I
where
    I: Into<String>,
{
    fn my_into(self) -> String {
        self.into()
    }
}

fn main() {
    let s: String = "Hello world!".into();
    let st: MyStruct = MyStruct::new(&s);
    println!("{:?}", st);
}

编译器现在声称MyIntoString的两个实现是冲突的。这对我来说甚至更奇怪,因为我们已经在另一个问题中看到From<&String>没有为String实现,所以它没有找到&StringInto<String>实现。那么现在为什么会发生冲突呢?
此外,即使我打开了#![feature(specialization)],也检测到了相同的冲突。 错误信息 根据这个问题的一个答案,似乎错误信息没有引导我走上正确的轨道。
所以让我贴出错误信息来指责一下,因为它可能会在未来发生变化。
error[E0119]: conflicting implementations of trait `MyIntoString` for type `&std::string::String`:
  --> src/main.rs:23:1
   |
17 | / impl<'a> MyIntoString for &'a String {
18 | |     fn my_into(self) -> String {
19 | |         self.clone()
20 | |     }
21 | | }
   | |_- first implementation here
22 |   
23 | / impl<I> MyIntoString for I
24 | | where
25 | |     I: Into<String>,
26 | | {
...  |
29 | |     }
30 | | }
   | |_^ conflicting implementation for `&std::string::String`

对我来说,这是编译器声称存在一个真正的冲突,而不是潜在的冲突。

相关链接:https://dev59.com/XaPia4cB1Zd3GeqPw1ME - Boiethios
2个回答

3
这个错误是由于孤立规则引起的(见《The Book 第二版》第10.2章末尾的将trait实现于类型上)。这些规则避免了在您使用的crate发生微小更改(根据RFC#1105)时导致代码中断。如果标准库的作者决定为&String 实现 Into<String>,那么您的程序将包含一个与my_into存在冲突的定义并且会中断。添加trait实现应该是一项微小更改,不应该破坏您的程序。

本文提供了该规则的理据。

这本书建议使用newtype pattern来解决这个问题。

#[derive(Debug)]
struct MyStruct(String);

impl MyStruct {
    fn new<T>(t: T) -> MyStruct
    where
        T: Into<String>,
    {
        MyStruct(t.into())
    }
}

struct Wrapper<'a>(&'a String);

impl<'a> From<Wrapper<'a>> for String  {
    fn from(t: Wrapper<'a>) -> String {
        t.0.clone()
    }
}

fn main() {
    let s: String = "Hello world!".into();
    let st: MyStruct = MyStruct::new(Wrapper(&s));
    println!("{:?}", st);
}

操场链接


1
这段代码完成了与我的代码相同的功能,但是采用了完全不同的方式。这段代码在 Wrapper<'a> == &'a String 上实现了 Into<String>,而我的代码在 &'a String 上实现了 MyIntoString。两者都应该通过孤儿规则。 - Earth Engine
2
不建议使用 MyIntoString 的全局实现(即 impl<I: Into<String>> MyIntoString for I)与 impl MyIntoString for &String 相结合,因为这样会导致标准库的微小变化可能会破坏代码。使用新类型变体则不会出现此问题。 - red75prime
2
我不明白孤儿规则与此有何关联,因为 OP 正在实现自己的特性 MyIntoString - interjay
孤儿规则是为了允许负推理(“类型X未实现特征Y”),这在某些情况下是必要的。但OP最初的问题是,在这种情况下,孤儿规则不够强大。我们不能应用负推理来假设From<&String>未实现String(因为这样的实现可以通过非破坏性更改添加)。但我想你可以在不提及“孤儿规则”的情况下解释这个问题 :/ - Lukas Kalbertodt
@LukasKalbertodt,“孤儿规则”是一个相关的搜索词,如果有人想探索这个主题。 - red75prime
显示剩余3条评论

0

这段代码使用了专业化技术

#![feature(specialization)]

#[derive(Debug)]
struct MyStruct(String);

impl MyStruct {
    fn new<T>(t: T) -> MyStruct
    where
        T: MyIntoString,
    {
        MyStruct(t.my_into())
    }
}

trait MyIntoString {
    fn my_into(self) -> String;
}

impl<'a> MyIntoString for &'a String {
    fn my_into(self) -> String {
        self.clone()
    }
}

default impl<I> MyIntoString for I 
{
    default fn my_into(self) -> String {
        String::from("FOO")
    }
}

fn main() {
    let s: String = "Hello world!".into();
    let st: MyStruct = MyStruct::new(&s);
    println!("{:?}", st);
}

据我所知,你的版本无法进行特化,因为编译器无法确定哪个版本更加“专业”。

编辑

为什么前一个问题的代码无法编译? 因为当你将&s传递给new时。

    let st: MyStruct = MyStruct::new(&s);

编译器将&s视为&String,并从std中的代码中看到:
impl<T, U> Into<U> for T where U: From<T>

impl<T, U> Into<U> for T where U: From<T> {
    fn into(self) -> U {
        U::from(self)
    }
}

// From (and thus Into) is reflexive
#[stable(feature = "rust1", since = "1.0.0")]
impl<T> From<T> for T {
    fn from(t: T) -> T { t }
}

由于From<&String>未针对String实现,因此会显示编译错误。因此,您必须明确指出&s是可以构造String的类型,即&str


    let st: MyStruct = MyStruct::new(&s as &str);

现在编译器可以看到这个了

impl<'a> From<&'a str> for String

编译器现在声称 MyIntoString 的两个实现存在冲突。这对我来说甚至更奇怪,因为我们已经在另一个问题中看到 From<&String> 没有为 String 实现,因此它没有找到 &String 的 Into 实现。
错误之所以发生只是因为编译器无法确定哪个实现更“专业化”,即使使用了专业化。

由于自动解引用。 - Oleh Devua
请在您的答案中解释,并提供解决方法。 - Earth Engine
当你尝试在其实例上调用into时,&String可以被视为实现了Into<String>的类型,因为存在“自动解引用”。但是,在我的代码中,由于我为&String实现了这个特质,它被视为MyIntoString。在你的代码中,由于误用了“特化”,它无法正常工作。 - Oleh Devua
如果你读了我的另一个问题 https://dev59.com/9qPia4cB1Zd3GeqPy3QS,你就会知道我实际上不能在 &String 上调用 into。所以我不明白为什么这里会自动解引用,但那里却不会。 - Earth Engine
所以,如果&String没有找到实现,为什么它实现了Into<String> - 它没有。这在std中没有实现。为什么呢?我认为是因为你可以通过取消引用将&String转换为String,这是最自然的方法。 - Oleh Devua
显示剩余4条评论

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