Un iterador es un valor que produce una secuencia de valores, generalmente para que un bucle opere sobre ellos.
La biblioteca estándar de Rust proporciona iteradores que recorren vectores, cadenas de texto, tablas hash y otras colecciones, pero también iteradores para producir líneas de texto desde un flujo de entrada, conexiones que llegan a un servidor de red, valores recibidos de otros hilos a través de un canal de comunicación, entre otros. Y, por supuesto, puedes implementar tus propios iteradores para tus propósitos.
El bucle for de Rust proporciona una sintaxis natural para utilizar iteradores, pero los iteradores en sí mismos también proporcionan un amplio conjunto de métodos para mapear, filtrar, unir, recolectar, y más.
Los iteradores de Rust son flexibles, expresivos y eficientes. Considera la siguiente función, que devuelve la suma de los primeros n números enteros positivos (a menudo llamado el número triangular de n):
fn triangle(n: i32) -> i32 {
let mut sum = 0;
for i in 1..=n {
sum += i;
}
sum
}
fn triangle_number(n: u32) -> u32 {
(1..=n).sum()
}
Esta función utiliza el iterador ..=
para generar una secuencia de números desde 1 hasta n
, y luego utiliza el método sum()
para calcular la suma de todos los elementos de la secuencia. Esta es solo una de las muchas formas en que puedes utilizar iteradores en Rust para realizar operaciones en colecciones de datos de manera concisa y eficiente.
Pero los iteradores también tienen un método llamado fold
, que puede usar en una definición equivalente:
fn triangle_number(n: u32) -> u32 {
(1..=n).fold(0, |acc, x| acc + x)
}
El método fold
toma un valor inicial (0
en este caso) y un cierre con dos argumentos (acc
y x
). El cierre se aplica a cada elemento en la secuencia del iterador, donde acc
acumula el resultado intermedio y x
representa el elemento actual. En este ejemplo, agrega el elemento actual x
al valor acumulado acc
. El valor acumulado final se devuelve como resultado.
El uso de fold
le permite realizar operaciones más complejas en los elementos de un iterador, como calcular una suma acumulada, un producto o cualquier otra operación personalizada que necesite. Proporciona una poderosa herramienta para reducir una secuencia de valores en un solo resultado.
fn triangle(n: i32) -> i32 {
(1..=n).fold(0, |sum, item| sum + item)
}
Comenzando con 0 como total acumulado, fold
toma cada valor producido por 1..=n
y aplica el cierre |sum, item| sum + item
al total acumulado y al valor actual. El valor de retorno del cierre se toma como el nuevo total acumulado. El último valor que devuelve es lo que fold
en sí mismo devuelve, en este caso, el total de toda la secuencia. Esto puede parecer extraño si estás acostumbrado a los bucles for
y while
, pero una vez que te hayas acostumbrado, fold
es una alternativa legible y concisa.
Esto es bastante común en los lenguajes de programación funcionales, que valoran la expresividad. Pero los iteradores de Rust fueron diseñados cuidadosamente para asegurarse de que el compilador pueda traducirlos en un código de máquina excelente. En una compilación de lanzamiento de la segunda definición mostrada antes, Rust conoce la definición de fold
e inlinea en triangle
. A continuación, el cierre |sum, item| sum + item
se inlinea en eso. Finalmente, Rust examina el código combinado y reconoce que hay una forma más sencilla de sumar los números del uno al n: la suma siempre es igual a n * (n+1) / 2. Rust traduce todo el cuerpo de triangle
, el bucle, el cierre y todo, en una sola instrucción de multiplicación y unos pocos bits adicionales de aritmética.
Este ejemplo involucra una operación aritmética simple, pero los iteradores también tienen un buen rendimiento cuando se utilizan de forma más intensiva. Son otro ejemplo de cómo Rust proporciona abstracciones flexibles que imponen poco o ningún sobrecoste en el uso típico.