我为另一个trait实现了一个trait,但无法调用两个trait的方法。

62

我有一个被称为Sleep的特点:

pub trait Sleep {
    fn sleep(&self);
}

我可以为每个结构提供不同的睡眠实现,但事实证明大多数人以非常少的方式睡觉。 你可以在床上睡觉:

pub trait HasBed {
    fn sleep_in_bed(&self);
    fn jump_on_bed(&self);
}

impl Sleep for HasBed {
    fn sleep(&self) {
        self.sleep_in_bed()
    }
}

如果您在露营,您可以睡在帐篷里:

pub trait HasTent {
    fn sleep_in_tent(&self);
    fn hide_in_tent(&self);
}

impl Sleep for HasTent {
    fn sleep(&self) {
        self.sleep_in_tent()
    }
}

有一些奇怪的情况。我有一个朋友可以站在墙边睡觉,但是大多数人大部分时间都属于某些简单的情况。

我们定义了一些结构体并让它们睡觉:

struct Jim;

impl HasBed for Jim {
    fn sleep_in_bed(&self) {}
    fn jump_on_bed(&self) {}
}

struct Jane;

impl HasTent for Jane {
    fn sleep_in_tent(&self) {}
    fn hide_in_tent(&self) {}
}

fn main() {
    use Sleep;
    let jim = Jim;
    jim.sleep();

    let jane = Jane;
    jane.sleep();
}

糟糕!编译错误:

error[E0599]: no method named `sleep` found for type `Jim` in the current scope
  --> src/main.rs:44:9
   |
27 | struct Jim;
   | ----------- method `sleep` not found for this
...
44 |     jim.sleep();
   |         ^^^^^
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `sleep`, perhaps you need to implement it:
           candidate #1: `Sleep`

error[E0599]: no method named `sleep` found for type `Jane` in the current scope
  --> src/main.rs:47:10
   |
34 | struct Jane;
   | ------------ method `sleep` not found for this
...
47 |     jane.sleep();
   |          ^^^^^
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `sleep`, perhaps you need to implement it:
           candidate #1: `Sleep`

这个编译错误很奇怪,因为如果有一个特质实现另一个特质时出了问题,我希望在那时就能听到错误信息,而不是在程序底部尝试使用结果时才听到。

在这个例子中,只有两个结构体和两种睡眠方式,但是在一般情况下会有许多结构体和几种睡眠方式(但并不像结构体那么多)。

Bed 主要是 Sleep 的实现,但在一般情况下,Bed 有许多用途,并且可以实现许多东西。

唯一明显的方法是将 impl Sleep for... 转换为宏,使结构体自己使用,但这似乎很不好。


2
参见:https://github.com/rust-lang/rfcs/issues/1024。也许OP实际上是那个RFC的作者? - xji
1
这是我读过的最有趣的代码示例。 - chantey
3个回答

57

您需要为实现第一特征的对象实现第二个特征:

impl<T> Sleep for T
where
    T: HasBed,
{
    fn sleep(&self) {
        self.sleep_in_bed()
    }
}

之前,您正在为特质类型实现Sleep,更好地表达为dyn HasBed。有关更多详细信息,请参见类型中的“dyn”是什么意思?

然而,一旦添加第二个毯子实现,这将会出现问题:

impl<T> Sleep for T
where
    T: HasTent,
{
    fn sleep(&self) {
        self.sleep_in_tent()
    }
}

随着

error[E0119]: conflicting implementations of trait `Sleep`:
  --> src/main.rs:24:1
   |
10 | / impl<T> Sleep for T
11 | | where
12 | |     T: HasBed,
13 | | {
...  |
16 | |     }
17 | | }
   | |_- first implementation here
...
24 | / impl<T> Sleep for T
25 | | where
26 | |     T: HasTent,
27 | | {
...  |
30 | |     }
31 | | }
   | |_^ conflicting implementation

有些东西可能同时实现了HasBedHasTent。如果出现这种情况,那么代码将会变得模糊不清。解决这个问题的方法是专业化,但目前还没有稳定的实现。

如何达成你的目标?我认为你已经提出了当前最佳解决方案 - 编写宏。你也可以编写自己的派生宏。宏并不那么糟糕,但编写起来可能有些复杂。

另一件事,可能完全基于您为示例选择的名称,就是将结构嵌入其他结构中,可选择将其公开。由于你对Sleep的实现基本上只取决于床/帐篷,因此通过这种方式不会丢失任何功能。当然,有些人可能认为这打破了封装性。你可以再次创建宏来实现某种委托。

trait Sleep {
    fn sleep(&self);
}

