Cada lenguaje de programación tiene herramientas para manejar eficazmente la duplicación de conceptos. En Rust, una de esas herramientas es genéricos. Los genéricos son sustitutos abstractos de tipos concretos u otras propiedades. Cuando escribimos código, podemos expresar el comportamiento de los genéricos o cómo se relacionan con otros genéricos sin saber qué estará en su lugar al compilar y ejecutar el código.

Similar a la forma en que una función toma parámetros con valores desconocidos para ejecutar el mismo código en múltiples valores concretos, las funciones pueden tomar parámetros de algún tipo genérico en lugar de un tipo concreto, como i32 o String. De hecho, ya usamos genéricos en el Capítulo 6 con Option<T>, Capítulo 8 con Vec<T> y HashMap<K, V>, y el Capítulo 9 con Result<T, E>. En este capítulo, explorará cómo definir sus propios tipos, funciones y métodos con genéricos.

Primero, revisaremos cómo extraer una función para reducir la duplicación de código. A continuación, usaremos la misma técnica para hacer una función genérica a partir de dos funciones que difieren solo en los tipos de sus parámetros. También explicaremos cómo usar tipos genéricos en las definiciones de estructura y enumeración.

Entonces aprenderás a usar rasgos para definir el comportamiento de forma genérica. Puede combinar rasgos con tipos genéricos para restringir un tipo genérico solo a aquellos tipos que tienen un comportamiento particular, a diferencia de cualquier tipo.

Finalmente, discutiremos vidas, una variedad de genéricos que brindan al compilador información sobre cómo las referencias se relacionan entre sí. La vida útil nos permite tomar prestados valores en muchas situaciones y al mismo tiempo permitir que el compilador verifique que las referencias son válidas.

Eliminar duplicaciones extrayendo una función

Antes de sumergirnos en la sintaxis genérica, veamos primero cómo eliminar la duplicación que no involucre tipos genéricos extrayendo una función. ¡Luego aplicaremos esta técnica para extraer una función genérica! De la misma manera que reconoce el código duplicado para extraerlo en una función, comenzará a reconocer el código duplicado que puede usar genéricos.

Considere un programa corto que encuentre el número más grande en una lista, como se muestra en el Listado 10-1.

Nombre de archivo: src / main.rs

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let mut largest = number_list[0];

    for number in number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {}", largest);
    assert_eq!(largest, 100);
}

Listado 10-1: Código para encontrar el número más grande en una lista de números

Este código almacena una lista de enteros en la variable number_list y coloca el primer número de la lista en una variable llamada largest. Luego, recorre todos los números de la lista, y si el número actual es mayor que el número almacenado en largest, reemplaza el número en esa variable. Sin embargo, si el número actual es menor o igual que el número más grande visto hasta ahora, la variable no cambia y el código pasa al siguiente número de la lista. Después de considerar todos los números de la lista, largest debe contener el número más grande, que en este caso es 100.

Para encontrar el número más grande en dos listas de números diferentes, podemos duplicar el código del Listado 10-1 y usar la misma lógica en dos lugares diferentes del programa, como se muestra en el Listado 10-2.

Nombre de archivo: src / main.rs

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let mut largest = number_list[0];

    for number in number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {}", largest);

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let mut largest = number_list[0];

    for number in number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {}", largest);
}

Listado 10-2: Código para encontrar el número más grande en dos listas de números

Aunque este código funciona, la duplicación de código es tediosa y propensa a errores. También tenemos que actualizar el código en varios lugares cuando queremos cambiarlo.

Para eliminar esta duplicación, podemos crear una abstracción definiendo una función que opere en cualquier lista de enteros que se le asigne en un parámetro. Esta solución hace que nuestro código sea más claro y nos permite expresar el concepto de encontrar el número más grande en una lista de forma abstracta.

En el Listado 10-3, extrajimos el código que encuentra el número más grande en una función llamada largest. A diferencia del código del Listado 10-1, que puede encontrar el número más grande en una sola lista en particular, este programa puede encontrar el número más grande en dos listas diferentes.

Nombre de archivo: src / main.rs

fn largest(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);
    assert_eq!(result, 100);

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let result = largest(&number_list);
    println!("The largest number is {}", result);
    assert_eq!(result, 6000);
}

Listado 10-3: código abstraído para encontrar el número más grande en dos listas

los largest La función tiene un parámetro llamado list, que representa cualquier segmento concreto de i32 valores que podríamos pasar a la función. Como resultado, cuando llamamos a la función, el código se ejecuta en los valores específicos que pasamos. No se preocupe por la sintaxis de la for bucle por ahora. No estamos haciendo referencia a una referencia a un i32 aquí; estamos haciendo coincidir patrones y desestructurando cada uno &i32 que el for bucle se pone de modo que item será un i32 dentro del cuerpo del lazo. Cubriremos la coincidencia de patrones en detalle en el Capítulo 18.

En resumen, estos son los pasos que tomamos para cambiar el código del Listado 10-2 al Listado 10-3:

  1. Identifica el código duplicado.
  2. Extraiga el código duplicado en el cuerpo de la función y especifique las entradas y los valores de retorno de ese código en la firma de la función.
  3. Actualice las dos instancias de código duplicado para llamar a la función en su lugar.

A continuación, usaremos estos mismos pasos con genéricos para reducir la duplicación de código de diferentes maneras. De la misma manera que el cuerpo de la función puede operar en un resumen list en lugar de valores específicos, los genéricos permiten que el código opere en tipos abstractos.

Por ejemplo, digamos que tenemos dos funciones: una que encuentra el elemento más grande en una porción de i32 valores y uno que encuentre el elemento más grande en una porción de char valores. ¿Cómo eliminaríamos esa duplicación? ¡Vamos a averiguar!