Structs Genéricas
Parte da seção Object Oriented Programming do Journey de Rust da Coddy — lição 24 de 61.
Até agora, cada struct que você criou teve campos com tipos específicos e concretos. Um Pet tem um nome String, um Rectangle tem dimensões f64. Mas e se você quiser criar uma struct que possa conter qualquer tipo de dado?
É aqui que entram os generics. Os generics permitem que você escreva código flexível e reutilizável que funciona com múltiplos tipos. Em vez de definir rigidamente um tipo específico, você usa um marcador de posição — normalmente T — que é substituído por um tipo concreto quando você usa a struct.
Aqui está uma struct wrapper simples que pode conter qualquer tipo:
struct Wrapper<T> {
value: T,
}
O <T> após o nome da struct declara um parâmetro de tipo genérico. Dentro da struct, T atua como um substituto para qualquer tipo que você realmente usará. Quando você cria uma instância, o Rust infere o tipo concreto:
let int_wrapper = Wrapper { value: 42 }; // T é i32
let float_wrapper = Wrapper { value: 3.14 }; // T é f64
let text_wrapper = Wrapper { value: "hello" }; // T é &str
Cada um destes é um tipo concreto diferente—Wrapper<i32>, Wrapper<f64> e Wrapper<&str>—mas todos compartilham a mesma definição de struct. Isso elimina a necessidade de escrever structs IntWrapper, FloatWrapper e StringWrapper separadas que fazem essencialmente a mesma coisa.
A letra T é apenas uma convenção (abreviação de "Type"). Você poderia usar qualquer identificador válido, mas T é o padrão em Rust e na maioria das outras linguagens com genéricos.
Desafio
FácilVamos construir um contêiner genérico que pode conter qualquer tipo de valor! Você criará uma struct Container que demonstra como os genéricos tornam seu código flexível e reutilizável.
Você organizará seu código em dois arquivos:
container.rs: Defina uma struct genérica pública chamadaContainer<T>com um único campo públicoitemdo tipoT. Este contêiner deve ser capaz de conter qualquer tipo—inteiros, floats, strings ou qualquer outra coisa.main.rs: Importe seu módulo de contêiner e demonstre sua flexibilidade criando contêineres que contêm diferentes tipos de dados. Você criará três contêineres e exibirá o que cada um contém.
No seu arquivo principal, crie os seguintes contêineres:
- Um contêiner contendo um valor inteiro (a primeira entrada, analisada como
i32) - Um contêiner contendo um valor de ponto flutuante (a segunda entrada, analisada como
f64) - Um contêiner contendo uma string (a terceira entrada, mantida como uma
String)
Sua saída deve exibir o conteúdo de cada contêiner neste formato:
Integer container: {value}
Float container: {value}
String container: {value}Por exemplo, com as entradas 42, 3.14 e hello:
Integer container: 42
Float container: 3.14
String container: helloObserve como a mesma definição de Container<T> funciona para todos os três tipos diferentes—esse é o poder dos genéricos!
Você receberá três entradas: um valor inteiro, um valor de ponto flutuante e um valor de string.
Folha de consulta
Generics permitem que você crie structs que podem conter qualquer tipo de dado usando um marcador de posição de parâmetro de tipo.
Para declarar uma struct genérica, use colchetes angulares com um parâmetro de tipo (convencionalmente T):
struct Wrapper<T> {
value: T,
}
O <T> declara um parâmetro de tipo genérico, e T dentro da struct atua como um marcador de posição para o tipo real.
Ao criar instâncias, o Rust infere o tipo concreto:
let int_wrapper = Wrapper { value: 42 }; // T é i32
let float_wrapper = Wrapper { value: 3.14 }; // T é f64
let text_wrapper = Wrapper { value: "hello" }; // T é &str
Cada instância se torna um tipo concreto diferente (Wrapper<i32>, Wrapper<f64>, Wrapper<&str>) enquanto compartilha a mesma definição de struct.
Experimente você mesmo
mod container;
use container::Container;
fn main() {
// Leia as três entradas
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let int_value: i32 = input1.trim().parse().expect("Failed to parse integer");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let float_value: f64 = input2.trim().parse().expect("Failed to parse float");
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let string_value = input3.trim().to_string();
// TODO: Crie três containers usando Container<T>
// 1. Um container de inteiro contendo int_value
// 2. Um container de float contendo float_value
// 3. Um container de string contendo string_value
// TODO: Imprima o conteúdo de cada container no formato exigido
// Integer container: {value}
// Float container: {value}
// String container: {value}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Object Oriented Programming
1Métodos e Comportamento
Introdução a Blocos de ImplementaçãoO Parâmetro SelfMétodos MutáveisFunções AssociadasMúltiplos Blocos de ImplementaçãoEncadeamento de MétodosRecapitulação - Ações de Retângulo4Projeto: Pet Virtual
Definindo o PetAlimentando o Pet7Traits Padrão
A Trait DebugA Trait DisplayClone e CopyTraits de IgualdadeRecapitulação - Ponto Imprimível10Projeto: Sistema de Documentos
A Trait DrawComponente de Texto2Encapsulamento e Módulos
Fundamentos de MódulosA Palavra-chave PublicCampos PrivadosGettersSettersRecapitulação - Cofre Seguro5Genéricos
Structs GenéricasMétodos GenéricosMúltiplos Tipos GenéricosFunções GenéricasRecapitulação - Ponto de Coordenada8Traits como Restrições
Sintaxe de Restrições de TraitRestrições MúltiplasA Cláusula WhereRetornando Tipos com TraitsRecapitulação - Impressora Genérica11Padrões de Projeto em Rust
Padrão NewtypeComposiçãoA Trait DropFrom e IntoRecapitulação - Mock de Smart Pointer3Enums Avançados
Enums com DadosMétodos em EnumsCorrespondência de Variantes de DadosO Enum Option RevisitadoRecapitulação - Enum Shape6Definição de Traits
O que é uma Trait?Implementando TraitsImplementações PadrãoSobrescrevendo PadrõesTraits com ParâmetrosRecapitulação - Media Player