【问题标题】:Type handling in the trait object特征对象中的类型处理
【发布时间】:2021-12-01 19:23:56
【问题描述】:

我是 rust 新手,最近遇到了 trait 的问题

我有一个特征,它用作消息的来源,并作为 Box 特征对象存储在结构中。我简化了逻辑,代码看起来像这样。

#[derive(Debug)]
enum Message {
    MessageTypeA(i32),
    MessageTypeB(f32),
}

enum Config {
    ConfigTypeA,
    ConfigTypeB,
}

trait Source {
    fn next(&mut self) -> Message;
}

struct SourceA;

impl Source for SourceA {
    fn next(&mut self) -> Message {
        Message::MessageTypeA(1)
    }
}

struct SourceB;

impl Source for SourceB {
    fn next(&mut self) -> Message {
        Message::MessageTypeB(1.1)
    }
}

struct Test {
    source: Box<dyn Source>,
}

impl Test {
    fn new(config: Config) -> Self {
        Test {
            source: match config {
                Config::ConfigTypeA => Box::new(SourceA{}),
                Config::ConfigTypeB => Box::new(SourceB{}),
            }
        }
    }

    fn do_sth(&mut self) -> String {
        match self.source.next() {
            Message::MessageTypeA(a) => format!("a is {:?}", a),
            Message::MessageTypeB(b) => format!("b is {:?}", b),
        }
    }
    
    fn do_sth_else(&mut self, message: Message) -> String {
        match message {
            Message::MessageTypeA(a) => format!("a is {:?}", a),
            Message::MessageTypeB(b) => format!("b is {:?}", b),
        }
    }
}

不同类型的Source返回不同类型的MessageTest结构需要根据config创建对应的trait对象并在do_sth函数中调用next()。

所以你可以看到ConfigMessage两种枚举类型,我觉得这是一个奇怪的用法,但我不知道它有什么奇怪的地方。

我尝试使用 trait 关联类型,但是当我声明像 source: Box&lt;dyn Source&lt;Item=xxxx&gt;&gt; 这样的测试结构时需要指定关联类型,但在创建结构对象时我实际上并不知道确切的类型。

然后我尝试使用Generic类型,但是由于上层代码的需要,Test不能使用Generic。

所以请帮帮我,有没有更优雅或更质朴的解决方案来解决这种情况?

【问题讨论】:

  • 这段代码编译得很好,所以我不明白是什么问题。
  • @SvetlinZarev 我想问题是让枚举的变体跟踪我们对某个特征的实现者并不是很习惯。 trait 对象的重点应该是我们不必担心到底是谁在实现它。
  • @SvetlinZarev Lagerbaer 表达了我想说的话,在这段代码中我需要模式匹配来确定config 和模式匹配来确定message,而且至关重要的是,即使我有确定trait对象是SourceA类型,我还是需要模式匹配来确定它的next()返回MessageTypeA,即使使用了if let模式,还是会有很多无用的代码。
  • 对我来说,一个 trait 可以有一个返回枚举的函数是完全合理的,特别是如果“源”特征可以有不同的行为但需要遵守严格的“消息”格式。但是,如果 ConfigA 总是创建一个总是返回 MessageA 的 SourceA,对于 ConfigB 也是如此……那么混合多态样式确实很奇怪。鉴于你已经解释过你想要的,我可能会一直使用 trait 对象。

标签: rust trait-objects


【解决方案1】:

所以这里可能发生的情况是您误用了 trait 对象的概念。 trait 对象的整个想法是您要编写完全依赖于其接口的代码。一旦您发现自己正在检查 trait 对象的底层类型,这应该会引发一个危险信号。

当然,在您的示例中,您没有使用任何奇怪的运行时类型检查;相反,您正在通过枚举隐式检查类型。

不过,您认为这是有问题的。首先,当您尝试将另一个变体添加到您的源时,它会变得非常笨拙,因为现在您必须将它添加到您的消息和配置枚举中。其次,您必须将处理逻辑添加到使用 trait 对象的任何地方。最后,类型系统有点“撒谎”。在我看来,源 A 只会发送第一个变体的消息,而源 B 只会发送第二个变体的消息,这就是我们区分它们的方式。

那么这里的出路是什么?

首先,应该设计 trait 对象,以便在无需知道我们正在处理的实现的情况下使用它们。特征表示结构可以扮演的角色,使用特征对象的代码表示“我很高兴与任何可以扮演该角色的人一起工作”。

如果您的代码乐于与任何能够发挥该特性的人合作,则说明设计存在缺陷。

如果您一般了解您如何处理源返回的消息,那就太好了。

例如,对于程序的其余部分,源 A 只返回整数而源 B 只返回浮点数是否重要?如果是这样,那么重要的是什么? 是否可以被抽象为特征?

【讨论】:

    猜你喜欢
    • 2018-01-15
    • 2015-05-26
    • 2019-10-23
    • 1970-01-01
    • 2017-10-25
    • 2015-09-04
    • 1970-01-01
    • 2016-03-10
    • 2022-12-03
    相关资源
    最近更新 更多