我有一个返回我认为应该可迭代的东西的函数,但到目前为止我一直在收集它并将其作为向量返回。
pub fn grid_coords() -> Vec<(i32, i32)> {
(0..SIZE).flat_map(|y| (0..SIZE).map(move |x| (x, y))).collect()
}
我假设这将在返回之前迭代,然后调用它的代码将不得不再次迭代。
我尝试删除.collect()
并让编译器引导我并最终得到类似FlatMap<Range<i32>, Map<Range<i32>, Fn>, Fn>
的内容,是否有一种不那么难看的类型我可以使用?
答案 0 :(得分:2)
在当前(稳定)Rust中,返回&#34;概念性&#34;的最简单方法迭代器是返回一个实现Iterator
的盒装特征对象:
pub fn grid_coords() -> Box<Iterator<Item=(i32, i32)>> {
let real_iter = (0..SIZE).flat_map(|y| (0..SIZE).map(move |x| (x, y)));
Box::new(real_iter)
}
有效地使用具体迭代器对象的框erases the type,并且调用者不需要知道它使用flat_map
实现的事实。如果稍后切换grid_coords
以使用不同的迭代原语,例如yield
一旦稳定,则该功能不会改变签名,并且保证不会破坏其调用者。你甚至可以更进一步,完全隐藏来自调用者的盒子:
pub struct GridCoordIter {
inner: Box<Iterator<Item=(i32, i32)>>
}
impl Iterator for GridCoordIter {
type Item = (i32, i32);
fn next(&mut self) -> Option<(i32, i32)> {
self.inner.next()
}
}
pub fn grid_coords() -> GridCoordIter {
let real_iter = (0..SIZE).flat_map(|y| (0..SIZE).map(move |x| (x, y)));
GridCoordIter { inner: Box::new(real_iter) }
}
盒装返回的缺点是每次调用grid_coords
需要一个小的堆分配,并且每次调用next()
都会通过vtable样式的间接调用,而这两个调用都没有优化远离当前的锈病。这在实践中是否存在问题取决于您的使用情况,但如果grid_coords()
是一个可能被称为数百万次的非常基本的功能,那么它就不会被解雇。
可以通过使用GridCoordIter
的不同实现来消除分配和间接。很遗憾,无法将inner
字段设为FlatMap
值并保留grid_coords
的当前实现。内部FlatMap
需要引用函数的类型,grid_coords
使用的闭包类型是匿名的。这可以通过将闭包重写为可调用的结构来解决,但此时首先使用flat_map
和map
的便利性将丢失,并且更容易实现GridCoordIter::next
具有所需的逻辑:
pub struct GridCoordIter {
i: i32,
j: i32,
}
impl Iterator for GridCoordIter {
type Item = (i32, i32);
fn next(&mut self) -> Option<(i32, i32)> {
if self.j == SIZE {
return None;
}
let coord = (self.i, self.j);
self.i += 1;
if self.i == SIZE {
self.i = 0;
self.j += 1;
}
Some(coord)
}
}
pub fn grid_coords() -> GridCoordIter {
GridCoordIter { i: 0, j: 0 }
}
要实现效率和简洁,需要来自不稳定Rust的impl Trait
功能,如Francis's answer所示。它允许函数直接返回一个匿名类型的值,调用者不需要知道任何关于它的信息,除非它实现了特定的特征。
答案 1 :(得分:1)
如果你使用夜间编译器,那么你可以使用不稳定的impl Trait
syntax:
#![feature(conservative_impl_trait)]
pub fn grid_coords() -> impl Iterator<Item = (i32, i32)> {
(0..SIZE).flat_map(|y| (0..SIZE).map(move |x| (x, y)))
}