Rust
Tutorial Rust: Estructuras de control condicional
Aprende las estructuras de control condicional en Rust con if/else como expresiones y match para un código seguro y expresivo.
Aprende Rust y certifícateif/else como expresiones
En Rust, una de las características más distintivas es que las estructuras de control como if/else
no son simples sentencias que ejecutan bloques de código, sino que son expresiones que pueden devolver valores. Esta característica fundamental diferencia a Rust de muchos otros lenguajes y permite escribir código más conciso y expresivo.
Cuando hablamos de expresiones en Rust, nos referimos a fragmentos de código que producen un valor. A diferencia de las sentencias, que simplemente ejecutan acciones sin devolver resultados. Esta distinción es crucial para entender cómo funcionan los condicionales en Rust.
Estructura básica de if/else como expresiones
La sintaxis básica de un condicional if/else
en Rust es similar a la de otros lenguajes:
if condicion {
// código si la condición es verdadera
} else {
// código si la condición es falsa
}
Sin embargo, lo que hace especial a Rust es que esta estructura completa puede evaluarse a un valor, permitiendo asignarlo a una variable:
let numero = 5;
let mensaje = if numero > 0 {
"positivo"
} else {
"no positivo"
};
println!("El número es {}", mensaje); // Imprime: El número es positivo
En este ejemplo, la variable mensaje
recibe el valor que resulta de evaluar la expresión if/else
. Observa que no necesitamos usar return
ni ninguna otra palabra clave especial.
Reglas importantes para if/else como expresiones
Cuando usamos if/else
como expresiones, debemos seguir algunas reglas importantes:
- Todas las ramas deben devolver valores del mismo tipo. El compilador de Rust verificará esto estrictamente.
- La última expresión de cada bloque (sin punto y coma) es el valor que devuelve ese bloque.
- Si una expresión termina con punto y coma, se convierte en una sentencia y devuelve
()
(unit type, similar a void).
Veamos un ejemplo que ilustra estas reglas:
let numero = 10;
let resultado = if numero > 5 {
"mayor que cinco" // Sin punto y coma, devuelve este valor
} else {
"cinco o menos" // Sin punto y coma, devuelve este valor
};
// Esto NO compilaría porque los tipos no coinciden:
// let incorrecto = if numero > 5 {
// 42 // Tipo: i32
// } else {
// "cinco o menos" // Tipo: &str
// };
Múltiples ramas con else if
Podemos encadenar múltiples condiciones usando else if
, y toda la estructura sigue siendo una expresión:
let temperatura = 25;
let descripcion = if temperatura < 0 {
"congelante"
} else if temperatura < 10 {
"muy frío"
} else if temperatura < 20 {
"fresco"
} else if temperatura < 30 {
"agradable"
} else {
"caluroso"
};
println!("El clima es {}", descripcion); // Imprime: El clima es agradable
Uso de bloques de código más complejos
Cada rama de un if/else
puede contener bloques de código con múltiples expresiones. En este caso, el valor de la última expresión (sin punto y coma) será el valor devuelto por esa rama:
let numero = 42;
let descripcion = if numero > 100 {
let mitad = numero / 2;
println!("Es un número grande, su mitad es {}", mitad);
"número grande" // Esta es la expresión que se devuelve
} else {
let doble = numero * 2;
println!("Es un número pequeño, su doble es {}", doble);
"número pequeño" // Esta es la expresión que se devuelve
};
println!("Descripción: {}", descripcion); // Imprime: Descripción: número pequeño
Uso práctico en inicialización condicional
Una aplicación común de if/else
como expresiones es la inicialización condicional de variables:
let limite = 100;
let valor_ingresado = 120;
let valor_validado = if valor_ingresado <= limite {
valor_ingresado
} else {
limite
};
println!("Valor validado: {}", valor_validado); // Imprime: Valor validado: 100
Este patrón es muy útil para validar entradas o establecer valores predeterminados de manera concisa.
Combinación con operadores lógicos
Podemos combinar condiciones usando operadores lógicos como &&
(AND) y ||
(OR):
let edad = 25;
let tiene_licencia = true;
let puede_conducir = if edad >= 18 && tiene_licencia {
true
} else {
false
};
println!("¿Puede conducir? {}", puede_conducir); // Imprime: ¿Puede conducir? true
Expresiones if sin else
También podemos usar if
sin else
como una expresión, pero en este caso, la rama implícita else
devuelve ()
(unit type):
let x = 10;
let resultado = if x > 5 {
"mayor que cinco"
};
// resultado será de tipo Option<&str>, con valor Some("mayor que cinco")
// Si la condición fuera falsa, resultado sería ()
Sin embargo, este uso es menos común y generalmente se prefiere incluir siempre una rama else
cuando se usa if
como expresión para asignar valores.
El enfoque de Rust de tratar los condicionales como expresiones hace que el código sea más declarativo y conciso, eliminando la necesidad de construcciones especiales como el operador ternario (?:
) presente en otros lenguajes.
Condicionales inline
Los condicionales inline en Rust son una forma concisa de utilizar expresiones if/else
en una sola línea de código. Esta técnica aprovecha la naturaleza expresiva de los condicionales en Rust para crear código más compacto y legible en situaciones donde la lógica condicional es simple.
La sintaxis básica de un condicional inline sigue este patrón:
let resultado = if condicion { valor_si_verdadero } else { valor_si_falso };
Esta forma compacta es especialmente útil cuando queremos asignar un valor a una variable basándonos en una condición simple. Veamos algunos ejemplos prácticos:
let edad = 20;
let estado = if edad >= 18 { "adulto" } else { "menor" };
println!("La persona es {}", estado); // Imprime: La persona es adulto
En este ejemplo, la variable estado
recibe un valor diferente dependiendo de si la condición edad >= 18
es verdadera o falsa, todo en una sola línea de código.
Reemplazo del operador ternario
A diferencia de lenguajes como C, JavaScript o Java, Rust no tiene un operador ternario (condicion ? valor_si_verdadero : valor_si_falso
). Esto es porque los condicionales inline con if/else
cumplen exactamente la misma función de manera más coherente con el resto de la sintaxis del lenguaje.
Comparemos cómo se vería el mismo código en JavaScript y en Rust:
JavaScript:
// JavaScript con operador ternario
let puntuacion = 85;
let resultado = puntuacion >= 60 ? "aprobado" : "reprobado";
Rust:
// Rust con condicional inline
let puntuacion = 85;
let resultado = if puntuacion >= 60 { "aprobado" } else { "reprobado" };
La versión de Rust es igualmente concisa pero mantiene la consistencia con el resto de las estructuras del lenguaje.
Uso en expresiones más complejas
Los condicionales inline pueden formar parte de expresiones más complejas, como argumentos de funciones o parte de otras operaciones:
let x = 5;
let y = 10;
println!("El mayor es: {}", if x > y { x } else { y }); // Imprime: El mayor es: 10
let suma = (if x > 0 { x } else { 0 }) + y;
println!("Suma: {}", suma); // Imprime: Suma: 15
En el primer ejemplo, el condicional inline se usa directamente como argumento de la macro println!
. En el segundo, forma parte de una expresión aritmética.
Anidación de condicionales inline
Aunque es posible anidar condicionales inline, esto puede reducir la legibilidad rápidamente:
let valor = 42;
// Anidación de condicionales inline (evitar si afecta la legibilidad)
let descripcion = if valor < 0 {
"negativo"
} else if valor == 0 {
"cero"
} else if valor < 10 {
"dígito positivo"
} else {
"número positivo grande"
};
println!("{} es {}", valor, descripcion); // Imprime: 42 es número positivo grande
Cuando la lógica condicional se vuelve más compleja, es mejor usar la estructura de bloques tradicional o considerar el uso de match
para mayor claridad.
Uso con operaciones aritméticas
Los condicionales inline son útiles para realizar operaciones aritméticas condicionales:
let temperatura = -5;
let ajuste = if temperatura < 0 { 10 } else { 5 };
let temperatura_ajustada = temperatura + ajuste;
println!("Temperatura ajustada: {}", temperatura_ajustada); // Imprime: Temperatura ajustada: 5
Condicionales inline en asignaciones
Una aplicación común es la asignación condicional de valores dentro de un rango específico:
let entrada_usuario = 150;
let maximo_permitido = 100;
// Limitar un valor a un máximo
let valor_validado = if entrada_usuario <= maximo_permitido { entrada_usuario } else { maximo_permitido };
println!("Valor validado: {}", valor_validado); // Imprime: Valor validado: 100
Mejores prácticas
Al usar condicionales inline, es importante seguir estas recomendaciones:
- Úsalos solo para lógica condicional simple y clara.
- Evita anidar demasiados condicionales inline, ya que reduce la legibilidad.
- Mantén las expresiones dentro de cada rama cortas y concisas.
- Si la lógica se vuelve compleja, considera usar la forma de bloque tradicional o
match
.
// Buena práctica: condicional inline simple
let es_par = if numero % 2 == 0 { true } else { false };
// Mejor práctica para casos más complejos: usar bloques
let categoria = if puntuacion < 60 {
"insuficiente"
} else if puntuacion < 70 {
"suficiente"
} else if puntuacion < 90 {
"notable"
} else {
"sobresaliente"
};
Los condicionales inline son una herramienta poderosa para escribir código Rust más conciso y expresivo cuando se usan apropiadamente. Aprovechan la naturaleza de expresión de los condicionales en Rust para permitir asignaciones y evaluaciones en una forma compacta sin sacrificar la claridad del código.
match fundamentals
La expresión match
es una de las características más potentes de Rust para el control de flujo. A diferencia de los condicionales if/else
que evalúan expresiones booleanas, match
permite comparar un valor contra una serie de patrones y ejecutar código basado en qué patrón coincide.
Estructura básica de match
La sintaxis básica de match
sigue este formato:
match valor_a_comparar {
patron1 => expresion1,
patron2 => expresion2,
patron3 => {
// Bloque de código para casos más complejos
expresion3
},
// Más patrones...
_ => expresion_predeterminada,
}
Cada línea dentro del bloque match
se llama un brazo (arm) y consiste en un patrón seguido de =>
y la expresión a ejecutar cuando ese patrón coincide.
Veamos un ejemplo simple:
let numero = 3;
let texto = match numero {
1 => "uno",
2 => "dos",
3 => "tres",
4 => "cuatro",
5 => "cinco",
_ => "otro número",
};
println!("{} en texto es: {}", numero, texto); // Imprime: 3 en texto es: tres
En este ejemplo, match
compara el valor de numero
con cada patrón (los números del 1 al 5) y devuelve el texto correspondiente cuando encuentra una coincidencia.
El patrón comodín (_)
El patrón _
(guion bajo) es el comodín que coincide con cualquier valor que no haya coincidido con los patrones anteriores. Es similar al default
en un switch
de otros lenguajes:
let codigo_dia = 6;
let dia = match codigo_dia {
1 => "lunes",
2 => "martes",
3 => "miércoles",
4 => "jueves",
5 => "viernes",
6 => "sábado",
7 => "domingo",
_ => "día inválido",
};
println!("El día es: {}", dia); // Imprime: El día es: sábado
Exhaustividad: una característica de seguridad
Una de las ventajas más importantes de match
en Rust es que el compilador verifica que todos los posibles valores sean manejados. Esto se conoce como exhaustividad y evita errores comunes donde olvidamos manejar ciertos casos:
let booleano = true;
// Este match debe cubrir todos los posibles valores de un booleano
let mensaje = match booleano {
true => "Es verdadero",
false => "Es falso",
// No necesitamos _ aquí porque ya cubrimos todos los casos posibles
};
println!("{}", mensaje); // Imprime: Es verdadero
Si intentáramos omitir alguno de los casos en el ejemplo anterior, el compilador nos mostraría un error indicando que el match
no es exhaustivo.
Capturando valores en patrones
Podemos capturar el valor que coincide con un patrón y usarlo en la expresión asociada:
let numero = 15;
let descripcion = match numero {
0 => "cero",
1 => "uno",
n if n < 0 => "negativo",
n if n < 10 => "dígito simple",
n => {
let mensaje = format!("número grande: {}", n);
mensaje // Devuelve esta expresión
}
};
println!("{} es {}", numero, descripcion); // Imprime: 15 es número grande: 15
En este ejemplo, n
captura el valor de numero
cuando coincide con los patrones condicionales o el patrón por defecto.
Patrones con guardas
Los guardas son condiciones adicionales que se pueden añadir a un patrón usando if
:
let temperatura = 25;
let sensacion = match temperatura {
t if t < 0 => "congelante",
t if t < 10 => "muy frío",
t if t < 20 => "fresco",
t if t < 30 => "agradable",
t if t < 40 => "caluroso",
_ => "extremadamente caluroso",
};
println!("La sensación térmica es: {}", sensacion); // Imprime: La sensación térmica es: agradable
Los guardas permiten crear patrones más específicos basados en condiciones arbitrarias.
Múltiples patrones con el operador |
Podemos usar el operador |
(OR) para hacer coincidir múltiples patrones en un solo brazo:
let dia_semana = 6;
let tipo_dia = match dia_semana {
1 | 2 | 3 | 4 | 5 => "día laborable",
6 | 7 => "fin de semana",
_ => "día inválido",
};
println!("El día {} es {}", dia_semana, tipo_dia); // Imprime: El día 6 es fin de semana
Rangos en patrones
Para rangos numéricos consecutivos, podemos usar la sintaxis de rango ..=
:
let calificacion = 85;
let resultado = match calificacion {
0..=59 => "suspenso",
60..=69 => "aprobado",
70..=79 => "notable",
80..=89 => "sobresaliente",
90..=100 => "matrícula de honor",
_ => "calificación inválida",
};
println!("Con {} puntos: {}", calificacion, resultado); // Imprime: Con 85 puntos: sobresaliente
Comparación con if/else
Aunque if/else
y match
pueden resolver problemas similares, match
brinda varias ventajas:
- Es más expresivo para comparar un valor contra múltiples posibilidades
- Garantiza exhaustividad (manejar todos los casos posibles)
- Permite patrones más sofisticados que simples comparaciones booleanas
Comparemos ambos enfoques:
// Usando if/else
let numero = 3;
let texto;
if numero == 1 {
texto = "uno";
} else if numero == 2 {
texto = "dos";
} else if numero == 3 {
texto = "tres";
} else {
texto = "otro número";
}
// Usando match (más conciso y expresivo)
let numero = 3;
let texto = match numero {
1 => "uno",
2 => "dos",
3 => "tres",
_ => "otro número",
};
Uso de match en expresiones
Al igual que if/else
, match
es una expresión que devuelve un valor, por lo que podemos usarlo en asignaciones o como parte de expresiones más complejas:
let codigo_error = 404;
let mensaje = format!(
"Error {}: {}",
codigo_error,
match codigo_error {
404 => "No encontrado",
403 => "Prohibido",
500 => "Error interno del servidor",
_ => "Error desconocido",
}
);
println!("{}", mensaje); // Imprime: Error 404: No encontrado
Patrones con variables
Podemos usar variables en los patrones para capturar valores específicos:
let puntos = 75;
let mensaje = match puntos {
0 => "Sin puntos",
1 => "Un punto",
n @ 2..=10 => format!("Pocos puntos: {}", n),
n @ 11..=50 => format!("Puntos moderados: {}", n),
n @ 51..=100 => format!("Muchos puntos: {}", n),
n => format!("Demasiados puntos: {}", n),
};
println!("{}", mensaje); // Imprime: Muchos puntos: 75
En este ejemplo, usamos la sintaxis n @ patrón
para capturar en la variable n
el valor que coincide con el patrón.
Consideraciones de rendimiento
El compilador de Rust optimiza las expresiones match
para que sean muy eficientes. En muchos casos, especialmente con tipos simples como enteros o enumeraciones, el código generado es tan eficiente como una tabla de saltos o una serie de comparaciones directas.
La expresión match
es una herramienta fundamental en Rust que combina potencia, seguridad y expresividad. A medida que avances en tu aprendizaje de Rust, descubrirás usos más sofisticados de match
con tipos de datos más complejos, pero incluso con estos fundamentos básicos, ya puedes aprovechar su potencial para escribir código más claro y robusto.
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 condicional 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 que en Rust las estructuras if/else son expresiones que devuelven valores.
- Aprender a usar condicionales inline para asignaciones concisas.
- Conocer la sintaxis y uso básico de la expresión match para control de flujo.
- Entender la exhaustividad y patrones en match, incluyendo guardas y rangos.
- Comparar el uso de if/else y match para diferentes escenarios de control condicional.