iter vs into_iter en Rust: ¿qué se esconde bajo el capó?

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

iter vs into_iter: batalla de titanes de la iteración en Rust

\n\n

Si alguna vez has escrito un bucle sobre un vector en Rust, seguramente te has encontrado con los tres hermanos gemelos: iter(), into_iter() y iter_mut(). A primera vista, todos hacen lo mismo: permiten recorrer los elementos. Pero la diferencia entre ellos es más profunda de lo que parece y se encuentra en el corazón de Rust: el sistema de propiedad.

\n\n

Imagina que eres un barista y tu colección son granos de café. iter() te permite mirar los granos, olerlos, pero no tocarlos. into_iter() es cuando tomas los granos, los mueles y haces un espresso, después de lo cual los granos desaparecen. Y iter_mut() — puedes no solo mirar, sino también moler los granos directamente en la bolsa, cambiando su forma.

\n\n

Hoy analizaremos iter vs into_iter como se explica en Stack Overflow, pero con ejemplos vivos y una inmersión profunda bajo el capó. ¡Allá vamos!

\n\n

¿Qué es un iterador en Rust?

\n\n

Un iterador es un objeto que sabe cómo obtener elementos secuencialmente de una colección. En Rust, los iteradores son perezosos: no hacen nada hasta que llamas a next(). Pero lo principal es que cada método de iteración trata la propiedad de los datos de manera diferente.

\n\n

Aquí están los tres enfoques principales:

\n
    \n
  • iter() — toma prestados los elementos (no toma la propiedad)
  • \n
  • into_iter() — consume la colección (toma la propiedad)
  • \n
  • iter_mut() — toma prestado con posibilidad de modificación
  • \n
\n\n

Veamos el código. Supongamos que tenemos un vector de nombres:

\n\n
let names = vec!["Ana".to_string(), "Boris".to_string(), "Víctor".to_string()];
\n\n

Ejemplo 1: iter() — solo mirar

\n\n
fn main() {\n    let names = vec!["Ana".to_string(), "Boris".to_string(), "Víctor".to_string()];\n    \n    // iter() devuelve &String (una referencia)\n    for name in names.iter() {\n        println!("¡Hola, {}!", name);\n    }\n    \n    // ¡names todavía está disponible!\n    println!("El vector vive: {:?}", names);\n}
\n\n

Observa: después del bucle, usamos names sin problemas. ¿Por qué? Porque iter() devolvió una referencia a cada elemento. La propiedad permaneció en el vector. Esto es seguro y predecible.

\n\n

Ejemplo 2: into_iter() — quemar los puentes

\n\n
fn main() {\n    let names = vec!["Ana".to_string(), "Boris".to_string(), "Víctor".to_string()];\n    \n    // into_iter() devuelve String (la propiedad se mueve)\n    for name in names.into_iter() {\n        println!("¡Adiós, {}!", name);\n    }\n    \n    // ¡Error! names ya no existe\n    // println!("El vector vive: {:?}", names); // ❌ borrow of moved value\n}
\n\n

Ahí está la diferencia clave. into_iter() consume la colección. Cada elemento se mueve al bucle y el vector original se vuelve inválido. Si necesitas conservar la colección para uso posterior, into_iter() no es tu opción.

\n\n

Ejemplo 3: iter_mut() — modificar sin destruir

\n\n
fn main() {\n    let mut names = vec!["Ana".to_string(), "Boris".to_string(), "Víctor".to_string()];\n    \n    // iter_mut() devuelve &mut String (referencia mutable)\n    for name in names.iter_mut() {\n        name.push_str(" (invitado)");\n    }\n    \n    println!("Vector modificado: {:?}", names);\n    // Salida: ["Ana (invitado)", "Boris (invitado)", "Víctor (invitado)"]\n}
\n\n

Aquí modificamos los elementos en su lugar, pero sin tomar la propiedad. Ideal para transformaciones.

\n\n

Bajo el capó: ¿qué devuelve cada método?

\n\n

Para entender realmente iter vs into_iter, echemos un vistazo a la biblioteca estándar. Para el vector Vec<T>:

\n\n