Introdução: o que são Generics e por que são necessários em Rust?
Rust é uma linguagem de programação de sistemas conhecida por sua segurança, desempenho e sistema de tipos rigoroso. Uma das ferramentas-chave que permite escrever código flexível e reutilizável são os Generics (tipos genéricos).
Generics permitem criar funções, structs, enums e métodos que podem trabalhar com diferentes tipos de dados sem a necessidade de duplicar código. Em vez de escrever uma função separada para i32, f64 ou String, você escreve uma única versão genérica.
Por exemplo, sem Generics, você teria que criar três funções diferentes para ordenar três tipos diferentes. Com Generics — apenas uma. Isso não só reduz a quantidade de código, mas também aumenta sua confiabilidade: o compilador verifica a correção dos tipos em tempo de compilação, e não em tempo de execução.
Parte principal: como os Generics funcionam em Rust
1. Funções genéricas: escreva uma vez — use para qualquer tipo
A maneira mais simples de conhecer Generics é através de funções genéricas. Imagine que você precisa escrever uma função que retorna o maior de dois valores. Em vez de escrevê-la para cada tipo separadamente, use uma generalização:
fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest}
fn main() { let numbers = vec![34, 50, 25, 100, 65]; let result = largest(&numbers); println!("Maior número: {}", result);
let chars = vec!['y', 'm', 'a', 'q']; let result = largest(&chars); println!("Maior caractere: {}", result);}Aqui, <T: PartialOrd> significa que a função funciona com qualquer tipo T que suporte comparação (trait PartialOrd). Isso é uma restrição de trait — um conceito importante que será abordado a seguir.
2. Structs e enums genéricos: flexibilidade na definição de dados
Generics não se aplicam apenas a funções, mas também a structs de dados. Um exemplo clássico é a struct Point, que pode armazenar coordenadas de diferentes tipos:
struct Point<T> { x: T, y: T,}
fn main() { let integer_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.0, y: 4.0 }; // let wont_work = Point { x: 5, y: 4.0 }; // Erro! Tipos diferentes}Se você precisar que x e y possam ser de tipos diferentes, use vários parâmetros de generalização:
struct Point<T, U> { x: T, y: U,}
fn main() { let mixed_point = Point { x: 5, y: 4.0 }; // Funciona!}O mesmo funciona para enums. O exemplo mais conhecido são Option<T> e Result<T, E>, que são genéricos:
enum Option<T> { Some(T), None,}
enum Result<T, E> { Ok(T), Err(E),}3. Métodos em structs genéricos: adicionando lógica
Você pode definir métodos para structs genéricos usando o bloco impl. É possível criar métodos que funcionam apenas para tipos específicos:
struct Point<T> { x: T, y: T,}
// Métodos para todos os tipos Timpl<T> Point<T> { fn x(&self) -> &T { &self.x }}
// Método apenas para o tipo f64impl Point<f64> { fn distance_from_origin(&self) -> f64 { (self.x.powi(2) + self.y.powi(2)).sqrt() }}
fn main() { let p = Point { x: 3.0, y: 4.0 }; println!("p.x = {}", p.x()); println!("Distância até a origem: {}", p.distance_