如何为返回“impl Trait”的函数实现扩展特征模式?

问题描述

假设我有以下辅助方法,它使用向量加法将两个迭代器(或 IntoIterators)相加:

fn add<I1,I2,T>(a: I1,b: I2) -> impl Iterator<Item = T>
where
    I1: IntoIterator<Item = T>,I2: IntoIterator<Item = T>,T: std::ops::Add<Output = T>,{
    a.into_iter().zip(b).map(|(x,y)| x + y)
}

可以如下使用:

fn main() {
    let a = vec![1,2,3,4,5]; // Can use vectors
    let b = (6..=10).map(|x| x + 1); // Or other complicated iterators
    let sum_iter = add(a,b); // Can be used as an iterator if you wish to chain other functions
    let sum_vec = sum_iter.collect::<Vec<i32>>(); // Or you can collect it
    assert_eq!(sum_vec,vec![8,10,12,14,16]);
}

在实现这一点后,我决定我应该更改 API,以便我可以使用语法 a.add(b),这将使链接这些函数非常好。我尝试实现 extension trait 模式,但很快意识到这是不可能的,因为特征函数的返回类型不支持 impl Trait,而且我的 add 函数无法指定具体的由于传递给 |(x,y)| x + y

的闭包 .map() 而导致的类型

为了帮助澄清我的想法,这基本上是我尝试过的(显然这不能编译):

trait IntoIteratorMathExt
where
    Self: IntoIterator,{
    fn add<I>(self,other: I) -> impl Iterator<Item = Self::Item> 
    where
        I: IntoIterator<Item = Self::Item>,Self::Item: std::ops::Add<Output = Self::Item>;
}

impl<T: IntoIterator> IteratorMathExt for T {
    fn add<I>(self,other: I) -> impl Iterator<Item = Self::Item>
    where
        I: IntoIterator<Item = Self::Item>,Self::Item: std::ops::Add<Output = Self::Item>,{
        self.into_iter().zip(other).map(|(x,y)| x + y)
    }
}

fn main() {
    let a = vec![1,5];
    let b = (6..=10).map(|x| x + 1);
    let sum_iter = a.add(b); // <- Really nice Syntax!
    let sum_vec = sum_iter.collect::<Vec<i32>>();
    assert_eq!(sum_vec,16]);
}

不出所料,这会导致以下编译器错误

error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
  --> src\main.rs:34:34
   |
34 |     fn add<I>(self,other: I) -> impl Iterator<Item = Self::Item>
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
  --> src\main.rs:41:34
   |
41 |     fn add<I>(self,other: I) -> impl Iterator<Item = Self::Item>
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

我理解为什么特征方法不支持 impl Trait,但是,我的特征“实际上”只在一个地方实现,所以我希望可能存在一种解决方法

所以放弃这种方法,我想知道实现这种模式的“正确”方法是什么。

目标是能够使用迭代器 abIteratorIntoIterator,我不是 100% 是最好的),然后能够编写 a.add(b) 并让它返回一个 Iterator,具有与 Itema 相同的关联 b 类型。

有什么建议吗?

解决方法

由于你的闭包不捕获任何值,它实际上可以表示为一个函数指针类型,它允许你表达类型(它很丑,但它有效):

type MathExtType<A,B,T> = std::iter::Map<std::iter::Zip<A,B>,fn((T,T)) -> T>;

trait IntoIteratorMathExt
where
    Self: IntoIterator,{
    fn add<I>(self,other: I) -> MathExtType<Self::IntoIter,I::IntoIter,Self::Item>
    where
        I: IntoIterator<Item = Self::Item>,Self::Item: std::ops::Add<Output = Self::Item>;
}

impl<T: IntoIterator> IntoIteratorMathExt for T {
    fn add<I>(self,Self::Item: std::ops::Add<Output = Self::Item>,{
        self.into_iter().zip(other).map(|(x,y)| x + y)
    }
}

fn main() {
    let a = vec![1,2,3,4,5];
    let b = (6..=10).map(|x| x + 1);
    let sum_iter = a.add(b); // <- Really nice syntax!
    let sum_vec = sum_iter.collect::<Vec<i32>>();
    assert_eq!(sum_vec,vec![8,10,12,14,16]);
}
,

如果您愿意采用动态方式,事情会变得更容易。您只需返回一个 Box<dyn Iterator<Item=Self::Item>>,一切都会好起来的。