Generics em Rust: para que servem e como usar para iniciantes

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: o que são Generics e para que servem em Rust?

\n

Imagine 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).

\n

Generics 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
\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\n

1. Generics em funções: o primeiro passo para a universalidade

\n

Sintaxe e exemplo básico

\n

Para 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:

\n
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}
\n

O compilador infere o tipo automaticamente com base no argumento passado. Você também pode especificar o tipo explicitamente: identity::<i32>(42).

\n\n

Restrições: traits como limites de tipo

\n

E 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:

\n
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}
\n

Aqui, 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.

\n\n

2. Generics em estruturas e enumerações

\n

Estruturas com campos genéricos

\n

Generics também funcionam bem com tipos personalizados. Por exemplo, a estrutura Point pode armazenar coordenadas de qualquer tipo:

\n
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}
\n

Se você precisar que os campos possam ser de tipos diferentes, use vários parâmetros:

\n
struct 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\n

Enumerações com Generics

\n

A enumeração padrão Option<T> é um excelente exemplo de enumeração genérica. Você pode criar a sua própria:

\n
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                

Blogs

Book Recommendations