【问题标题】:How do I implement a trait for a struct when the trait requires more state than is contained in the struct?当特征需要的状态多于结构中包含的状态时,如何为结构实现特征?
【发布时间】:2019-02-25 16:14:49
【问题描述】:

当特征需要的状态多于结构中包含的状态时,如何为结构实现特征?例如,我将如何为下面显示的 Human 结构实现 Employee 特征?

struct Human {
    name: &str,
}

trait Employee {
    fn id(&self) -> i32;
    fn name(&self) -> &str;
}

impl Employee for Human {
    fn id(&self) -> i32 {
        // From where do I get the ID?
    }
    fn name(&self) -> &str {
        self.name
    }
}

我没有看到任何方法可以将其他状态添加到 impl 或特征中。

是否只有创建一个新的 HumanToEmployeeAdapter 结构来保存缺失的信息,然后为新结构实现 Employee 特征?

附:我的背景是 C#。以下是我用那种语言处理它的方法:

class Human
{
    public string Name { get; }

    public Human(string name) { Name = name; }
}

interface IEmployee
{
    int Id { get; }
    string Name { get; }
}

class HumanToEmployeeAdapter : IEmployee
{
    readonly Human _human;

    public int Id { get; }
    public string Name => _human.Name;

    public HumanToEmployeeAdapter(
        Human human,
        int id)
    {
        _human = human;
        Id = id;
    }
}

您会注意到这是“创建一个新的HumanToEmployeeAdapter struct”路径。那么,这就是 Rustaceans 解决这个问题的方式吗?

【问题讨论】:

  • 你的背景是C#,所以如果你用GetId方法实现了一个C#接口,在这种情况下你会如何提供呢?
  • “当 trait 需要的状态多于结构中包含的状态时,我如何为结构实现 trait” => 你不能。这不是特征的用途。您需要创建另一个结构(可能是包装器类型)。
  • 提示:rust 不会归类为 OO。
  • 评论不是进入这个论点的最佳位置,但我们中的许多人(包括我自己)将 Rust 视为一种 OO 语言。它没有继承,但这不是 OO 所必需的。

标签: oop struct rust traits composition


【解决方案1】:

您几乎可以完全翻译您的 C# 代码,如下所示:

struct Human<'a> {
    name: &'a str,
}

trait Employee {
    fn id(&self) -> i32;
    fn name(&self) -> &str;
}

struct HumanToEmployeeAdapter<'a> {
    human: &'a Human<'a>,
    id: i32,
}

impl<'a> HumanToEmployeeAdapter<'a> {
    fn new(id: i32, human: &'a Human<'a>) -> Self {
        HumanToEmployeeAdapter { id, human }
    }
}

impl<'a> Employee for HumanToEmployeeAdapter<'a> {
    fn id(&self) -> i32 {
        self.id
    }

    fn name(&self) -> &str {
        self.human.name
    }
}

如果您的Human 类型可以设为Copy(其行为类似于C# value type),那么您可以通过让HumanToEmployeeAdapter 拥有Human 来简化问题,这意味着您不必担心关于引用的生命周期:

#[derive(Copy, Clone)]
struct Human<'a> {
    name: &'a str,
}

trait Employee {
    fn id(&self) -> i32;
    fn name(&self) -> &str;
}

struct HumanToEmployeeAdapter<'a> {
    human: Human<'a>,
    id: i32,
}

impl<'a> HumanToEmployeeAdapter<'a> {
    fn new(id: i32, human: Human<'a>) -> Self {
        HumanToEmployeeAdapter { id, human }
    }
}

impl<'a> Employee for HumanToEmployeeAdapter<'a> {
    fn id(&self) -> i32 {
        self.id
    }

    fn name(&self) -> &str {
        self.human.name
    }
}

请注意,您仍然需要跟踪name 的生命周期,因为&amp;str 是一个参考。如果你把它变成了一个拥有的String,那么你就不需要Human的生命周期参数,但是Human不能是Copy。这是因为 Strings 不能安全地复制到内存中,因为它们的 Drop impl(类似于 C# finalizer),如果 Rust 允许你这样做,这将导致双重释放 - 这就是它不这样做的原因t.

【讨论】:

  • human: &amp;'a Human&lt;'a&gt; — 这是 Rust 允许/要求您做出比 C# 之类的语言更多决定的地方。对于 this 特定示例,存储对 Human 的引用是多余的。您可以完全内联存储它。
  • @Shepmaster 如果您已经使用了Human,并且需要将其传递给需要Employee 的函数,那么您可能不想移动原件。作为一般情况,我认为这“更好”并且更接近您在 C# 中的期望。
  • 当然,通常使用引用是最兼容的解决方案,但Human 应该实现Copy,从而使“原始”点没有实际意义。您也可以直接从Human 中取出name 并将其存储在适配器中,因为&amp;strCopy
  • @Shepmaster FWIW C#确实让您可以选择内联存储它。
  • 另外,如果 OP 的真正 Human 结构只是一个 &amp;str 我本来希望他们只是说 &amp;str 而不是将结构带入他们的 MVCE。与现实世界的问题相比,这显然非常简单。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-17
  • 1970-01-01
相关资源
最近更新 更多