Todos los lugares donde se pueden usar patrones

RustRustBeginner
Practicar Ahora

This tutorial is from open-source community. Access the source code

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

Bienvenido a Todos los lugares donde se pueden usar los patrones. Esta práctica es parte del Rust Book. Puedes practicar tus habilidades de Rust en LabEx.

En esta práctica, exploraremos todos los lugares donde se pueden usar los patrones en Rust.

Todos los lugares donde se pueden usar los patrones

Los patrones aparecen en varios lugares en Rust, y los has estado usando mucho sin darte cuenta. Esta sección discute todos los lugares donde los patrones son válidos.

Brasos de match

Como se discutió en el Capítulo 6, usamos patrones en los brazos de las expresiones match. Formalmente, las expresiones match se definen como la palabra clave match, un valor sobre el que se hará la coincidencia, y uno o más brazos de coincidencia que constan de un patrón y una expresión que se ejecutará si el valor coincide con el patrón de ese brazo, como esto:

match VALOR {
    PATRÓN => EXPRESIÓN,
    PATRÓN => EXPRESIÓN,
    PATRÓN => EXPRESIÓN,
}

Por ejemplo, aquí está la expresión match del Listado 6-5 que coincide con un valor Option<i32> en la variable x:

match x {
    None => None,
    Some(i) => Some(i + 1),
}

Los patrones en esta expresión match son None y Some(i) a la izquierda de cada flecha.

Un requisito para las expresiones match es que deben ser exhaustivas en el sentido de que se deben considerar todas las posibilidades para el valor en la expresión match. Una forma de asegurarse de haber cubierto todas las posibilidades es tener un patrón general para el último brazo: por ejemplo, un nombre de variable que coincida con cualquier valor nunca fallará y, por lo tanto, cubrirá todos los casos restantes.

El patrón particular _ coincidirá con cualquier cosa, pero nunca se enlazará a una variable, por lo que a menudo se usa en el último brazo de coincidencia. El patrón _ puede ser útil cuando se desea ignorar cualquier valor no especificado, por ejemplo. Cubriremos el patrón _ con más detalle en "Ignorando valores en un patrón".

Expresiones if let Condicionales

En el Capítulo 6, discutimos cómo usar las expresiones if let principalmente como una forma más corta de escribir lo equivalente a un match que solo coincide con un caso. Opcionalmente, if let puede tener un else correspondiente que contiene el código a ejecutar si el patrón en if let no coincide.

El Listado 18-1 muestra que también es posible combinar y mezclar expresiones if let, else if y else if let. Hacer esto nos da más flexibilidad que una expresión match en la que solo podemos expresar un solo valor para comparar con los patrones. Además, Rust no requiere que las condiciones en una serie de brazos if let, else if y else if let estén relacionadas entre sí.

El código en el Listado 18-1 determina qué color usar para el fondo basado en una serie de comprobaciones para varias condiciones. Para este ejemplo, hemos creado variables con valores codificados en duro que un programa real podría recibir a partir de la entrada del usuario.

Nombre del archivo: src/main.rs

fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

  1 if let Some(color) = favorite_color {
      2 println!(
            "Using your favorite, {color}, as the background"
        );
  3 } else if is_tuesday {
      4 println!("Tuesday is green day!");
  5 } else if let Ok(age) = age {
      6 if age > 30 {
          7 println!("Using purple as the background color");
        } else {
          8 println!("Using orange as the background color");
        }
  9 } else {
     10 println!("Using blue as the background color");
    }
}

Listado 18-1: Combinando if let, else if, else if let y else

Si el usuario especifica un color favorito [1], ese color se usa como fondo [2]. Si no se especifica un color favorito y hoy es martes [3], el color de fondo es verde [4]. De lo contrario, si el usuario especifica su edad como una cadena y podemos analizarla correctamente como un número [5], el color es o violeta [7] o naranja [8] dependiendo del valor del número [6]. Si ninguna de estas condiciones se aplica [9], el color de fondo es azul [10].

Esta estructura condicional nos permite soportar requisitos complejos. Con los valores codificados en duro que tenemos aquí, este ejemplo imprimirá Using purple as the background color.

Puedes ver que if let también puede introducir variables sombreadas de la misma manera que los brazos de match pueden: la línea if let Ok(age) = age [5] introduce una nueva variable age sombreada que contiene el valor dentro de la variante Ok. Esto significa que necesitamos colocar la condición if age > 30 [6] dentro de ese bloque: no podemos combinar estas dos condiciones en if let Ok(age) = age && age > 30. La variable age sombreada que queremos comparar con 30 no es válida hasta que el nuevo ámbito comienza con la llave.

La desventaja de usar expresiones if let es que el compilador no comprueba la exhaustividad, mientras que con las expresiones match sí lo hace. Si omitimos el último bloque else [9] y, por lo tanto, perdemos el manejo de algunos casos, el compilador no nos alertará sobre el posible error de lógica.

Bucles Condicionales while let

Similar en construcción a if let, el bucle condicional while let permite que un bucle while se ejecute mientras un patrón siga coincidiendo. En el Listado 18-2, codificamos un bucle while let que utiliza un vector como una pila y muestra los valores del vector en el orden inverso al en que se insertaron.

Nombre del archivo: src/main.rs

let mut stack = Vec::new();

stack.push(1);
stack.push(2);
stack.push(3);

while let Some(top) = stack.pop() {
    println!("{top}");
}

Listado 18-2: Usando un bucle while let para imprimir valores mientras stack.pop() devuelva Some

