与通用边界相冲突的特征,但并非没有潜在的“下游”实现

问题描述 投票:0回答:1

请原谅这里有大量的代码,但是最小的示例很难解释,我试图使其尽可能短。我正在尝试通过泛型类型实现具有泛型边界的泛型特征,这会导致编译器错误:

note: downstream crates may implement trait `Named<_>` for type `ExampleThing`

奇怪的是,这工作得很好,Named 不采用通用参数,所以我不明白为什么这会导致问题,以及如何修复它。

最小破坏示例

(游乐场:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6d0bdb30f1977318a9613b4629c5be80

代码的解释可能是最容易阅读注释的。在现实世界中,

Named
是被引入到
Talk<K>
make_talk<T, K>
的现有代码库中的特征。

use std::fmt::Debug;

/// This trait is to be implemented by downstream crates for objects that talk
/// In order to break things there is a spurious extra piece of information of
/// generic type K that is to be included in the message
pub trait Talks<K> {
    fn say(&self, extra: K);
}

/// A normal use case
pub struct ExampleThing(u64);

/// So we implement it manually like this
impl<K> Talks<K> for ExampleThing where K: Debug {
    fn say(&self, extra: K) {
        println!("I have the number {} [extra: {:?}]", self.0, extra);
    }
}

/// Then this can make any that implemnets Talk, talk.  We can always pass some
/// extra bit of info of type K to include in the message
pub fn make_talk<T, K>(thing: T, extra: K) where T: Talks<K> {
    thing.say(extra);
}

/// A large collection of objects are named, exposing a name method,
/// and they are capable of including the extra info 
pub trait Named<K> {
    fn name(&self, extra: K) -> String;
}

/// For example this guy
pub struct Person{ name: String }

impl<K> Named<K> for Person where K: Debug {
    fn name(&self, extra: K) -> String {
        format!("{:?} of {:?}" , self.name.clone(), extra)
    }
}

/// And then we can try make it so that all things that implement Name
/// introduce themselves automatically using Name
impl<K, T> Talks<K> for T where 
K: Debug,
T: Named<K>
{
    fn say(&self, extra: K) {
        println!("My name is {}", self.name(extra))
    }
}

fn main() { 
    let example = ExampleThing(42);
    make_talk(example, "Did it work?");
    let person = Person{name: "Bob".to_string()};
    make_talk(person, 1234);
}

产生此错误:

error[E0119]: conflicting implementations of trait `Talks<_>` for type `ExampleThing`
  --> src/main.rs:43:1
   |
14 |   impl<K> Talks<K> for ExampleThing where K: Debug {
   |   ------------------------------------------------ first implementation here
...
43 | / impl<K, T> Talks<K> for T where 
44 | | K: Debug,
45 | | T: Named<K>
   | |___________^ conflicting implementation for `ExampleThing`
   |
   = note: downstream crates may implement trait `Named<_>` for type `ExampleThing`

有效的微小改变

如果我们删除通用边界

Named<K>
并将其替换为
Named<u64>
那么一切都很好:

impl<K, T> Talks<K> for T where 
K: Debug,
T: Named<u64>
{
    fn say(&self, extra: K) {
        println!("My name is {}", self.name(999))
    }
}

输出:

I have the number 42 [extra: "Did it work?"]
My name is "Bob" of 999

问题

这是怎么回事?我该如何解决这个问题,以便可以将

extra
类型的
K
字段传递到总体实现中的
Named<K>
中。有关下游板条箱的错误消息令人困惑,因为它仅适用于特征边界包含 K 的情况。

generics rust traits
1个回答
0
投票

如果没有泛型参数,除了你之外,没有人可以为

Named
编写
ExampleThing
的实现。这样的 impl 会使两个 impls
impl Talks for ExampleThing
impl<T: Named> Talks for T
发生冲突。但是,由于只有您可以编写此 impl(因为没有其他板条箱拥有该类型或特征),因此编译器可以检查此类 impl 是否不存在并继续。

但是,由于

Named
具有泛型参数,不仅特征的所有者(
Named
)或类型的所有者(
ExampleThing
)可以编写这样的impl,而且泛型参数的所有者(
SomeType
impl Named<SomeType> for ExampleThing
)。因此,您无法再确保您的实现不会发生冲突,这就是编译器抱怨的原因。

您可以将泛型参数移至方法中来解决此问题。

© www.soinside.com 2019 - 2024. All rights reserved.