Saltar al contenido

if let y while let

El azúcar sintáctico para hacer match de un solo patrón: if let, if let/else, let-else y while let. Su uso idiomático con Option, y las cadenas de if let de la edición 2024.

match es exhaustivo por diseño, pero a menudo solo te interesa un patrón y quieres ignorar el resto. Escribir un match con un brazo útil y un _ => () de relleno es ruidoso. Para eso existen if let y while let: azúcar que hace matching de un único patrón sin la ceremonia. Son la herramienta cotidiana para trabajar con Option, y en la edición 2024 se encadenan para expresar condiciones que antes exigían anidar bloques.

  • Reconocer if let como un match de un solo brazo.
  • Usar if let/else y let-else para el camino feliz y el de fallo.
  • Consumir con while let: pilas, colas e iteradores.
  • Encadenar condiciones con las let chains de la edición 2024.

if let: un match de un solo patrón

Estas dos formas son equivalentes, pero la segunda dice lo mismo con la mitad de ruido:

Rust
let config: Option<i32> = Some(7);// con match: un brazo útil y uno de rellenomatch config {    Some(valor) => println!("configurado a {valor}"),    None => {}}// con if let: idéntico, sin el brazo vacíoif let Some(valor) = config {    println!("configurado a {valor}");}

La clave está en que Some(valor) = config es un patrón refutable: puede no encajar (si config fuese None). Un let normal solo admite patrones irrefutables —los que siempre encajan—; if let es precisamente lo que te deja usar un patrón refutable, ejecutando el cuerpo solo cuando encaja. Y admite un else para el caso contrario:

Rust
if let Some(valor) = config {    println!("hay {valor}");} else {    println!("no había nada");}

Y cuando solo quieres un bool —“¿encaja este patrón?”— sin ejecutar nada en el cuerpo, la macro matches! lo condensa en una sola expresión, guarda incluida:

Rust
let es_par = matches!(config, Some(n) if n % 2 == 0);

let-else: ligar o divergir

A veces quieres el valor de un Option para el resto de la función, y si no está, salir. Anidar todo el cuerpo dentro de un if let empuja la lógica hacia la derecha. La forma let-else invierte la estructura: liga en el scope actual, y si el patrón no encaja, ejecuta un bloque que debe divergir (retornar, romper un bucle o entrar en pánico):

Rust
fn procesar(entrada: Option<i32>) -> i32 {    let Some(valor) = entrada else {        return -1; // obligado a salir: el else no puede "seguir"    };    // aquí `valor` vive en el scope normal, ya desenvuelto    valor * 2}

El compilador exige que el else no caiga hacia abajo (su tipo debe ser el tipo never, del que hablaremos en la próxima lección). Así se garantiza que, pasado el let-else, el nombre está ligado sin ninguna duda. Es el patrón idiomático para la validación temprana: la “cláusula de guarda” de Rust.

Rust
let Ok(puerto) = "8080".parse::<u16>() else {    eprintln!("puerto inválido");    return;};// a partir de aquí `puerto` es un u16 válido, sin envolver

while let: repetir mientras encaje

while let es a while lo que if let a if: repite el cuerpo mientras el patrón siga encajando, y para en cuanto falla. Su uso estelar es vaciar una estructura cuyo “sacar” devuelve Option:

Rust
let mut pila = vec![1, 2, 3, 4, 5];while let Some(cima) = pila.pop() { // pop() devuelve Option    println!("saco {cima}");        // 5, 4, 3, 2, 1}// el bucle para solo cuando pop() devuelve None (pila vacía)

El mismo esqueleto vale para drenar un iterador a mano o recibir de un canal hasta que se cierra: en todos, None es la señal de “se acabó”.

Rust
let mut it = [1, 2, 3].into_iter();while let Some(x) = it.next() {    println!("{x}");}

Esto expresa “procesa hasta agotar” con exactitud y sin un break manual: el None que marca el final es la condición de salida. Funciona igual con el recv() de un canal, con next() de un iterador que manejas a mano, o con cualquier API que señale “no hay más” con None.

if let renuncia a la exhaustividad a cambio de brevedad

Es un intercambio consciente, no gratis. match te obliga a tratar todos los casos; if let maneja uno y descarta el resto (con o sin else). Esa brevedad es ideal cuando de verdad solo te importa un patrón, pero es una trampa si el “resto” merecía atención. Regla práctica: si mañana añadir una variante al tipo debería obligarte a reconsiderar este código, usa match y deja que el compilador te avise; si el otro caso es un “no hacer nada” legítimo y estable, if let comunica mejor la intención.

Cadenas de if let: la edición 2024

Históricamente, comprobar dos Option a la vez obligaba a anidar:

Rust
// la "pirámide de la perdición" de antesif let Some(usuario) = sesion {    if let Some(rol) = usuario.rol {        if rol.es_admin {            conceder_acceso();        }    }}

La edición 2024 estabiliza las let chains: puedes unir varios let y condiciones booleanas con && en un solo if, ligando nombres que se ven en el resto de la cadena y en el cuerpo:

Rust
// Rust 2024: una sola condición, plana y legibleif let Some(usuario) = sesion    && let Some(rol) = usuario.rol    && rol.es_admin{    conceder_acceso();}

Cada let de la cadena solo continúa si su patrón encaja, y las variables ligadas van estando disponibles hacia la derecha. La pirámide se aplana en una lista de condiciones que se lee de arriba abajo. Es una de las mejoras de ergonomía más celebradas de la edición 2024, y requiere justamente esa edición para funcionar.

El azúcar sintáctico correcto revela la intención, no la esconde

Es tentador despreciar if let, let-else y while let como meras abreviaturas de match, cosmética sin sustancia. Sería un error de juicio. Todas se desazucaran a un match —if let Some(x) = e { c } no es más que match e { Some(x) => { c }, _ => {} }— pero eso es precisamente lo valioso: cada forma codifica una intención distinta sobre el mismo mecanismo. match dice “voy a razonar sobre todos los casos”. if let dice “solo me importa este”. let-else dice “este valor tiene que estar, o me largo”. while let dice “seguiré mientras haya”. El buen código no es el que usa la construcción más potente en todas partes, sino el que hace coincidir la construcción con la forma del problema, de modo que quien lo lea capte la intención sin descifrarla. Por eso Rust cultiva un vocabulario de azúcares alrededor del pattern matching en lugar de obligarte a escribir match siempre: no es debilidad del match, es riqueza expresiva. Y la evolución no se detiene: las let chains de la edición 2024 nacieron de observar qué escribía la gente una y otra vez —comprobaciones anidadas— y darles una forma plana. Aprender Rust no es solo aprender qué construcción es válida, sino cuál comunica mejor lo que quieres decir. Esa sensibilidad separa el código que funciona del código que además se entiende.

Elige el azúcar correcto

  1. Reescribe un match sobre un Option<i32> que solo actúa en el caso Some usando if let, y luego añádele una rama else.
  2. Escribe una función que reciba Option<&str> y, con let-else, devuelva pronto si es None, usando el valor desenvuelto en el resto del cuerpo.
  3. Vacía un Vec con while let Some(x) = v.pop() e imprime el orden en que salen los elementos; explica por qué es el inverso de la inserción.
  4. Convierte dos if let anidados sobre dos Option distintos en una única let chain de la edición 2024 con &&.