Rust
Tutorial Rust: Estructuras de control iterativo
Aprende las estructuras de control iterativo en Rust: loop, while, for, controladores de flujo y ranges para programación eficiente.
Aprende Rust y certifícateloop, while, for
En Rust, los bucles son estructuras fundamentales que permiten ejecutar un bloque de código repetidamente. A diferencia de otros lenguajes, Rust ofrece tres tipos de bucles principales, cada uno con características únicas que los hacen adecuados para diferentes situaciones.
El bucle loop
El bucle loop
es la estructura iterativa más básica en Rust. Este bucle ejecuta un bloque de código indefinidamente hasta que se encuentra explícitamente con una instrucción break
.
fn main() {
let mut contador = 0;
loop {
println!("Valor actual: {}", contador);
contador += 1;
if contador == 5 {
break; // Sale del bucle cuando contador llega a 5
}
}
println!("Bucle finalizado");
}
Una característica poderosa de los bucles en Rust es que son expresiones, lo que significa que pueden devolver valores. Esto se logra proporcionando un valor a la instrucción break
:
fn main() {
let mut contador = 0;
let resultado = loop {
contador += 1;
if contador == 10 {
break contador * 2; // Devuelve contador * 2 al salir del bucle
}
};
println!("El resultado es: {}", resultado); // Imprime: El resultado es: 20
}
Este enfoque es especialmente útil cuando necesitas buscar un valor o realizar cálculos iterativos y devolver el resultado final.
El bucle while
El bucle while
ejecuta un bloque de código mientras una condición específica sea verdadera. Es ideal cuando conoces la condición de terminación pero no necesariamente el número exacto de iteraciones.
fn main() {
let mut numero = 1;
while numero < 50 {
// Si es divisible por 7, imprime el número
if numero % 7 == 0 {
println!("{} es divisible por 7", numero);
}
numero += 1;
}
}
El bucle while
es más conciso que un loop
con una condición de salida cuando la lógica de terminación es simple. Sin embargo, a diferencia de loop
, no es común usar while
para devolver valores, aunque técnicamente es posible.
El bucle for
El bucle for
en Rust está diseñado específicamente para iterar sobre elementos de una colección o secuencia. La sintaxis es clara y previene errores comunes como acceder fuera de los límites de una colección.
fn main() {
// Iterando sobre un rango numérico
for i in 1..6 {
println!("Número: {}", i);
}
// Iterando sobre un rango inclusivo (incluye el valor final)
for letra in 'a'..='e' {
println!("Letra: {}", letra);
}
}
El bucle for
es especialmente útil porque:
- Es conciso y elimina la necesidad de gestionar manualmente índices o contadores
- Previene errores de fuera de rango comunes en otros lenguajes
- Funciona con cualquier tipo que implemente el trait
IntoIterator
Comparación de los tres tipos de bucles
Cada tipo de bucle tiene su caso de uso ideal:
- loop: Cuando necesitas un bucle infinito o quieres devolver un valor desde el bucle
- while: Cuando tienes una condición clara de terminación
- for: Cuando necesitas iterar sobre una secuencia de elementos
Veamos un ejemplo que resuelve el mismo problema con los tres tipos de bucles:
fn main() {
// Sumar los números del 1 al 5 usando loop
let mut suma = 0;
let mut contador = 1;
loop {
suma += contador;
contador += 1;
if contador > 5 {
break;
}
}
println!("Suma con loop: {}", suma);
// Sumar los números del 1 al 5 usando while
let mut suma = 0;
let mut contador = 1;
while contador <= 5 {
suma += contador;
contador += 1;
}
println!("Suma con while: {}", suma);
// Sumar los números del 1 al 5 usando for
let mut suma = 0;
for numero in 1..=5 {
suma += numero;
}
println!("Suma con for: {}", suma);
}
Como puedes observar, el bucle for
proporciona la solución más elegante y menos propensa a errores para este caso particular.
Bucles y mutabilidad
Es importante notar que en Rust, la variable de iteración en un bucle for
es inmutable por defecto dentro del cuerpo del bucle:
fn main() {
for i in 1..6 {
// i es inmutable aquí
println!("Valor: {}", i);
// Esto causaría un error de compilación:
// i += 1;
}
}
Si necesitas una variable mutable en el cuerpo del bucle, puedes declarar una nueva variable:
fn main() {
for i in 1..6 {
let mut valor = i;
valor *= 2;
println!("El doble de {} es {}", i, valor);
}
}
Esta característica ayuda a prevenir modificaciones accidentales de la variable de iteración, lo que podría llevar a comportamientos inesperados o bucles infinitos en otros lenguajes.
Controladores de flujo
Los controladores de flujo son herramientas esenciales que nos permiten modificar el comportamiento normal de los bucles en Rust. Estos mecanismos nos dan un control preciso sobre cuándo continuar con la siguiente iteración o cuándo abandonar completamente un bucle.
break y continue
Rust proporciona dos controladores de flujo principales para manipular la ejecución de bucles:
- break: Termina inmediatamente el bucle más interno y continúa con el código que sigue después del bucle.
- continue: Salta el resto del código en la iteración actual y pasa a la siguiente iteración.
Veamos cómo funcionan estos controladores con ejemplos prácticos:
fn main() {
// Ejemplo de continue
for i in 1..10 {
// Salta los números pares
if i % 2 == 0 {
continue;
}
println!("Número impar: {}", i);
}
// Ejemplo de break
let mut suma = 0;
for i in 1..100 {
suma += i;
if suma > 50 {
println!("La suma superó 50 en la iteración {}", i);
break;
}
}
}
En el primer ejemplo, continue
nos permite filtrar valores durante la iteración sin necesidad de anidar todo el código en un bloque condicional. En el segundo ejemplo, break
nos permite detener el bucle cuando se cumple una condición específica, evitando iteraciones innecesarias.
Etiquetas de bucle
Cuando trabajamos con bucles anidados, a veces necesitamos más control sobre qué bucle específico queremos afectar con break
o continue
. Rust resuelve este problema con las etiquetas de bucle, que nos permiten nombrar cada bucle y referenciarlos específicamente:
fn main() {
// Etiquetamos el bucle externo como 'externo'
'externo: for x in 1..6 {
println!("x: {}", x);
// Bucle interno sin etiquetar
for y in 1..6 {
println!(" y: {}", y);
if x * y > 10 {
// Rompe el bucle externo, no solo el interno
println!(" Rompiendo bucle externo en x={}, y={}", x, y);
break 'externo;
}
if y > 2 {
// Continúa con la siguiente iteración del bucle externo
println!(" Saltando a la siguiente x");
continue 'externo;
}
}
}
}
En este ejemplo:
- Definimos una etiqueta llamada
'externo'
para el primer bucle - Usamos
break 'externo'
para salir completamente de ambos bucles cuandox * y > 10
- Usamos
continue 'externo'
para saltar a la siguiente iteración del bucle externo cuandoy > 2
Las etiquetas de bucle son especialmente útiles en algoritmos complejos donde necesitas controlar con precisión el flujo de ejecución entre múltiples niveles de bucles.
Combinando controladores de flujo con valores de retorno
Como vimos anteriormente, los bucles en Rust son expresiones que pueden devolver valores. Podemos combinar esto con los controladores de flujo para crear patrones elegantes:
fn main() {
let resultado = 'busqueda: loop {
for i in 1..100 {
for j in 1..100 {
if i * j == 819 {
// Rompe el bucle etiquetado y devuelve un valor
break 'busqueda (i, j);
}
}
}
// Si no se encuentra, devuelve esto (aunque en este caso siempre encontrará)
break (0, 0);
};
println!("Los números que multiplicados dan 819 son: {} y {}", resultado.0, resultado.1);
}
En este ejemplo, estamos buscando dos números cuyo producto es 819. Cuando los encontramos, usamos break
con la etiqueta del bucle más externo para salir de todos los bucles y devolver una tupla con los valores encontrados.
Controladores de flujo condicionales
A menudo necesitamos aplicar controladores de flujo basados en condiciones. Aunque ya hemos visto ejemplos con if
, vale la pena destacar algunos patrones comunes:
fn main() {
let mut contador = 0;
// Bucle que se ejecuta hasta que se cumple una condición
while contador < 10 {
contador += 1;
// Salta las iteraciones para múltiplos de 3
if contador % 3 == 0 {
println!("Saltando {}", contador);
continue;
}
println!("Procesando {}", contador);
// Sale del bucle si encuentra un valor específico
if contador == 7 {
println!("¡Encontrado el valor especial!");
break;
}
}
}
Este patrón de filtrado y terminación temprana es muy común en la programación real y permite escribir código más eficiente que no realiza trabajo innecesario.
Uso de controladores de flujo en diferentes tipos de bucles
Los controladores de flujo funcionan de manera similar en los tres tipos de bucles de Rust, pero hay algunos matices a considerar:
fn main() {
// En bucles loop
let mut n = 0;
let resultado = loop {
n += 1;
if n % 5 == 0 {
break n; // Devuelve el primer múltiplo de 5
}
};
println!("Primer múltiplo de 5: {}", resultado);
// En bucles while
let mut x = 0;
while x < 10 {
x += 1;
if x % 2 == 0 {
continue; // Salta los números pares
}
println!("Número impar en while: {}", x);
}
// En bucles for
for i in 0..8 {
if i < 2 {
println!("Valor muy pequeño, continuando");
continue;
}
if i > 5 {
println!("Valor muy grande, saliendo");
break;
}
println!("Procesando valor: {}", i);
}
}
En este ejemplo, podemos ver cómo los controladores de flujo se adaptan a cada tipo de bucle, permitiéndonos expresar lógica compleja de manera clara y concisa.
Consideraciones de rendimiento
Los controladores de flujo no solo mejoran la legibilidad del código, sino que también pueden tener un impacto positivo en el rendimiento:
fn main() {
let mut encontrado = false;
let objetivo = 317;
// Búsqueda en un rango grande
for i in 1..1000 {
if i * i == objetivo {
encontrado = true;
println!("¡Encontrado! {} es el cuadrado de {}", objetivo, i);
break; // Evita iteraciones innecesarias
}
}
if !encontrado {
println!("{} no es un cuadrado perfecto en el rango buscado", objetivo);
}
}
En este caso, break
nos permite detener la búsqueda tan pronto como encontramos lo que estamos buscando, evitando cálculos innecesarios y mejorando la eficiencia del programa.
Iteración sobre ranges
Los ranges (rangos) en Rust son una forma elegante de expresar secuencias de valores. Funcionan como una especie de atajo para generar una serie de números o caracteres sin tener que escribirlos explícitamente. Esta característica es particularmente útil cuando necesitamos iterar sobre secuencias en bucles for
.
Un range se crea utilizando la sintaxis con dos puntos (..
o ..=
). Existen dos tipos principales:
fn main() {
// Range exclusivo: incluye el inicio pero excluye el final (1, 2, 3, 4)
let rango_exclusivo = 1..5;
// Range inclusivo: incluye tanto el inicio como el final (1, 2, 3, 4, 5)
let rango_inclusivo = 1..=5;
println!("Demostración de rangos");
}
Iterando sobre rangos numéricos
La forma más común de utilizar ranges es con el bucle for
, que automáticamente recorre cada valor del rango:
fn main() {
// Iteración sobre un rango exclusivo
println!("Rango exclusivo (1..5):");
for numero in 1..5 {
println!(" Valor: {}", numero);
}
// Iteración sobre un rango inclusivo
println!("Rango inclusivo (1..=5):");
for numero in 1..=5 {
println!(" Valor: {}", numero);
}
}
Este código imprimirá los valores del 1 al 4 para el rango exclusivo, y del 1 al 5 para el rango inclusivo.
Rangos con diferentes tipos de datos
Los ranges no están limitados a enteros. Podemos crear rangos con cualquier tipo que implemente el trait Step
, como los caracteres:
fn main() {
// Rango de caracteres (exclusivo)
println!("Letras de 'a' a 'd' (exclusivo):");
for letra in 'a'..'e' {
println!(" Letra: {}", letra);
}
// Rango de caracteres (inclusivo)
println!("Letras de 'a' a 'e' (inclusivo):");
for letra in 'a'..='e' {
println!(" Letra: {}", letra);
}
}
Rangos con paso personalizado
Aunque los ranges por sí solos no permiten especificar un paso diferente a 1, podemos combinarlos con el método step_by()
para iterar saltando elementos:
fn main() {
// Números pares del 0 al 10
println!("Números pares del 0 al 10:");
for numero in (0..=10).step_by(2) {
println!(" {}", numero);
}
// Múltiplos de 3 del 0 al 20
println!("Múltiplos de 3 del 0 al 20:");
for numero in (0..=20).step_by(3) {
println!(" {}", numero);
}
}
Rangos en orden inverso
Para iterar sobre un rango en orden inverso, podemos usar el método rev()
:
fn main() {
println!("Cuenta regresiva:");
for numero in (1..=10).rev() {
println!(" {}", numero);
}
println!("¡Despegue!");
// También funciona con caracteres
println!("Alfabeto inverso (de 'e' a 'a'):");
for letra in ('a'..='e').rev() {
println!(" {}", letra);
}
}
Casos de uso prácticos
Los ranges son extremadamente útiles para muchas tareas comunes de programación:
Generación de tablas
fn main() {
println!("Tabla de multiplicar del 5:");
for i in 1..=10 {
println!(" 5 × {} = {}", i, 5 * i);
}
}
Cálculo de sumatorias
fn main() {
let mut suma = 0;
// Sumar números del 1 al 100
for numero in 1..=100 {
suma += numero;
}
println!("La suma de los números del 1 al 100 es: {}", suma);
// Verificación: fórmula n(n+1)/2
let verificacion = 100 * 101 / 2;
println!("Verificación mediante fórmula: {}", verificacion);
}
Generación de patrones
fn main() {
// Generar un patrón de triángulo
let altura = 5;
for i in 1..=altura {
for _ in 1..=i {
print!("* ");
}
println!();
}
}
Limitaciones de los ranges
Es importante entender algunas limitaciones de los ranges:
- Los ranges por sí mismos no almacenan todos los valores de la secuencia, sino solo los límites
- No se pueden crear ranges sin límite superior en un bucle
for
(como0..
) - Los ranges funcionan mejor con tipos que tienen un tamaño fijo y conocido
fn main() {
// Esto funciona bien
for i in 1..=5 {
println!("{}", i);
}
// Esto causaría un error porque no tiene límite superior
// for i in 5.. {
// println!("{}", i);
// }
}
Combinando ranges con otras estructuras de control
Los ranges se integran perfectamente con las estructuras de control que ya conocemos:
fn main() {
// Encontrar el primer número cuyo cuadrado es mayor que 50
for n in 1..100 {
if n * n > 50 {
println!("El primer número cuyo cuadrado es mayor que 50 es: {}", n);
println!("Su cuadrado es: {}", n * n);
break;
}
}
// Imprimir solo los números impares en un rango
println!("Números impares entre 1 y 10:");
for n in 1..=10 {
if n % 2 == 0 {
continue;
}
println!(" {}", n);
}
}
Ranges como expresiones
Los ranges también pueden usarse en expresiones condicionales para verificar si un valor está dentro de ciertos límites:
fn main() {
let puntuacion = 85;
let calificacion = if puntuacion >= 90 {
"A"
} else if puntuacion >= 80 && puntuacion < 90 {
"B"
} else if puntuacion >= 70 && puntuacion < 80 {
"C"
} else if puntuacion >= 60 && puntuacion < 70 {
"D"
} else {
"F"
};
println!("Con una puntuación de {}, la calificación es: {}", puntuacion, calificacion);
// Forma más concisa usando ranges y match
let calificacion = match puntuacion {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
_ => "F",
};
println!("Usando match: Con una puntuación de {}, la calificación es: {}", puntuacion, calificacion);
}
Los ranges son una herramienta fundamental en Rust que simplifica muchas tareas comunes de programación. Su integración con los bucles for
hace que la iteración sobre secuencias sea intuitiva y menos propensa a errores, permitiéndonos escribir código más limpio y expresivo.
Otras lecciones de Rust
Accede a todas las lecciones de Rust y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Rust
Introducción Y Entorno
Primer Programa
Introducción Y Entorno
Instalación Del Entorno
Introducción Y Entorno
Funciones
Sintaxis
Operadores
Sintaxis
Estructuras De Control Condicional
Sintaxis
Arrays Y Strings
Sintaxis
Manejo De Errores Panic
Sintaxis
Variables Y Tipos Básicos
Sintaxis
Estructuras De Control Iterativo
Sintaxis
Colecciones Estándar
Estructuras De Datos
Option Y Result
Estructuras De Datos
Pattern Matching
Estructuras De Datos
Estructuras (Structs)
Estructuras De Datos
Enumeraciones Enums
Estructuras De Datos
El Concepto De Ownership
Ownership
Lifetimes Básicos
Ownership
Slices Y Referencias Parciales
Ownership
References Y Borrowing
Ownership
Funciones Anónimas Closures
Abstracción
Traits De La Biblioteca Estándar
Abstracción
Traits
Abstracción
Generics
Abstracción
Channels Y Paso De Mensajes
Concurrencia
Memoria Compartida Segura
Concurrencia
Threads Y Sincronización Básica
Concurrencia
Introducción A Tokio
Asincronía
Fundamentos Asíncronos Y Futures
Asincronía
Async/await
Asincronía
Ejercicios de programación de Rust
Evalúa tus conocimientos de esta lección Estructuras de control iterativo con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender las diferencias y usos de los bucles loop, while y for en Rust.
- Aprender a utilizar controladores de flujo como break, continue y etiquetas de bucle para controlar la ejecución.
- Entender cómo los bucles en Rust pueden devolver valores y cómo aprovechar esta característica.
- Conocer la sintaxis y aplicaciones de los rangos (ranges) para iterar secuencias numéricas y de caracteres.
- Aplicar buenas prácticas para evitar errores comunes y mejorar la legibilidad y eficiencia del código iterativo.