Este ejemplo imprime 3, 2 y luego 1. El método pop saca el último elemento del vector y devuelve Some(value). Si el vector está vacío, pop devuelve None. El bucle while continúa ejecutando el código en su bloque mientras pop devuelva Some. Cuando pop devuelve None, el bucle se detiene. Podemos usar while let para sacar cada elemento de nuestra pila.

Bucles for

En un bucle for, el valor que sigue directamente a la palabra clave for es un patrón. Por ejemplo, en for x in y, el x es el patrón. El Listado 18-3 demuestra cómo usar un patrón en un bucle for para desestructurar, o descomponer, una tupla como parte del bucle for.

Nombre del archivo: src/main.rs

let v = vec!['a', 'b', 'c'];

for (index, value) in v.iter().enumerate() {
    println!("{value} is at index {index}");
}

Listado 18-3: Usando un patrón en un bucle for para desestructurar una tupla

El código en el Listado 18-3 imprimirá lo siguiente:

a is at index 0
b is at index 1
c is at index 2

Adaptamos un iterador usando el método enumerate para que produzca un valor y el índice para ese valor, colocados en una tupla. El primer valor producido es la tupla (0, 'a'). Cuando este valor se coincide con el patrón (index, value), index será 0 y value será 'a', imprimiendo la primera línea de la salida.

Declaraciones let

Antes de este capítulo, solo habíamos discutido explícitamente el uso de patrones con match e if let, pero de hecho, también los hemos usado en otros lugares, incluyendo en declaraciones let. Por ejemplo, considere esta asignación de variable directa con let:

let x = 5;

Cada vez que ha usado una declaración let como esta, ha estado usando patrones, aunque puede que no lo haya notado. Más formalmente, una declaración let se ve así:

let PATRÓN = EXPRESIÓN;

En declaraciones como let x = 5; con un nombre de variable en el slot del patrón, el nombre de variable es solo una forma particularmente simple de un patrón. Rust compara la expresión con el patrón y asigna cualquier nombre que encuentre. Entonces, en el ejemplo let x = 5;, x es un patrón que significa "asigna lo que coincide aquí a la variable x". Debido a que el nombre x es todo el patrón, este patrón efectivamente significa "asigna todo a la variable x, cualquiera que sea el valor".

Para ver más claramente el aspecto de coincidencia de patrones de let, considere el Listado 18-4, que usa un patrón con let para desestructurar una tupla.

let (x, y, z) = (1, 2, 3);

Listado 18-4: Usando un patrón para desestructurar una tupla y crear tres variables a la vez

Aquí, hacemos coincidir una tupla con un patrón. Rust compara el valor (1, 2, 3) con el patrón (x, y, z) y ve que el valor coincide con el patrón, en el sentido de que ve que el número de elementos es el mismo en ambos, por lo que Rust asigna 1 a x, 2 a y y 3 a z. Puede pensar en este patrón de tupla como anidar tres patrones de variable individuales dentro de él.

Si el número de elementos en el patrón no coincide con el número de elementos en la tupla, el tipo general no coincidirá y obtendremos un error del compilador. Por ejemplo, el Listado 18-5 muestra un intento de desestructurar una tupla con tres elementos en dos variables, lo que no funcionará.

let (x, y) = (1, 2, 3);

Listado 18-5: Construyendo incorrectamente un patrón cuyas variables no coinciden con el número de elementos en la tupla

Intentar compilar este código da como resultado este error de tipo:

error[E0308]: mismatched types
 --> src/main.rs:2:9
  |
2 |     let (x, y) = (1, 2, 3);
  |         ^^^^^^   --------- this expression has type `({integer}, {integer},
{integer})`
  |         |
  |         expected a tuple with 3 elements, found one with 2 elements
  |
  = note: expected tuple `({integer}, {integer}, {integer})`
             found tuple `(_, _)`

Para corregir el error, podríamos ignorar uno o más de los valores en la tupla usando _ o .., como verá en "Ignorando valores en un patrón". Si el problema es que tenemos demasiadas variables en el patrón, la solución es hacer que los tipos coincidan eliminando variables para que el número de variables sea igual al número de elementos en la tupla.

Parámetros de función

Los parámetros de función también pueden ser patrones. El código en el Listado 18-6, que declara una función llamada foo que toma un parámetro llamado x de tipo i32, probablemente ya le sea familiar.

fn foo(x: i32) {
    // código aquí
}

Listado 18-6: Una firma de función que utiliza patrones en los parámetros

La parte x es un patrón ¡Como hicimos con let, podríamos hacer coincidir una tupla en los argumentos de una función con el patrón. El Listado 18-7 separa los valores de una tupla mientras la pasamos a una función.

Nombre del archivo: src/main.rs

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({x}, {y})");
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

Listado 18-7: Una función con parámetros que desestructuran una tupla

Este código imprime Current location: (3, 5). Los valores &(3, 5) coinciden con el patrón &(x, y), por lo que x es el valor 3 y y es el valor 5.

También podemos usar patrones en las listas de parámetros de cierre de la misma manera que en las listas de parámetros de función porque los cierres son similares a las funciones, como se discutió en el Capítulo 13.

En este momento, ha visto varias maneras de usar patrones, pero los patrones no funcionan del mismo modo en todos los lugares donde los podemos usar. En algunos lugares, los patrones deben ser irrefutables; en otras circunstancias, pueden ser refutables. Discutiremos estos dos conceptos a continuación.

Resumen

¡Felicitaciones! Has completado el laboratorio de Todos los Lugares Donde se Pueden Usar Patrones. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.