O que são tipos de dados genéricos?
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 tipoT
, que é o valor contido por sua varianteSome
. - O
Result<T, E>
é genérico em relação ao seu tipo de êxito e de falha, contido por suas variantesOk
eErr
, respectivamente. - O tipo de vetor
Vec<T>
, o tipo de matriz[T; n]
e o mapa de hashHashMap<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.