¿Qué son los tipos de datos genéricos?

Completado

Un tipo de datos genérico es un tipo que se define en términos de otros tipos parcialmente desconocidos. Hemos usado muchos tipos de datos genéricos desde el principio de este curso, por ejemplo:

  • La enumeración Option<T> es genérica con respecto al tipo T, que es el valor que contiene su variante Some.
  • El valor Result<T, E> es genérico tanto en el tipo correcto como en el error, que contiene sus variantes Ok y Err, respectivamente.
  • El tipo de vector Vec<T>, el tipo de matriz [T; n] y el mapa hash HashMap<K, V> son genéricos con respecto a los tipos que contienen.

Cuando se usan tipos genéricos, se puede especificar la operación deseada sin tener que preocuparse por algunos de los tipos internos que contiene el tipo definido.

Para implementar un nuevo tipo genérico, debemos declarar el nombre del parámetro de tipo dentro de los corchetes angulares inmediatamente después del nombre de la estructura. Entonces podemos usar el tipo genérico en la definición de la estructura donde, de lo contrario, especificaríamos tipos de datos concretos.

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let boolean = Point { x: true, y: false };
    let integer = Point { x: 1, y: 9 };
    let float = Point { x: 1.7, y: 4.3 };
    let string_slice = Point { x: "high", y: "low" };
}

El código anterior define una estructura Point<T>. Esta estructura contiene cualquier tipo ( T) de valores x y y.

Aunque T puede asumir cualquier tipo concreto, x y y deben ser del mismo tipo, ya que se definieron como si fueran del mismo tipo. Si se intenta crear una instancia de una estructura Point<T> que tenga valores de tipos diferentes, como en el fragmento de código siguiente, el código no se compilará.

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let wont_work = Point { x: 25, y: true };
}
    error[E0308]: mismatched types
     --> src/main.rs:7:39
      |
    7 |     let wont_work = Point { x: 25, y: true };
      |                                       ^^^^ expected integer, found `bool`

El mensaje de error indica que el tipo esperado para el campo y era un entero. Pero como definimos y para que tenga el mismo tipo que x, el compilador ha dado un error de falta de coincidencia de tipos.

Como se muestra en el ejemplo siguiente, se pueden usar varios parámetros de tipo genérico. En este caso, se muestra un Point<T, U> genérico sobre dos tipos, de modo que x y y pueden ser valores de distintos tipos.

struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let integer_and_boolean = Point { x: 5, y: false };
    let float_and_string = Point { x: 1.0, y: "hey" };
    let integer_and_float = Point { x: 5, y: 4.0 };
    let both_integer = Point { x: 10, y: 30 };
    let both_boolean = Point { x: true, y: true };
}

Todos los tipos Point anteriores tienen distintos tipos concretos. Por orden:

  • Point<integer, bool>
  • Point<f64, &'static str>
  • Point<integer, f64>
  • Point<integer, integer>
  • Point<bool, bool>

Así que realmente no puede combinar esos valores directamente entre sí hasta que haya implementado ese tipo de interacción en el código.

En la siguiente unidad, vamos a obtener información sobre los rasgos y a descubrir cómo pueden ser útiles los tipos genéricos en nuestro código. Se pueden usar para escribir funciones genéricas que operen en objetos de tipos diferentes pero relacionados.