如何从trait方法返回std::iter::Map?

How to return std::iter::Map from trait method?

提问人:ynn 提问时间:6/17/2023 最后编辑:ynn 更新时间:11/17/2023 访问量:104

问:

我有一个元组向量:

let l = vec![(0, 1), (2, 3)];

我想得到哪个提取每个元组的第一个元素。天真地,这是有效的:std::iter::Map

let m: std::iter::Map<_, _> = l.iter().map(|e| e.0);

现在我想扩展一下,使这个伪代码工作:Iterator

let m: std::iter::Map<_, _> = l.iter().tuple_first();

尝试 1

我试图返回:impl Iterator

trait TupleExtractor<'a, T1: 'a, T2: 'a>: Iterator<Item = &'a (T1, T2)> {
    fn tuple_first(self) -> impl Iterator<Item = &'a T1>;
}

impl<'a, T1: 'a + Copy, T2: 'a, I> TupleExtractor<'a, T1, T2> for I
where
    I: Iterator<Item = &'a (T1, T2)>,
{
    fn tuple_first(self) -> impl Iterator<Item = &'a T1> {
        self.map(|e| e.0)
    }
}

此代码无法编译,因为

`impl Trait` only allowed in function and inherent method return types, not in trait method return types

尝试 2

我试图显式返回而不是.但是,出于同样的原因,此代码也不会编译。std::iter::Mapimpl Iterator

trait TupleExtractor<'a, T1: 'a, T2: 'a>: Iterator<Item = &'a (T1, T2)> {
    fn tuple_first(
        self,
    ) -> std::iter::Map<impl Iterator<Item = &'a (T1, T2)>, impl FnMut(&'a (T1, T2)) -> T1>;
}

impl<'a, T1: 'a + Copy, T2: 'a, I> TupleExtractor<'a, T1, T2> for I
where
    I: Iterator<Item = &'a (T1, T2)>,
{
    fn tuple_first(self) -> std::iter::Map<I, impl FnMut(&'a (T1, T2)) -> T1> {
        self.map(|e| e.0)
    }
}

尝试 3

由于尝试 2 不起作用的原因是我无法指定 的类型参数,我尝试将它们设置为类型参数:std::iter::Map

trait TupleExtractor<'a, T1: 'a, T2: 'a, A, B>: Iterator<Item = &'a (T1, T2)> {
    fn tuple_first(self) -> std::iter::Map<A, B>;
}

