我的功能可以返回我认为应该是值得一提的东西,但是到目前为止,我一直在收集它并将其返回为矢量。
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>
的东西,我可以使用不太丑陋的类型吗?
在当前(稳定)生锈中,返回"概念"迭代器的最简单方法是返回实现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)
}
使用盒子有效地擦除了混凝土迭代器对象的类型,即呼叫者不需要知道它是使用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式的间接间接,这两个间接都不是通过当前Rust优化的。实践中的问题是否取决于您的用法,但是如果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 }
}
实现效率和简洁都需要不稳定生锈的impl Trait
功能,如弗朗西斯的回答所示。它允许该函数直接返回呼叫者不需要了解的匿名类型的值,除了它实现了特定特征。
如果您可以使用夜间编译器,则可以使用不稳定的impl Trait
语法:
#![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)))
}