Rust
Tutorial Rust: Pattern Matching
Aprende pattern matching exhaustivo, destructuring y uso de if let y while let en Rust para manejar datos complejos de forma segura y concisa.
Aprende Rust y certifícatematch exhaustivo
El pattern matching (coincidencia de patrones) es una de las características más potentes de Rust, permitiéndonos examinar y descomponer valores complejos de manera segura y expresiva. En el centro de esta funcionalidad se encuentra la expresión match
, que nos permite comparar un valor contra una serie de patrones y ejecutar código basado en qué patrón coincide.
La expresión match
en Rust destaca por una propiedad fundamental: la exhaustividad. Esto significa que Rust verifica en tiempo de compilación que todos los posibles casos sean manejados, evitando errores en tiempo de ejecución por casos no contemplados.
Sintaxis básica de match
La estructura básica de una expresión match
es la siguiente:
match valor_a_comparar {
patrón1 => expresión1,
patrón2 => expresión2,
patrón3 => {
// Bloque de código para casos más complejos
let resultado = hacer_algo();
resultado
},
// Más patrones...
}
Cada rama de un match
consiste en un patrón seguido de =>
y la expresión o bloque de código a ejecutar cuando ese patrón coincide. El valor resultante de la expresión match
será el valor de la rama que coincida.
Exhaustividad: la seguridad de no olvidar casos
La característica más importante de match
es que el compilador de Rust verifica que todos los posibles valores sean manejados. Esto previene errores comunes como olvidar manejar un caso específico.
Veamos un ejemplo con un enum simple:
enum Semaforo {
Rojo,
Amarillo,
Verde,
}
fn accion_segun_semaforo(color: Semaforo) {
match color {
Semaforo::Rojo => println!("¡Detente!"),
Semaforo::Verde => println!("Adelante"),
// Si omitimos el caso Amarillo, el compilador mostrará un error
}
}
Si intentamos compilar este código, Rust nos mostrará un error indicando que no hemos manejado todos los casos posibles:
error[E0004]: non-exhaustive patterns: `Semaforo::Amarillo` not covered
Para corregirlo, debemos incluir todos los casos:
fn accion_segun_semaforo(color: Semaforo) {
match color {
Semaforo::Rojo => println!("¡Detente!"),
Semaforo::Amarillo => println!("¡Precaución!"),
Semaforo::Verde => println!("Adelante"),
}
}
El patrón comodín (_)
En situaciones donde tenemos muchos casos posibles y solo nos interesan algunos específicos, podemos usar el patrón comodín _
para manejar todos los casos restantes:
enum Moneda {
Euro,
Dolar,
Libra,
Yen,
Bitcoin,
}
fn valor_en_euros(moneda: Moneda) -> f64 {
match moneda {
Moneda::Euro => 1.0,
Moneda::Dolar => 0.85,
Moneda::Libra => 1.17,
_ => {
println!("Conversión aproximada para otras monedas");
0.5 // Valor por defecto para otras monedas
}
}
}
El patrón _
captura todos los casos que no fueron manejados explícitamente, manteniendo la exhaustividad del match
.
Match con valores literales
También podemos hacer coincidir valores literales como números o caracteres:
fn describir_numero(n: i32) -> &'static str {
match n {
0 => "cero",
1 => "uno",
2 => "dos",
_ if n < 0 => "negativo",
_ => "otro número positivo",
}
}
Observa cómo usamos una condición de guarda (if n < 0
) para refinar aún más nuestros patrones.
Patrones con rangos
Rust permite usar rangos en los patrones para simplificar el código:
fn calificar_examen(puntuacion: u8) -> &'static str {
match puntuacion {
0..=49 => "Suspenso",
50..=69 => "Aprobado",
70..=89 => "Notable",
90..=100 => "Sobresaliente",
_ => "Puntuación inválida",
}
}
El operador ..=
crea un rango inclusivo, permitiéndonos manejar grupos de valores de forma concisa.
Match con enums que contienen datos
El verdadero poder de match
se aprecia cuando trabajamos con enums que contienen datos:
enum Contenido {
Texto(String),
Numero(i32),
Lista(Vec<String>),
Nada,
}
fn procesar_contenido(contenido: Contenido) {
match contenido {
Contenido::Texto(texto) => {
println!("Contenido de texto: {}", texto);
}
Contenido::Numero(n) if n > 0 => {
println!("Número positivo: {}", n);
}
Contenido::Numero(n) => {
println!("Número no positivo: {}", n);
}
Contenido::Lista(items) if items.is_empty() => {
println!("Lista vacía");
}
Contenido::Lista(items) => {
println!("Lista con {} elementos", items.len());
}
Contenido::Nada => {
println!("Sin contenido");
}
}
}
En este ejemplo, no solo verificamos qué variante del enum tenemos, sino que también extraemos y utilizamos los datos contenidos en cada variante.
Múltiples patrones con el operador |
Podemos combinar varios patrones en una sola rama usando el operador |
(OR):
fn es_vocal(c: char) -> bool {
match c {
'a' | 'e' | 'i' | 'o' | 'u' |
'A' | 'E' | 'I' | 'O' | 'U' => true,
_ => false,
}
}
Capturando el valor en el patrón comodín
Si necesitamos usar el valor capturado por el patrón comodín, podemos darle un nombre:
enum Mensaje {
Saludo(String),
Despedida,
Personalizado { contenido: String, urgente: bool },
}
fn procesar_mensaje(msg: Mensaje) {
match msg {
Mensaje::Saludo(nombre) => println!("¡Hola, {}!", nombre),
Mensaje::Despedida => println!("¡Hasta luego!"),
otro_mensaje => {
println!("Procesando otro tipo de mensaje");
// Podemos usar 'otro_mensaje' aquí
}
}
}
Beneficios de la exhaustividad
La exhaustividad de match
proporciona varios beneficios importantes:
- Seguridad: El compilador garantiza que todos los casos posibles sean manejados.
- Mantenibilidad: Si añadimos una nueva variante a un enum, el compilador nos indicará todos los lugares donde debemos actualizar nuestro código.
- Documentación implícita: El código muestra claramente todos los casos que se están considerando.
// Si añadimos una nueva variante al enum Semaforo:
enum Semaforo {
Rojo,
Amarillo,
Verde,
Intermitente, // Nueva variante
}
// El compilador nos obligará a actualizar todas las expresiones match
// que usen este enum, evitando errores por casos no contemplados
Patrones @ para vincular valores
El operador @
nos permite vincular un valor a un nombre mientras verificamos que coincida con un patrón:
fn describir_numero_en_rango(x: i32) -> &'static str {
match x {
n @ 0..=9 => {
println!("Tenemos un dígito: {}", n);
"dígito"
}
n @ 10..=99 => {
println!("Tenemos un número de dos cifras: {}", n);
"dos cifras"
}
n => {
println!("Tenemos un número grande: {}", n);
"grande"
}
}
}
El patrón match
exhaustivo es la base del sistema de pattern matching en Rust, proporcionando una forma segura y expresiva de trabajar con datos complejos. En las siguientes secciones, veremos cómo podemos aplicar estos conceptos para desestructurar datos y utilizar formas más concisas de pattern matching con if let
y while let
.
Destructuring
El destructuring (desestructuración) es una técnica elegante que nos permite extraer y descomponer valores de estructuras de datos complejas en Rust. Esta característica nos permite acceder a partes específicas de structs, enums, tuplas y arrays de forma concisa y expresiva.
La desestructuración funciona como un espejo de la construcción: si sabemos cómo se estructura un valor, podemos desarmarlo siguiendo ese mismo patrón. Esto resulta particularmente útil cuando solo necesitamos trabajar con algunas partes de una estructura de datos compleja.
Desestructuración de tuplas
Las tuplas son una de las estructuras más simples que podemos desestructurar:
fn main() {
let punto = (10, 20);
// Desestructuración básica
let (x, y) = punto;
println!("Coordenadas: x={}, y={}", x, y);
// También funciona con diferentes tipos
let info = ("Rust", 2015, true);
let (nombre, año, estable) = info;
println!("{} fue lanzado en {} y estable={}", nombre, año, estable);
}
Podemos ignorar elementos específicos usando el patrón comodín _
:
let (x, _, z) = (1, 2, 3); // Ignoramos el valor del medio
println!("x={}, z={}", x, z);
Desestructuración de structs
La desestructuración de structs nos permite extraer campos específicos de manera elegante:
struct Persona {
nombre: String,
edad: u32,
ciudad: String,
}
fn main() {
let usuario = Persona {
nombre: String::from("Ana"),
edad: 28,
ciudad: String::from("Madrid"),
};
// Extraemos solo los campos que necesitamos
let Persona { nombre, edad, .. } = usuario;
println!("{} tiene {} años", nombre, edad);
// También podemos asignar a variables con nombres diferentes
let Persona { nombre: n, edad: e, ciudad: c } = usuario;
println!("Nombre: {}, Edad: {}, Ciudad: {}", n, e, c);
}
El operador ..
nos permite ignorar los campos restantes que no nos interesan, lo que resulta útil en structs con muchos campos.
Desestructuración de enums
La desestructuración de enums es donde realmente brilla esta característica, permitiéndonos extraer los datos contenidos en las variantes:
enum Mensaje {
Texto(String),
Coordenadas(i32, i32),
Configuracion { id: u32, visible: bool },
Vacio,
}
fn procesar_mensaje(msg: Mensaje) {
match msg {
Mensaje::Texto(contenido) => {
println!("Mensaje de texto: {}", contenido);
},
Mensaje::Coordenadas(x, y) => {
println!("Ubicación: ({}, {})", x, y);
},
Mensaje::Configuracion { id, visible } => {
println!("Configuración #{}: visible={}", id, visible);
},
Mensaje::Vacio => {
println!("No hay contenido");
},
}
}
Observa cómo la sintaxis de desestructuración se adapta a cada variante del enum, ya sea que contenga una tupla, múltiples valores o un struct anónimo.
Desestructuración anidada
Podemos combinar patrones para desestructurar estructuras anidadas:
struct Punto {
x: i32,
y: i32,
}
enum Figura {
Circulo { centro: Punto, radio: f64 },
Rectangulo { esquina_sup_izq: Punto, esquina_inf_der: Punto },
}
fn describir_figura(figura: Figura) {
match figura {
Figura::Circulo {
centro: Punto { x, y },
radio
} => {
println!("Círculo en ({}, {}) con radio {}", x, y, radio);
},
Figura::Rectangulo {
esquina_sup_izq: Punto { x: x1, y: y1 },
esquina_inf_der: Punto { x: x2, y: y2 }
} => {
println!("Rectángulo desde ({}, {}) hasta ({}, {})", x1, y1, x2, y2);
},
}
}
Este patrón es extremadamente útil cuando trabajamos con estructuras de datos complejas y anidadas, permitiéndonos acceder directamente a los valores más profundos.
Desestructuración en parámetros de funciones
Podemos aplicar la desestructuración directamente en los parámetros de funciones:
struct Config {
max_conexiones: u32,
timeout: u64,
debug: bool,
}
// Desestructuración en la firma de la función
fn iniciar_servidor(Config { max_conexiones, timeout, debug }: Config) {
println!("Iniciando servidor con:");
println!(" Max conexiones: {}", max_conexiones);
println!(" Timeout: {} ms", timeout);
println!(" Modo debug: {}", debug);
}
// Uso:
let config = Config {
max_conexiones: 100,
timeout: 30000,
debug: true,
};
iniciar_servidor(config);
Esta técnica hace que el código sea más legible al mostrar claramente qué partes de la estructura se utilizan dentro de la función.
Desestructuración parcial con ref y mut
Podemos usar las palabras clave ref
y mut
para controlar cómo se vinculan los valores durante la desestructuración:
struct Datos {
valor: String,
contador: u32,
}
fn main() {
let datos = Datos {
valor: String::from("ejemplo"),
contador: 5,
};
// 'ref' para obtener referencias en lugar de mover valores
let Datos { ref valor, contador } = datos;
println!("Valor: {}, Contador: {}", valor, contador);
// 'valor' es &String, 'contador' es u32
// Combinando 'ref' y 'mut'
let mut datos_mut = datos;
let Datos { ref mut valor, contador: c } = datos_mut;
valor.push_str(" modificado");
println!("Nuevo valor: {}, Contador: {}", valor, c);
}
Esto es especialmente útil cuando queremos evitar mover valores o cuando necesitamos modificarlos durante la desestructuración.
Desestructuración de slices y arrays
También podemos desestructurar arrays y slices:
fn main() {
let numeros = [1, 2, 3, 4, 5];
// Extraer elementos específicos
let [primero, segundo, .., ultimo] = numeros;
println!("Primero: {}, Segundo: {}, Último: {}", primero, segundo, ultimo);
// Capturar un slice del medio
let [cabeza, medio @ .., cola] = numeros;
println!("Cabeza: {}, Cola: {}, Medio: {:?}", cabeza, cola, medio);
}
El operador @
nos permite vincular un nombre a un subpatrón, lo que resulta útil para capturar slices dentro de arrays.
Patrones de desestructuración en bucles for
La desestructuración también funciona en los bucles for, lo que facilita trabajar con colecciones de tuplas o estructuras:
fn main() {
let puntos = vec![(0, 0), (1, 5), (10, -3)];
// Desestructuración en el bucle for
for (x, y) in puntos {
println!("Punto en ({}, {}), distancia al origen: {:.2}",
x, y, ((x*x + y*y) as f64).sqrt());
}
// Con estructuras
let usuarios = vec![
Persona { nombre: String::from("Carlos"), edad: 32, ciudad: String::from("Barcelona") },
Persona { nombre: String::from("Elena"), edad: 25, ciudad: String::from("Valencia") },
];
for Persona { nombre, edad, .. } in usuarios {
println!("{} tiene {} años", nombre, edad);
}
}
Esta técnica hace que el código sea más limpio y expresivo al evitar accesos repetitivos a los campos o elementos.
Desestructuración con patrones OR
Podemos combinar la desestructuración con el operador |
para manejar múltiples patrones:
enum Dispositivo {
Movil { marca: String, modelo: String, año: u32 },
Tablet { marca: String, tamaño: f32 },
Laptop { marca: String, ram: u32 },
}
fn es_apple(dispositivo: &Dispositivo) -> bool {
match dispositivo {
Dispositivo::Movil { marca, .. } |
Dispositivo::Tablet { marca, .. } |
Dispositivo::Laptop { marca, .. } if marca == "Apple" => true,
_ => false,
}
}
Esta técnica nos permite aplicar la misma lógica a diferentes variantes que comparten un campo común.
La desestructuración es una herramienta fundamental en Rust que nos permite escribir código más conciso, expresivo y seguro. Al dominar estos patrones, podremos trabajar con estructuras de datos complejas de manera elegante y sin sacrificar la seguridad que caracteriza a Rust.
if let y while let
Mientras que la expresión match
es extremadamente poderosa para el pattern matching exhaustivo, a veces resulta demasiado verbosa cuando solo nos interesa un patrón específico. Rust ofrece construcciones más concisas para estos casos: if let
y while let
, que nos permiten trabajar con patrones de forma más elegante cuando solo nos importa un caso particular.
La sintaxis if let
La construcción if let
nos permite combinar un if
con un patrón, ejecutando código solo cuando el valor coincide con dicho patrón:
if let Patrón = expresión {
// Código que se ejecuta cuando el patrón coincide
} else {
// Código opcional que se ejecuta cuando no coincide
}
Esta sintaxis es particularmente útil cuando trabajamos con enums y solo nos interesa una de sus variantes.
Comparación entre match e if let
Veamos un ejemplo comparativo para entender mejor cuándo usar if let
:
enum Temperatura {
Celsius(i32),
Fahrenheit(i32),
Kelvin(i32),
}
// Usando match (más verboso)
fn mostrar_celsius_match(temp: Temperatura) {
match temp {
Temperatura::Celsius(grados) => {
println!("La temperatura es {}°C", grados);
},
_ => {
// No hacemos nada con las otras variantes
}
}
}
// Usando if let (más conciso)
fn mostrar_celsius_if_let(temp: Temperatura) {
if let Temperatura::Celsius(grados) = temp {
println!("La temperatura es {}°C", grados);
}
}
Como podemos observar, if let
elimina la necesidad de escribir el caso comodín _
cuando solo nos interesa un patrón específico.
Combinando if let con else
Podemos extender if let
con ramas else
para manejar el caso cuando el patrón no coincide:
enum Estado {
Conectado { ip: String, puerto: u16 },
Desconectado,
EnEspera,
}
fn verificar_conexion(estado: Estado) {
if let Estado::Conectado { ip, puerto } = estado {
println!("Conectado a {}:{}", ip, puerto);
} else {
println!("No está conectado actualmente");
}
}
También podemos encadenar múltiples patrones usando else if let
:
fn describir_estado(estado: Estado) {
if let Estado::Conectado { ip, puerto } = estado {
println!("Conectado a {}:{}", ip, puerto);
} else if let Estado::EnEspera = estado {
println!("Conexión en espera, intentando reconectar...");
} else {
println!("Desconectado");
}
}
Patrones complejos con if let
La sintaxis if let
admite los mismos patrones complejos que podemos usar en match
:
struct Punto {
x: i32,
y: i32,
}
enum Forma {
Circulo(Punto, i32), // Centro y radio
Rectangulo(Punto, Punto), // Esquinas opuestas
}
fn es_circulo_grande(forma: &Forma) {
if let Forma::Circulo(Punto { x, y }, radio) = forma {
if *radio > 100 {
println!("Círculo grande en ({}, {}) con radio {}", x, y, radio);
}
}
}
La sintaxis while let
Mientras que if let
nos permite ejecutar código una vez si un patrón coincide, while let
nos permite repetir código mientras un patrón siga coincidiendo. Esta construcción es especialmente útil para procesar secuencias de valores:
while let Patrón = expresión {
// Código que se ejecuta mientras el patrón coincida
}
Ejemplo básico de while let
Un caso de uso común es procesar elementos de una pila hasta que esté vacía:
fn procesar_pila() {
let mut pila = Vec::new();
pila.push(1);
pila.push(2);
pila.push(3);
// Procesamos elementos hasta que la pila esté vacía
while let Some(valor) = pila.pop() {
println!("Procesando valor: {}", valor);
}
}
En este ejemplo, pila.pop()
devuelve Some(valor)
mientras la pila tenga elementos, y None
cuando está vacía. El bucle while let
continúa ejecutándose hasta que el patrón deja de coincidir (cuando obtenemos None
).
Iterando sobre elementos con índices
Otro uso común de while let
es para iterar sobre elementos con sus índices:
fn iterar_con_indices() {
let numeros = vec![10, 20, 30, 40];
let mut iter = numeros.iter().enumerate();
while let Some((indice, valor)) = iter.next() {
println!("Índice: {}, Valor: {}", indice, valor);
}
}
Aquí estamos desestructurando una tupla (indice, valor)
en cada iteración.
Procesando flujos de datos
while let
es ideal para procesar flujos de datos donde no conocemos de antemano cuántos elementos procesaremos:
use std::io::{self, BufRead};
fn leer_lineas_no_vacias() {
let stdin = io::stdin();
let mut lineas = stdin.lock().lines();
println!("Escribe texto (línea vacía para terminar):");
while let Some(Ok(linea)) = lineas.next() {
if linea.is_empty() {
break;
}
println!("Leído: {}", linea);
}
}
Este código lee líneas de la entrada estándar hasta encontrar una línea vacía, demostrando cómo while let
puede manejar patrones anidados (Some(Ok(linea))
).
Combinando if let y while let
Podemos combinar ambas construcciones para crear lógicas más complejas:
enum Evento {
Mensaje(String),
Movimiento { x: i32, y: i32 },
Tecla(char),
Salir,
}
fn procesar_eventos(eventos: Vec<Evento>) {
let mut iter = eventos.into_iter();
while let Some(evento) = iter.next() {
if let Evento::Mensaje(texto) = evento {
if texto == "pausa" {
println!("Pausando procesamiento...");
// Hacemos algo para pausar
continue;
}
println!("Mensaje: {}", texto);
} else if let Evento::Salir = evento {
println!("Saliendo del bucle");
break;
} else {
println!("Otro tipo de evento");
}
}
}
Ventajas y desventajas frente a match
Las construcciones if let
y while let
ofrecen varias ventajas:
- Concisión: Menos código cuando solo nos interesa un patrón específico
- Claridad: Expresan claramente la intención de verificar un solo caso
- Flexibilidad: Se integran bien con otras estructuras de control
Sin embargo, también tienen limitaciones:
- No exhaustivas: A diferencia de
match
, el compilador no verifica que hayamos manejado todos los casos posibles - Menos visibles: Los patrones que estamos manejando no están tan explícitamente documentados como en un
match
// Ejemplo donde if let podría ocultar casos no manejados
enum Resultado {
Exito(i32),
Error(String),
EnProceso,
}
fn procesar_resultado(res: Resultado) {
// Si solo usamos if let, no es obvio que estamos ignorando el caso EnProceso
if let Resultado::Exito(valor) = res {
println!("Éxito con valor: {}", valor);
} else if let Resultado::Error(msg) = res {
println!("Error: {}", msg);
}
// Con match, es más claro que estamos manejando todos los casos
// o explícitamente ignorando algunos
/*
match res {
Resultado::Exito(valor) => println!("Éxito con valor: {}", valor),
Resultado::Error(msg) => println!("Error: {}", msg),
Resultado::EnProceso => (), // Explícitamente no hacemos nada
}
*/
}
Cuándo usar cada construcción
Como regla general:
- Usa
match
cuando:
- Necesitas manejar múltiples patrones diferentes
- Quieres asegurarte de manejar todos los casos posibles (exhaustividad)
- La lógica para cada patrón es simple
- Usa
if let
cuando:
- Solo te interesa un patrón específico
- Quieres combinar la coincidencia de patrones con otras condiciones
- Los otros casos pueden manejarse de manera genérica o ignorarse
- Usa
while let
cuando:
- Necesitas procesar elementos mientras un patrón siga coincidiendo
- Estás trabajando con iteradores o flujos de datos
- No conoces de antemano cuántos elementos procesarás
Aplicaciones prácticas
Veamos un ejemplo más completo que muestra cómo estas construcciones pueden simplificar el código en situaciones reales:
enum Comando {
Mover { x: i32, y: i32 },
Cambiar { color: String },
Dibujar { forma: String },
Guardar,
Salir,
}
fn ejecutar_comandos(comandos: Vec<Comando>) {
let mut iter = comandos.into_iter();
while let Some(cmd) = iter.next() {
// Procesamos solo los comandos de movimiento y dibujo
if let Comando::Mover { x, y } = cmd {
println!("Moviendo cursor a la posición ({}, {})", x, y);
} else if let Comando::Dibujar { forma } = cmd {
println!("Dibujando: {}", forma);
} else if let Comando::Salir = cmd {
println!("Saliendo del programa");
break;
}
// Ignoramos otros comandos por ahora
}
}
Este patrón es común en procesadores de comandos, parsers y sistemas de eventos donde solo nos interesa manejar ciertos tipos de mensajes.
Las construcciones if let
y while let
complementan perfectamente el sistema de pattern matching de Rust, ofreciendo alternativas más concisas cuando no necesitamos la exhaustividad de match
. En la próxima lección, veremos cómo estos conceptos se aplican a Option
y Result
para implementar un manejo de errores elegante y seguro.
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 Pattern Matching 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 la expresión match y su propiedad de exhaustividad en Rust.
- Aprender a desestructurar tuplas, structs y enums para extraer datos.
- Utilizar patrones avanzados como rangos, condiciones de guarda y el operador @.
- Diferenciar y aplicar las construcciones if let y while let para casos específicos y bucles.
- Reconocer las ventajas y limitaciones de match frente a if let y while let en el manejo de patrones.