El trait Iterator es el corazón de la iteración funcional en Rust. Su función es definir una secuencia de elementos que pueden ser recorridos uno a uno. La esencia de este trait reside en un único método requerido: next(&mut self) -> Option<Self::Item>. Este método debe devolver Some(valor) mientras existan elementos y None cuando la secuencia haya terminado. Implementar este trait permite que un tipo personalizado pueda ser utilizado en bucles for (gracias al trait IntoIterator) y sea compatible con toda la biblioteca de iteradores.

Una característica fundamental es la evaluación perezosa (lazy evaluation). Los métodos como map, filter o take son “adaptadores”. Un adaptador no transforma los datos inmediatamente; lo que hace es envolver el iterador original en una nueva estructura que contiene la lógica de transformación. El trabajo real no ocurre hasta que se invoca un consumidor (como collect, sum, o un bucle for), que es el encargado de llamar repetidamente a next(). Esta arquitectura permite a Rust optimizar el código mediante el “loop fusion” de LLVM, donde múltiples transformaciones se colapsan en un único bucle de alto rendimiento, evitando asignaciones de memoria intermedias innecesarias.

Si se implementa next() incorrectamente, se pueden producir errores críticos: si nunca devuelve None, se creará un bucle infinito; si no actualiza el estado interno, se repetirá el mismo valor indefinidamente. Además, abusar de collect() en medio de una cadena de adaptadores rompe la lazitud, forzando la creación de colecciones temporales en memoria y degradando el rendimiento.

// Implementación de un iterador personalizado
struct Contador {
    actual: u32,
    limite: u32,
}

impl Iterator for Contador {
    // El tipo de elemento que devuelve el iterador
    type Item = u32;

    // La lógica de avance de la secuencia
    fn next(&mut self) -> Option<Self::Item> {
        if self.actual < self.limite {
            self.actual += 1;
            Some(self.actual)
        } else {
            None
        }
    }
}

fn main() {
    // 1. Uso de iteradores sobre colecciones existentes
    let numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // 2. Cadena de adaptadores lazy (no se ejecuta nada aún)
    let transformacion = numeros
        .into_iter()           // Convierte el vec en un iterador (ownership)
        .filter(|x| x % 2 == 0) // Adaptador: solo elementos pares
        .map(|x| x * 10)        // Adaptador: multiplica por 10
        .take(3);               // Adaptador: limita a los primeros 3 resultados

    // 3. El consumidor (collect) dispara la ejecución
    let resultado: Vec<u32> = transformacion.collect();

    println!("Resultado de la cadena: {:?}", resultado);

    // 4. Uso del iterador personalizado
    let mi_contador = Contador { actual: 0, limite: 5 };
    
    // El bucle for actúa como consumidor
    println!("Iterando con Contador personalizado:");
    for num in mi_contador {
        println!("{}", num);
    }
}

Explicación del Código

  • Contador: Es una estructura personalizada que mantiene el estado de la iteración mediante los campos actual (valor presente) y limite (techo de la secuencia).
  • impl Iterator for Contador: Aquí definimos el comportamiento de nuestra secuencia.
    • type Item = u32: Define que el tipo de datos que el iterador entrega es un entero de 32 bits.
    • next: Implementa la lógica de control. Incrementa self.actual y devuelve Some(self.actual) mientras sea menor que limite. Cuando actual alcanza limite, devuelve None, señalando el fin de la iteración.
  • numeros.into_iter(): Convierte el vector en un iterador que consume la colección, moviendo la propiedad de los elementos.
  • .filter(|x| x % 2 == 0): Un adaptador que toma una función de cierre (closure) y solo permite que next() devuelva un valor si cumple la condición.
  • .map(|x| x * 10): Otro adaptador que transforma cada elemento obtenido mediante la función de cierre.
  • .take(3): Un adaptador que limita la secuencia. Una vez que se han producido 3 elementos, este adaptador hará que el iterador devuelva None, sin importar si hay más elementos en la fuente.
  • .collect(): El consumidor. Es el motor que impulsa la cadena; sin este método (o un bucle for), la variable transformacion no habría procesado ningún número.
  • for num in mi_contador: El bucle for utiliza internamente into_iter() sobre mi_contador, llamando a next() de forma iterativa hasta recibir None.

65