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

3
我有一个元组的向量。
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::Map`而不是`impl 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)
    }
}

第三次尝试

由于第二次尝试失败的原因是我无法指定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.0 的类型不是 F
= 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`

如何从trait方法返回`std::iter::Map`?我不想使用`Box`。

2
你的第一次尝试将适用于未来的 Rust 版本。在此之前,我相信除了动态分派之外没有其他方式。除非存在类型在允许在特性中返回 impl Trait 之前得到稳定,那种情况下你会使用它们。 - undefined
2个回答

3
我能想到的唯一解决方法是禁止使用捕获闭包(因为它们的类型在特质中无法命名(尚未)),而改用函数指针。然后,您可以使用关联类型来定义特质,而不是返回位置的实现特质。这显然会限制可能的实现,但仍然允许您展示的用例。
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()))
}

谢谢你的建议。在你的答案作为提示的基础上,我想到了一个不同但非常相似的实现。这个怎么样(playground)?不需要关联类型。 - undefined
你需要生成一个永久链接来分享一个游乐场(尝试点击右上角的“分享”按钮)。 - undefined
无法使用关联类型来实现这个。 - undefined
谢谢。在这种情况下,两个特质实现发生了冲突。那么是否有可能同时满足以下条件:(1)定义了两个不冲突的特质实现,以及(2)使用特质方法会产生歧义?由于我讨论的目的是为了理解Rust本身,偏离std::iter::Map示例也是可以的。 - undefined
让我们在聊天中继续这个讨论。 - undefined
显示剩余9条评论

1
Rust版本1.75中,你将能够将impl Trait作为返回类型。这使得你的Attempt 1(在修正正确性后)保持不变。唯一的其他影响是你需要从m中移除类型,因为它现在是不透明的。
我简化了你的trait,以使阅读更容易,但这种技术也适用于你原始的带有所有泛型的trait。特别是,你可能希望保留trait中的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年底发布。这个被称为“trait中返回位置实现特质”(RPITIT)的总体概念是专门为将异步特质转换为“impl Future”而添加的。您可以通过使用Rust的beta版或夜间版本来尝试这个功能。

1
谢谢。标记为已接受,因为Rust 1.75将在几周内发布。此外,我可以确认声明“这使得您的Attempt 1(在修复正确性后)可以无需更改地工作”是正确的:playground - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接