struct Bed;
impl Bed {
    fn jump(&self) {}
}
impl Sleep for Bed {
    fn sleep(&self) {}
}

struct Tent;
impl Tent {
    fn hide(&self) {}
}
impl Sleep for Tent {
    fn sleep(&self) {}
}

struct Jim {
    bed: Bed,
}
struct Jane {
    tent: Tent,
}

fn main() {
    let jim = Jim { bed: Bed };
    jim.bed.sleep();
}

理论上来说,这个问题不是可以通过像 use HasBed; 这样的代码在调用方进行消歧吗? - Drew
我不知道编译器的详细信息,无法做出有根据的猜测,以确定启用它的复杂程度。 - Shepmaster
1
@Drew,问题不在于实现这样的东西,而是找到一个从语言设计角度来看可取的好设计。并且激励它(它将是解决可以用不同方法解决的问题的重要补充)。 - user395760
1
在现有语言的限制下,这里最好的方法是什么?这是一个很好的“你不能用这种方式做”的答案,但它并没有真正解决问题的动机。预处理器宏?还有其他应该考虑的设计模式吗? - Drew
1
@Drew,我没有什么好的建议给你,但我更新了一些我所拥有的建议。既然你已经解决了关于特征实现特征的高级问题,你还可以考虑提出一个更详细的关于你真实用例的低级问题。这可能会让比我更聪明的人能够给你一个答案!^_^ - Shepmaster

29
我们可以在这里使用关联项。
pub trait Sleep: Sized {
    type Env: SleepEnv;

    fn sleep(&self, env: &Self::Env) {
        env.do_sleep(self);
    }

    fn get_name(&self) -> &'static str;
}

pub trait SleepEnv {
    fn do_sleep<T: Sleep>(&self, &T);
}

接下来,我们实现了两种不同的睡眠环境。

struct Bed;
struct Tent;

impl SleepEnv for Bed {
    fn do_sleep<T: Sleep>(&self, person: &T) {
        println!("{} is sleeping in bed", person.get_name());
    }
}

impl SleepEnv for Tent {
    fn do_sleep<T: Sleep>(&self, person: &T) {
        println!("{} is sleeping in tent", person.get_name());
    }
}

最后一部分是它们的具体实现。
struct Jim;
struct Jane;

impl Sleep for Jim {
    type Env = Bed;
    fn get_name(&self) -> &'static str {
        "Jim"
    }
}

impl Sleep for Jane {
    type Env = Tent;
    fn get_name(&self) -> &'static str {
        "Jane"
    }
}

测试代码:

fn main() {
    let bed = Bed;
    let tent = Tent;

    let jim = Jim;
    let jane = Jane;
    jim.sleep(&bed);
    jane.sleep(&tent);
}

1
手头的问题似乎是为n种不同的床型实现m个特征。
其他解决方案建议使用泛型和关联项。我的设计涉及使用枚举和匹配语句。
我想象客户端代码会像这样:
let jim = Person;
jim.sleep(BedType::Tent);
jim.hide(BedType::Bed);

我会创建一个 `Person` 结构体,这样我就可以像你的例子中一样创建 `jim` 和 `jane`:
struct Person;

该语句“大多数人以非常有限的方式睡觉”暗示了一个BedType枚举:
enum BedType {
    Tent,
    Bed,
    Futon,
}

然后,我会创建一个名为“Sleep”的特质,并将其实现到所有的“BedType”中:
trait Sleep {
    fn sleep(&self);
}

impl Sleep for BedType {
    fn sleep(&self) {
        match self {
            BedType::Tent => { /***/ }
            BedType::Bed => { /***/ }
            BedType::Futon => { /***/ }
        }
    }
}

如果除了睡觉之外还有其他可以对床进行的操作,那么我们可以分别创建特征,并为每个枚举变量实现。这里是一个用于 Hide 的例子:
trait Hide {
  fn hide(&self);
}

impl Hide for BedType {
  fn hide(&self) {
    match self {
      BedType::Bed => { /***/ }
      _ => { /***/ }
    }
  }
}

最后,为 Person 实现不同的特征:
impl Sleep for Person {
  fn sleep(&self) { /***/ }
}

impl Hide for Person {
  fn hide(&self) { /***/ }
}

尽管 `Person` 和 `BedType` 共享相同的特征,但我认为我们仍然可以为 `Person`(如果需要)实现封装。
请注意,理想情况下,我们希望每个 `BedType` 枚举变量都有不同的实现,但这仍然是一个正在进行中的工作https://github.com/rust-lang/rfcs/pull/2593#issuecomment-823429973

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