impl<'a, T1: 'a + Copy, T2: 'a, I, F> TupleExtractor<'a, T1, T2, I, F> for I
where
    I: Iterator<Item = &'a (T1, T2)>,
    F: FnMut(&'a (T1, T2)) -> T1,
{
    fn tuple_first(self) -> std::iter::Map<I, F> {
        self.map(|e: &'a (T1, T2)| e.0)
    }
}

此代码不编译,因为闭包的类型不是:|e: &'a (T1, T2)| e.0F

= note: expected type parameter `F`
        found closure `[closure@src/main.rs:38:18: 38:35]`
= help: every closure has a distinct type and so could not always match the caller-chosen type of parameter `F`

如何从特质方法返回?我不想使用 .std::iter::MapBox

评论

2赞 Filipe Rodrigues 6/17/2023
您的第一次尝试将在未来的 rust 版本中起作用。在那之前,我相信除了动态调度之外别无他法。除非存在类型在允许返回特征之前得到稳定,在这种情况下,你会使用它们。impl Trait

答:

3赞 isaactfa 6/17/2023 #1

我能想到的解决这个问题的唯一方法是禁止使用捕获闭包(因为它们的类型还不能在特征中命名)以支持函数指针。然后,您可以使用关联类型定义您的特征,而不是返回位置 impl 特征。这显然限制了可能的实现,但它仍然允许您展示的用例:

trait TupleExtractor<'a, T1: 'a, T2: 'a>: Iterator<Item = &'a (T1, T2)> {
    type Mapper: Iterator<Item = T1>;
    fn tuple_first(
        self,
    ) -> Self::Mapper;
    //   ^^^^^^^^^^^^ replace return position impl trait with associated type
}

impl<'a, T1: 'a + Copy, T2: 'a, I> TupleExtractor<'a, T1, T2> for I
where
    I: Iterator<Item = &'a (T1, T2)>,
{
    type Mapper = std::iter::Map<I, fn(&'a (T1, T2)) -> T1>;
    //                              ^^^^^^^^^^^^^^^^^^^^^^ we can actually name this type
    fn tuple_first(self) -> Self::Mapper {
        // implementation stays the same because this is just an anonymous function
        // not a closure
        self.map(|e| e.0)
    }
}

#[test]
fn test_tuple_first() {
    let v = vec![(1, 'a'), (2, 'b'), (3, 'b')];
    assert!(v.iter().tuple_first().eq([1, 2, 3].into_iter()))
}

如果该类型需要针对其他潜在的特征方法更具可扩展性,则可以将其设置为泛型,而不是它生成的项类型:Mapper

trait TupleExtractor<'a, T1: 'a, T2: 'a>: Iterator<Item = &'a (T1, T2)> {
    type Mapper<T>: Iterator<Item = T>
    where
        T: 'a;
    
    fn tuple_first(
        self,
    ) -> Self::Mapper<T1>;
    
    fn tuple_second(
        self,
    ) -> Self::Mapper<T2>;
}

impl<'a, T1: 'a + Copy, T2: 'a + Copy, I> TupleExtractor<'a, T1, T2> for I
where
    I: Iterator<Item = &'a (T1, T2)>,
{
    type Mapper<T> = std::iter::Map<I, fn(&'a (T1, T2)) -> T> where T: 'a;
    
    fn tuple_first(self) -> Self::Mapper<T1> {
        self.map(|e| e.0)
    }
    
    fn tuple_second(self) -> Self::Mapper<T2> {
        self.map(|e| e.1)
    }
}

#[test]
fn test_tuple_first() {
    let v = vec![(1, 'a'), (2, 'b'), (3, 'b')];
    assert!(v.iter().tuple_first().eq([1, 2, 3].into_iter()))
}

#[test]
fn test_tuple_second() {
    let v = vec![(1, 'a'), (2, 'b'), (3, 'b')];
    assert!(v.iter().tuple_second().eq(['a', 'b', 'b'].into_iter()))
}

评论

0赞 ynn 6/17/2023
谢谢你的建议。以你的答案为提示,我只是想出了一个不同(但非常相似)的实现。这个(游乐场)怎么样?不需要关联类型。
0赞 isaactfa 6/17/2023
您需要生成一个永久链接才能共享一个 Playground(尝试右上角的按钮)。share
0赞 ynn 6/17/2023
啊,对不起。更新了原始评论。
0赞 isaactfa 6/17/2023
这不会发生在关联类型中。
0赞 ynn 6/17/2023
目前,我只是部分理解您的评论。在你给出的操场中,我认为第二个特征实现本身是无效的(即使第一个被注释掉了)。你能给我一个完整的例子吗?顺便说一句,我的实现的一个优点是可以很容易地将特征扩展到定义 、 等,而无需添加关联类型 、 等。tuple_second()tuple_third()Mapper2Mapper3
1赞 drewtato 11/17/2023 #2

Rust 1.75 版本中,您将能够编写为返回类型。这将使您的尝试 1(当修复正确性时)工作不变。唯一的其他效果是您需要从中删除该类型,因为它现在是不透明的。impl Traitm

我简化了你的特征,使内容更易于阅读,但这种技术也适用于所有泛型的原始特征。特别是,您可能希望在特征中保留泛型。T1

trait TupleExtractor {
    type TupleItem;
    fn tuple_first(self) -> impl Iterator<Item = Self::TupleItem>;
}

impl<'a, T1, T2, I> TupleExtractor for I
where
    I: Iterator<Item = &'a (T1, T2)>,
    T1: Copy + 'a,
    T2: 'a,
{
    type TupleItem = T1;
    fn tuple_first(self) -> impl Iterator<Item = Self::TupleItem> {
        self.map(|e| e.0)
    }
}

1.75 将于 2023 年底推出。一般概念称为“返回位置 impl trait in trait”(RPITIT),专门用于将异步特征脱糖为 。你今天就可以使用 Rust 的测试版或夜间版本来尝试这个。impl Future

评论

1赞 ynn 11/18/2023
谢谢。标记为已接受,因为 Rust 1.75 将在几周内发布。此外,我可以确认“这使得您的尝试 1(当修复正确性时)工作不变”的说法是正确的:游乐场