Мне кажется, что до тех пор, пока .step_by
метод не станет стабильным, можно легко выполнить то, что вы хотите, с помощью Iterator
(что Range
в любом случае действительно есть):
struct SimpleStepRange(isize, isize, isize);
impl Iterator for SimpleStepRange {
type Item = isize;
#[inline]
fn next(&mut self) -> Option<isize> {
if self.0 < self.1 {
let v = self.0;
self.0 = v + self.2;
Some(v)
} else {
None
}
}
}
fn main() {
for i in SimpleStepRange(0, 10, 2) {
println!("{}", i);
}
}
Если нужно перебрать несколько диапазонов разных типов, код можно сделать универсальным следующим образом:
use std::ops::Add;
struct StepRange<T>(T, T, T)
where for<'a> &'a T: Add<&'a T, Output = T>,
T: PartialOrd,
T: Clone;
impl<T> Iterator for StepRange<T>
where for<'a> &'a T: Add<&'a T, Output = T>,
T: PartialOrd,
T: Clone
{
type Item = T;
#[inline]
fn next(&mut self) -> Option<T> {
if self.0 < self.1 {
let v = self.0.clone();
self.0 = &v + &self.2;
Some(v)
} else {
None
}
}
}
fn main() {
for i in StepRange(0u64, 10u64, 2u64) {
println!("{}", i);
}
}
Я оставлю вам исключить проверку верхних границ, чтобы создать структуру с открытым концом, если требуется бесконечный цикл ...
Преимущества этого подхода в том, что он работает с for
шугарингом и будет продолжать работать, даже когда нестабильные функции станут доступны для использования; Кроме того, в отличие от подхода без сахара, использующего стандартные Range
s, он не теряет эффективности из-за нескольких .next()
вызовов. Недостатки заключаются в том, что для настройки итератора требуется несколько строк кода, поэтому оно может иметь смысл только для кода с большим количеством циклов.