Implementar el trait Iterator es la forma estándar de definir mecanismos de iteración personalizados en Rust, permitiendo que sus tipos propios se integren con el ecosistema de métodos funcionales como map, filter y collect. Un iterador es, esencialmente, una máquina de estados que implementa el método next, el cual devuelve un Option<Self::Item>. Si el iterador tiene un fin, devuelve None; de lo contrario, devuelve Some(valor) y actualiza su estado interno.
Para lograr una implementación de nivel avanzado y eficiente, no basta con next. Es crucial implementar size_hint para informar al consumidor (como collect) sobre la cantidad de elementos que se pueden esperar; esto permite optimizaciones de memoria, evitando múltiples realocaciones. Si el tamaño es conocido con exactitud, se debe implementar ExactSizeIterator. Para iteradores bidireccionales, como los que permiten usar .rev(), se implementa DoubleEndedIterator mediante el método next_back. Finalmente, implementar IntoIterator es lo que permite que una estructura sea consumida directamente en un bucle for.
/// Un rango bidireccional personalizado que permite iterar de forma
/// ascendente y descendente, implementando rasgos avanzados.
struct BiDirectionalRange {
start: i32,
end: i32,
}
impl BiDirectionalRange {
fn new(start: i32, end: i32) -> Self {
Self { start, end }
}
}
impl Iterator for BiDirectionalRange {
// El tipo asociado que define qué produce el iterador
type Item = i32;
/// Avanza el estado desde el inicio hacia el final.
fn next(&mut self) -> Option<Self::Item> {
if self.start <= self.end {
let val = self.start;
self.start += 1;
Some(val)
} else {
None
}
}
/// Informa la cantidad de elementos restantes para optimizar memoria.
fn size_hint(&self) -> (usize, Option<usize>) {
if self.start <= self.end {
let len = (self.end - self.start + 1) as usize;
(len, Some(len))
} else {
(0, Some(0))
}
}
}
// Indica que el tamaño es exactamente conocido mediante size_hint
impl ExactSizeIterator for BiDirectionalRange {}
impl DoubleEndedIterator for BiDirectionalRange {
/// Avanza el estado desde el final hacia el inicio.
fn next_back(&mut self) -> Option<Self::Item> {
if self.end >= self.start {
let val = self.end;
self.end -= 1;
Some(val)
} else {
None
}
}
}
// Permite que el struct sea usado en bucles 'for'
impl IntoIterator for BiDirectionalRange {
type Item = i32;
type IntoIter = BiDirectionalRange;
fn into_iter(self) -> Self::IntoIter {
self
}
}
fn main() {
// Uso de Iterator y size_hint para collect
let range = BiDirectionalRange::new(1, 5);
let v: Vec<i32> = range.collect();
println!("Colección (ascendente): {:?}", v);
// Uso de DoubleEndedIterator (next_back)
let mut range_db = BiDirectionalRange::new(1, 5);
println!("Primer next(): {:?}", range_db.next()); // Some(1)
println!("Primer next_back(): {:?}", range_db.next_back()); // Some(5)
// Uso de IntoIterator en un bucle for
let range_for = BiDirectionalRange::new(1, 3);
for n in range_for {
println!("Elemento del for: {}", n);
}
}
Análisis del código
El código define un struct BiDirectionalRange con dos campos: start y end, que actúan como los límites de nuestro rango.
impl Iterator for BiDirectionalRange: Definimos eltype Item = i32, indicando que el iterador produce enteros. El métodonextutiliza el campostartpara devolver el valor actual e incrementarlo, actuando como el motor de la máquina de estados.size_hint: Este método calcula la diferencia entreendystart. Al devolverSome(len)en el segundo elemento de la tupla, le informamos a Rust que conocemos el tamaño exacto, lo que permite quecollectreserve la memoria necesaria de un solo golpe.ExactSizeIterator: Al implementar este trait vacío, garantizamos quesize_hintsiempre sea preciso, permitiendo optimizaciones en algoritmos que dependen del tamaño del iterador.DoubleEndedIterator: Implementamosnext_back, que consume el iterador desde el otro extremo decrementando el campoend. Esto permite una iteración bidireccional (siempre questartyendno se crucen).IntoIterator: Este trait es fundamental para la ergonomía. Al implementarinto_iter(self), permitimos queBiDirectionalRangesea consumido por un buclefor, transformando la estructura en su propio tipo iterador.main: En la función principal, se demuestra cómocollectaprovecha la información desize_hint, cómonext_backopera de forma independiente anext, y cómo el bucleforconsume la estructura gracias aIntoIterator.
N° 67