简体   繁体   English

实现具有 function 返回特征的特征

[英]Implement trait that has function which return traits

I am trying to implement a trait for a struct which in turn has functions that return traits.我正在尝试为一个结构实现一个特征,该结构又具有返回特征的函数。 I want this, because I do not want to bind the uer to a specific data structure.我想要这个,因为我不想将 uer 绑定到特定的数据结构。 However, trying to apply the compiler's correction suggestions, I fell deeper and deeper into a rabbit hole to no avail.然而,试图应用编译器的修正建议,我越陷越深,无济于事。 Here's a minimal example of what I'm trying to do:这是我正在尝试做的一个最小示例:

trait WordsFilter {
    fn starting_with(&self, chr: char) -> dyn Iterator<Item = String>;
}

struct WordsContainer {
    words: Vec<String>,
}

impl WordsFilter for WordsContainer {
    fn starting_with(&self, chr: char) -> dyn Iterator<Item = String>
    {
        self.words.iter().filter(|word| word.starts_with("a"))
    }
}

fn main() {}

Which results in:结果是:

error[E0277]: the size for values of type `(dyn Iterator<Item = String> + 'static)` cannot be known at compilation time
  --> .\traits.rs:10:40
   |
10 |     fn starting_with(&self, chr: char) -> dyn Iterator<Item = String>
   |                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `(dyn Iterator<Item = String> + 'static)`
   = note: the return type of a function must have a statically known size

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

I tried to apply the compiler's correction's step by step but they were just getting more.我尝试逐步应用编译器的更正,但它们越来越多。

TL;DR Use generics or associated types. TL;DR 使用 generics 或相关类型。

You cannot use bare dyn Trait types.您不能使用裸dyn Trait类型。 They are Unsized which means that compiler cannot know their size at the compile time.它们是Unsized的,这意味着编译器在编译时无法知道它们的大小。 Therefore you can only use them behind some reference (similarly to how you cannot use str or [T] and must use &str and &[T] ).因此,您只能在某些引用后面使用它们(类似于您不能使用str[T]而必须使用&str&[T]的方式)。 The easiest way is to use Box<dyn Trait> , like PitaJ earlier suggested.最简单的方法是使用Box<dyn Trait> ,就像PitaJ之前建议的那样。 This requires of course one heap allocation, but allows a very nice API.这当然需要一个堆分配,但允许非常好的 API。

In normal functions and methods you can however use impl Trait trick to make compiler infer returned type.但是,在普通函数和方法中,您可以使用impl Trait技巧使编译器推断返回类型。 This is used like this这是这样使用的

fn foo() -> impl Iterator<Item = ()> {
    todo!()
}

Calling this function would return some concrete object that implements given trait.调用此 function 将返回一些实现给定特征的具体 object。 This however is currently not possible in Trait definitions.然而,这在 Trait 定义中目前是不可能的。 There is active development and hopefully in the future we will be able to use it.有积极的发展,希望将来我们能够使用它。 However now it's not the case.然而现在并非如此。 So following code will not compile!所以下面的代码将无法编译!

// XXX: This doesn't currently work!
trait Foo {
    fn foo() -> impl Iterator<Item = ()>;
}

There is luckily a simple solution that wouldn't require boxing trait objects.幸运的是,有一个不需要装箱特征对象的简单解决方案。 You can define an associated type and require each implementator to specify a concrete type.您可以定义关联类型并要求每个实现者指定一个具体类型。 You can also require that this type must implement the Iterator trait.您还可以要求此类型必须实现Iterator特性。

trait WordsFilter {
    type Iter: Iterator<Item = String>;
    
    fn starting_with(&self, chr: char) -> Self::Iter;
}

This would make each implementation specify one concrete type of returned iterator.这将使每个实现指定一种具体类型的返回迭代器。 If you would like to be able to return multiple different iterators for a single type you can use generics.如果您希望能够为单一类型返回多个不同的迭代器,您可以使用 generics。

trait WordsFilter<I>
where
    I: Iterator<Item = String>
{
    fn starting_with(&self, chr: char) -> I;
}

There are several things that need to be fixed to get this to work.有几件事情需要解决才能让它发挥作用。

I'll first show the workng version then step through what had to change and why.我将首先展示工作版本,然后逐步说明必须更改的内容和原因。

trait WordsFilter {
    fn starting_with(&self, chr: char) -> Box<dyn Iterator<Item = String> + '_>;
}

struct WordsContainer {
    words: Vec<String>,
}

impl WordsFilter for WordsContainer {
    fn starting_with(&self, chr: char) -> Box<dyn Iterator<Item = String> + '_>
    {
        Box::new(self.words.iter().filter(|word| word.starts_with("a")).cloned())
    }
}

fn main() {}
  1. Boxing the iterator.装箱迭代器。 Rust doesn't allow you to return naked dyn types. Rust 不允许您返回裸dyn类型。 The compiler error tells you to Box it.编译器错误告诉您将其装箱。 So I did.所以我做了。

  2. Clone after filter.过滤后克隆。 Vec<X>::iter() returns an iterator with Item=&X so in this example we get Item=&String we want Item=String so we need to clone. Vec<X>::iter()返回一个带有Item=&X的迭代器,所以在这个例子中我们得到Item=&String我们想要Item=String所以我们需要克隆。

  3. Add a lifetime.加一生。 The problem was that without the lifetime checks I could write this...问题是如果没有生命周期检查我可以写这个......

     let words_container = WordsContainer{...}; let it = words_container.starting_with(); drop(words_container) it.next()

    but it.next() is stepping through the internal vector that is inside words_container - which no longer exists.但是it.next()正在逐步通过words_container内部的内部向量 - 它不再存在。

    Adding the '_ lifetime on the trait is saying the iterator is only valid for as long as the underlying container is.在 trait 上添加'_ lifetime 表示迭代器仅在底层容器有效时才有效。 So now the above code would generate a compile error.所以现在上面的代码会产生一个编译错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM