Estructuras de Control en Rust
Las estructuras de control nos permiten dirigir el flujo de ejecución de nuestros programas. En este capítulo exploraremos condicionales, bucles y pattern matching en Rust.
Expresiones Condicionales
if Básico
fn main() {
let numero = 6;
if numero % 4 == 0 {
println!("El número es divisible por 4");
} else if numero % 3 == 0 {
println!("El número es divisible por 3");
} else if numero % 2 == 0 {
println!("El número es divisible por 2");
} else {
println!("El número no es divisible por 4, 3, o 2");
}
}
if como Expresión
En Rust, if es una expresión, no solo una declaración:
fn main() {
let condicion = true;
// if como expresión
let numero = if condicion { 5 } else { 6 };
println!("El valor de numero es: {numero}");
// Clasificar edad
let edad = 25;
let categoria = if edad < 13 {
"niño"
} else if edad < 20 {
"adolescente"
} else if edad < 65 {
"adulto"
} else {
"adulto mayor"
};
println!("Categoría: {categoria}");
// Los tipos deben coincidir
// let error = if condicion { 5 } else { "seis" }; // ERROR!
}
Condiciones Complejas
fn main() {
let temperatura = 22;
let llueve = false;
let es_fin_de_semana = true;
// Operadores lógicos
if temperatura > 20 && !llueve {
println!("¡Perfecto para salir!");
}
if es_fin_de_semana || temperatura > 25 {
println!("Buen día para relajarse");
}
// Expresiones más complejas
let actividad = if temperatura > 30 {
if llueve {
"quedarse en casa con aire acondicionado"
} else {
"ir a la playa"
}
} else if temperatura < 10 {
"quedarse en casa"
} else if llueve {
"ir al cine"
} else {
"dar un paseo"
};
println!("Actividad recomendada: {actividad}");
}
Bucles
Rust proporciona tres tipos de bucles: loop, while, y for.
loop - Bucle Infinito
fn main() {
let mut contador = 0;
// Bucle infinito con break
loop {
contador += 1;
if contador == 3 {
continue; // Salta a la siguiente iteración
}
println!("Contador: {contador}");
if contador == 5 {
break; // Sale del bucle
}
}
println!("Fin del bucle");
}
Retornar Valores desde loop
fn main() {
let mut contador = 0;
// loop puede retornar un valor
let resultado = loop {
contador += 1;
if contador == 10 {
break contador * 2; // Retorna el valor
}
};
println!("El resultado es: {resultado}"); // 20
}
Etiquetas de Bucle
fn main() {
let mut cuenta = 0;
// Etiquetas para bucles anidados
'externo: loop {
println!("Bucle externo: {cuenta}");
let mut restante = 10;
loop {
println!(" Bucle interno: {restante}");
if restante == 9 {
break; // Sale solo del bucle interno
}
if cuenta == 2 {
break 'externo; // Sale del bucle externo
}
restante -= 1;
}
cuenta += 1;
}
println!("Fin de bucles anidados");
}
while - Bucle Condicional
fn main() {
let mut numero = 3;
// Bucle while básico
while numero != 0 {
println!("{numero}!");
numero -= 1;
}
println!("¡DESPEGUE!");
// while con condiciones complejas
let mut x = 1;
while x < 100 {
x *= 2;
println!("x = {x}");
}
// Cuidado con bucles infinitos
let mut bandera = true;
let mut iteraciones = 0;
while bandera {
iteraciones += 1;
println!("Iteración: {iteraciones}");
if iteraciones >= 5 {
bandera = false; // Evita bucle infinito
}
}
}
for - Iteración sobre Colecciones
fn main() {
// Iterar sobre array
let array = [10, 20, 30, 40, 50];
for elemento in array {
println!("Elemento: {elemento}");
}
// Iterar con índices
for (indice, valor) in array.iter().enumerate() {
println!("Índice {indice}: {valor}");
}
// Rangos
for numero in 1..4 { // Excluye el 4
println!("Número: {numero}"); // 1, 2, 3
}
for numero in 1..=4 { // Incluye el 4
println!("Número inclusivo: {numero}"); // 1, 2, 3, 4
}
// Rango inverso
for numero in (1..4).rev() {
println!("Número reverso: {numero}"); // 3, 2, 1
}
}
Patrones de Iteración Comunes
fn main() {
let datos = vec![1, 2, 3, 4, 5];
// 1. Iterar por valor (consume la colección)
for item in datos.clone() {
println!("Por valor: {item}");
}
// 2. Iterar por referencia inmutable
for item in &datos {
println!("Por referencia: {item}");
}
// 3. Iterar por referencia mutable
let mut datos_mut = vec![1, 2, 3, 4, 5];
for item in &mut datos_mut {
*item *= 2; // Modificar cada elemento
}
println!("Datos modificados: {:?}", datos_mut);
// 4. Usar collect para crear nueva colección
let cuadrados: Vec<i32> = (1..6)
.map(|x| x * x)
.collect();
println!("Cuadrados: {:?}", cuadrados);
}
Pattern Matching con match
match es una característica poderosa de Rust para pattern matching:
match Básico
fn main() {
let numero = 3;
match numero {
1 => println!("Uno"),
2 => println!("Dos"),
3 => println!("Tres"),
4 => println!("Cuatro"),
5 => println!("Cinco"),
_ => println!("Otro número"), // Comodín (catch-all)
}
}
match como Expresión
fn main() {
let numero = 6;
let descripcion = match numero {
1 => "uno",
2 => "dos",
3 => "tres",
n if n > 10 => "número grande",
_ => "número normal",
};
println!("El número {numero} es: {descripcion}");
}
Patrones Múltiples
fn main() {
let valor = 4;
match valor {
1 | 2 => println!("Uno o dos"),
3..=5 => println!("Entre tres y cinco"),
6 | 7 | 8 => println!("Seis, siete u ocho"),
_ => println!("Otro valor"),
}
}
Destructuring con match
fn main() {
// Con tuplas
let punto = (3, 5);
match punto {
(0, 0) => println!("Origen"),
(0, y) => println!("En el eje Y: {y}"),
(x, 0) => println!("En el eje X: {x}"),
(x, y) => println!("Punto ({x}, {y})"),
}
// Con arrays
let array = [1, 2, 3];
match array {
[a, 2, c] => println!("Segundo elemento es 2: [{a}, 2, {c}]"),
[a, b, c] => println!("Array: [{a}, {b}, {c}]"),
}
}
Guards en match
fn main() {
let numero = Some(4);
match numero {
Some(x) if x < 5 => println!("Menor que cinco: {x}"),
Some(x) => println!("Mayor o igual a cinco: {x}"),
None => println!("No hay valor"),
}
// Ejemplo más complejo
let par_x_y = (4, -2);
match par_x_y {
(x, y) if x == y => println!("Estos son iguales"),
(x, y) if x + y == 0 => println!("Se suman cero"),
(x, _) if x % 2 == 0 => println!("X es par"),
_ => println!("Sin patrón especial"),
}
}
if let - Sintaxis Concisa
Para casos donde solo te interesa un patrón específico:
fn main() {
let config_max = Some(3u8);
// Con match
match config_max {
Some(max) => println!("El máximo está configurado a {max}"),
_ => (),
}
// Con if let (más conciso)
if let Some(max) = config_max {
println!("El máximo está configurado a {max}");
}
// Combinado con else
let numero = Some(7);
if let Some(n) = numero {
if n > 5 {
println!("Número grande: {n}");
}
} else {
println!("No hay número");
}
}
while let - Bucle Condicional con Pattern Matching
fn main() {
let mut pila = Vec::new();
pila.push(1);
pila.push(2);
pila.push(3);
// Procesar hasta que la pila esté vacía
while let Some(tope) = pila.pop() {
println!("Procesando: {tope}");
}
println!("Pila vacía");
}
Ejercicios Prácticos
Ejercicio 1: Juego de Adivinanza
fn main() {
let numero_secreto = 7;
let mut intentos = 0;
let max_intentos = 5;
loop {
intentos += 1;
// Simular adivinanza (normalmente leerías input del usuario)
let adivinanza = match intentos {
1 => 3,
2 => 9,
3 => 7,
_ => 5,
};
println!("Intento {intentos}: {adivinanza}");
match adivinanza {
n if n == numero_secreto => {
println!("¡Ganaste en {intentos} intentos!");
break;
},
n if n < numero_secreto => println!("Muy bajo"),
n if n > numero_secreto => println!("Muy alto"),
_ => unreachable!(),
}
if intentos >= max_intentos {
println!("Se agotaron los intentos. El número era {numero_secreto}");
break;
}
}
}
Ejercicio 2: Clasificador de Caracteres
fn main() {
let texto = "Hola Rust 123!";
let mut letras = 0;
let mut numeros = 0;
let mut espacios = 0;
let mut otros = 0;
for caracter in texto.chars() {
match caracter {
'a'..='z' | 'A'..='Z' => letras += 1,
'0'..='9' => numeros += 1,
' ' => espacios += 1,
_ => otros += 1,
}
}
println!("Análisis de '{texto}':");
println!("Letras: {letras}");
println!("Números: {numeros}");
println!("Espacios: {espacios}");
println!("Otros: {otros}");
}
Ejercicio 3: Tabla de Multiplicar
fn main() {
let numero = 7;
let limite = 10;
println!("Tabla de multiplicar del {numero}:");
println!("------------------------");
for i in 1..=limite {
let resultado = numero * i;
// Formatting con match
let descripcion = match i {
1 => "primera",
2 => "segunda",
3 => "tercera",
n if n <= 10 => "normal",
_ => "alta",
};
println!("{numero} x {i:2} = {resultado:3} ({descripcion} fila)");
}
}
Ejercicio 4: Contador de Palabras
fn main() {
let frase = "Rust es un lenguaje de programación moderno y seguro";
let palabras: Vec<&str> = frase.split_whitespace().collect();
println!("Frase: '{frase}'");
println!("Palabras encontradas: {}", palabras.len());
println!();
for (indice, palabra) in palabras.iter().enumerate() {
let categoria = match palabra.len() {
1..=3 => "corta",
4..=6 => "media",
7..=10 => "larga",
_ => "muy larga",
};
println!("Palabra {}: '{}' - {} caracteres ({})",
indice + 1, palabra, palabra.len(), categoria);
}
// Estadísticas
let palabra_mas_larga = palabras.iter()
.max_by_key(|palabra| palabra.len())
.unwrap();
let palabra_mas_corta = palabras.iter()
.min_by_key(|palabra| palabra.len())
.unwrap();
println!("\nEstadísticas:");
println!("Palabra más larga: '{palabra_mas_larga}' ({} chars)", palabra_mas_larga.len());
println!("Palabra más corta: '{palabra_mas_corta}' ({} chars)", palabra_mas_corta.len());
}
Puntos Clave para Recordar
if es una expresión - puede retornar valores
loop crea bucles infinitos - usa break para salir
while ejecuta mientras la condición sea verdadera
for es ideal para iterar sobre colecciones y rangos
match debe ser exhaustivo - cubrir todos los casos posibles
- Usa
_ como comodín para casos no manejados explícitamente
if let y while let son útiles para casos específicos
- Los guards (
if) añaden condiciones adicionales a los patrones
- Las etiquetas de bucle ayudan con bucles anidados
Próximo Paso
En el siguiente capítulo exploraremos uno de los conceptos más importantes y únicos de Rust: el Ownership (sistema de propiedad). Este sistema es lo que permite a Rust garantizar seguridad de memoria sin un recolector de basura.