Rust: “decombine”特质?

huangapple go评论71阅读模式
英文:

Rust: "decombine" traits?

问题

假设我有一些特性:

trait Device {}
trait Named {}
trait Report {}

我有一些结构体,其中一些实现了这些特性的某个子集,然后我有一些逻辑期望一个实现了所有这些特性的类型,例如,我想要一个包含具有名称并能够生成报告的设备集合。为了减少键盘输入,我创建了一个组合特性:

pub trait ReportNamedDevice: Named + Device {}
impl<T: Named + Device> ReportNamedDevice for T {}

(暂时省略Report,直到我能使现有的工作)

然后我在一个结构体中使用这些特性的集合:

pub struct Room<'a> {
    pub name: String,
    pub devices: Vec<&'a dyn ReportNamedDevice>,
}

现在,房间本身是Named的,我希望能够将其添加到House中。我希望所有包含Named事物的集合都包含具有唯一名称的事物,因此我编写了以下筛选函数:

fn name_is_in_named_vector(name: &String, haystack: &Vec<&dyn Named>) -> bool {
    haystack
        .iter()
        .map(|x| x.name().clone())
        .collect::<Vec<String>>()
        .contains(name)
}

但是当我尝试在我的Room实现中使用它时,我会得到一个“类型不匹配”的错误,因为编译器不将ReportNamedDevice视为Named

impl<'a> Room<'a> {
    pub fn add_device(&mut self, device: &'a dyn ReportNamedDevice) {
        if !name_is_in_named_vector(device.name(), &self.devices) {
            self.devices.push(device)
        }
    }
}

我知道我可以替换筛选函数中的haystack类型,但我希望能够重用它来筛选房间。

有没有办法使这个工作?

英文:

Suppose, I have a number of traits:

trait Device {}
trait Named {}
trait Report {}

I have a number of structs, some of which implement some subset of these traits, and then I have some logic that expects a type that implements all of them, say, I want to have a collection of devices that have names and can produce reports. To reduce the typing (on the keyboard), I create a combined trait:

pub trait ReportNamedDevice: Named + Device {}
impl&lt;T: Named + Device&gt; ReportNamedDevice for T {}

(Report is omitted for now until I can make the existing ones work)

And then I use a collection of those in a struct:

pub struct Room&lt;&#39;a&gt; {
    pub name: String,
    pub devices: Vec&lt;&amp;&#39;a dyn ReportNamedDevice&gt;,
}

Now, the room itself is Named, and I want to be able to add it to a House. And I want all of my collections of Named things to contain things with unique names, so I write the following filter function:

fn name_is_in_named_vector(name: &amp;String, haystack: &amp;Vec&lt;&amp;dyn Named&gt;) -&gt; bool {
    haystack
        .iter()
        .map(|x| x.name().clone())
        .collect::&lt;Vec&lt;String&gt;&gt;()
        .contains(name)
}

But when I try to use it in my Room impl, I get a mismatched types error, because the compiler doesn't treat ReportNamedDevice as Named:

impl&lt;&#39;a&gt; Room&lt;&#39;a&gt; {
    pub fn add_device(&amp;mut self, device: &amp;&#39;a dyn ReportNamedDevice) {
        if !name_is_in_named_vector(device.name(), &amp;self.devices) {
            self.devices.push(device)
        }
    }
}

I know I could replace the haystack type in the filter function, but I would like to be able to reuse it for filtering rooms as well.

Is there a way I can make this work?

答案1

得分: 4

是的,有一种方法,只需使用泛型而不是特质对象,这可以工作,因为&dyn ReportNamedDevice虽然与&dyn Named不同,但确实实现了Named

fn name_is_in_named_vector<T: ?Sized + Named>(name: &str, haystack: &[&T]) -> bool {
    haystack
        .iter()
        .find(|x| x.name() == name)
        .is_some()
}

Playground

注意:我已经应用了isaactfa在评论中提到的关于find的提示以及为什么不建议接受引用&String、&Vec或&Box作为函数参数?的建议。

英文:

Yes there is a way, just use generics instead of trait objects, that works becaues while a &amp;dyn ReportNamedDevice isn't the same as a &amp;dyn Named it does implement Named:

fn name_is_in_named_vector&lt;T: ?Sized + Named&gt;(name: &amp;str, haystack: &amp;[&amp;T]) -&gt; bool {
    haystack
        .iter()
        .find(|x| x.name() == name)
        .is_some()
}

Playground

<sub><sub>Note: I've applied isaactfa's hint about find in the comments and the tips of Why is it discouraged to accept a reference &String, &Vec, or &Box as a function argument?</sub></sub>

huangapple
  • 本文由 发表于 2023年6月28日 23:51:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76574820.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定