Introdução: o que são Generics e para que servem em Rust?
\nImagine que você precisa escrever uma função que encontra o maior elemento em uma lista. Para números inteiros — uma função, para números de ponto flutuante — outra, para strings — uma terceira. O código será duplicado e a manutenção se tornará difícil. Em Rust, esse problema é resolvido com Generics (programação genérica).
\nGenerics permitem escrever código que funciona com diferentes tipos de dados, sem perder a tipagem forte. Em vez de duplicar funções para cada tipo, você escreve uma versão genérica. Em tempo de compilação, o Rust substitui o tipo concreto e verifica a correção. Isso proporciona:
\n- \n
- Reutilização de código — uma função para todos os tipos. \n
- Segurança — todos os erros de tipo são detectados em tempo de compilação. \n
- Desempenho — graças à monomorfização (o compilador gera código separado para cada tipo usado), o código genérico é tão rápido quanto o especializado. \n
Neste artigo, vamos explorar como usar Generics em Rust com exemplos práticos, começando com funções simples e avançando para estruturas e traits.
\n\n1. Generics em funções: o primeiro passo para a universalidade
\nSintaxe e exemplo básico
\nPara tornar uma função genérica, você precisa especificar um parâmetro de tipo entre colchetes angulares <T>. Geralmente, usam-se letras maiúsculas: T, U, V. Veja como fica uma função que retorna o valor recebido:
fn identity<T>(value: T) -> T {\n value\n}\n\nfn main() {\n let num = identity(42);\n let text = identity("Hello");\n println!("{} {}", num, text); // Exibe: 42 Hello\n}\nO compilador infere o tipo automaticamente com base no argumento passado. Você também pode especificar o tipo explicitamente: identity::<i32>(42).
Restrições: traits como limites de tipo
\nE se quisermos comparar valores? Simplesmente escrever value1 > value2 não funcionará — nem todos os tipos suportam comparação. É necessário especificar que o tipo deve implementar o trait PartialOrd:
fn largest<T: PartialOrd>(list: &[T]) -> &T {\n let mut largest = &list[0];\n for item in list {\n if item > largest {\n largest = item;\n }\n }\n largest\n}\n\nfn main() {\n let numbers = vec![34, 50, 25, 100, 65];\n println!("Maior número: {}", largest(&numbers));\n\n let chars = vec!['y', 'm', 'a', 'q'];\n println!("Maior caractere: {}", largest(&chars));\n}\nAqui, T: PartialOrd é uma restrição de trait. Ela garante que o operador de comparação está definido para o tipo T. Sem essa restrição, o código não compilaria.
2. Generics em estruturas e enumerações
\nEstruturas com campos genéricos
\nGenerics também funcionam bem com tipos personalizados. Por exemplo, a estrutura Point pode armazenar coordenadas de qualquer tipo:
struct Point<T> {\n x: T,\n y: T,\n}\n\nfn main() {\n let integer_point = Point { x: 5, y: 10 };\n let float_point = Point { x: 1.0, y: 4.0 };\n // let mixed = Point { x: 5, y: 4.0 }; // Erro! Ambos os campos devem ser do mesmo tipo\n}\nSe você precisar que os campos possam ser de tipos diferentes, use vários parâmetros:
\nstruct Point<T, U> {\n x: T,\n y: U,\n}\n\nfn main() {\n let mixed = Point { x: 5, y: 4.0 }; // Agora funciona\n}\n\nEnumerações com Generics
\nA enumeração padrão Option<T> é um excelente exemplo de enumeração genérica. Você pode criar a sua própria:
enum Result<T, E> {\n Ok(T),\n Err(E),\n}\n\n// Uso\nfn divide(a: f64, b: f64) -> Result<f64, String> {\n if b