O que são tipos de dados genéricos?

Concluído

Um tipo de dados genérico é um tipo que é definido em termos de outros tipos parcialmente desconhecidos. Estamos usando muitos tipos de dados genéricos desde o início deste curso, por exemplo:

  • A enumeração Option<T> é genérica em relação ao tipo T, que é o valor contido por sua variante Some.
  • O Result<T, E> é genérico em relação ao seu tipo de êxito e de falha, contido por suas variantes Ok e Err, respectivamente.
  • O tipo de vetor Vec<T>, o tipo de matriz [T; n] e o mapa de hash HashMap<K, V> são genéricos em relação aos tipos que contêm.

Quando usamos tipos genéricos, podemos especificar a operação que desejamos, sem grandes preocupações sobre alguns dos tipos internos mantidos pelo tipo definido.

Para implementar um novo tipo genérico, devemos declarar o nome do parâmetro de tipo dentro de colchetes angulares logo após o nome do struct. Em seguida, podemos usar o tipo genérico na definição do struct, em que normalmente especificaríamos tipos de dados 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" };
}

O código anterior define um struct Point<T>. Esse struct contém qualquer tipo (T) de valores x e y.

Embora T possa assumir qualquer tipo concreto, x e y devem ser desse mesmo tipo, pois foram definidos como sendo do mesmo tipo. Se tentarmos criar uma instância de um Point<T> que tenha valores de tipos diferentes, como no snippet a seguir, nosso código não será compilado.

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`

A mensagem de erro diz que o tipo esperado para o campo y era um inteiro. No entanto, como configuramos y para ter o mesmo tipo que x, o compilador indicou um erro de tipos incompatíveis.

Como visto no exemplo a seguir, podemos usar vários parâmetros de tipo genérico. Nesse caso, mostramos um Point<T, U> genérico em dois tipos para que x e y sejam valores de tipos diferentes.

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 os tipos Point anteriores têm tipos concretos diferentes. Na ordem:

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

Portanto, você não pode de fato misturar esses valores diretamente entre si antes de ter implementado esse tipo de interação em seu código.

Na próxima unidade, vamos conhecer as características e descobrir como tipos genéricos podem ser úteis em nosso código. Podemos usá-los para escrever funções genéricas que funcionarão em objetos de tipos diferentes, mas relacionados.