Genéricos em Rust: não tenha medo de generalizações, escreva código como um profissional

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

Genéricos em Rust: por que você não pode viver sem eles?

\n\n

Quando você começa a programar em Rust, parece que o sistema de tipos é um inimigo. O compilador reclama de cada vírgula, e a palavra Generic causa arrepios. Mas, na verdade, os genéricos (tipos genéricos) são seus melhores amigos. Eles permitem escrever código flexível, seguro e reutilizável sem perda de desempenho.

\n\n

Neste artigo, vamos entender o que são generics em Rust, como usá-los em funções, estruturas e traits. Você verá que a programação genérica não é mágica, mas uma ferramenta poderosa que torna seu código mais limpo e rápido.

\n\n

O que são genéricos e para que servem?

\n\n

Imagine que você está escrevendo uma função que encontra o elemento máximo em um array. Se não houvesse genéricos, você teria que escrever uma implementação separada para i32, f64, String e assim por diante. Isso é demorado, tedioso e viola o princípio DRY (Don't Repeat Yourself).

\n\n

Os genéricos resolvem esse problema. Eles permitem parametrizar tipos: você escreve uma única função que funciona com qualquer tipo de dado. O compilador gera as versões necessárias do código para cada caso específico. Esse processo é chamado de monomorfização.

\n\n

Sintaxe dos genéricos: do simples ao complexo

\n\n

1. Funções genéricas

\n\n

Vamos começar com um exemplo básico. Suponha que queremos criar uma função que retorna o valor passado (função identidade):

\n\n
fn identity<T>(value: T) -> T {\n    value\n}\n\nfn main() {\n    let n = identity(42);\n    let s = identity("hello");\n    println!("{}, {}", n, s); // 42, hello\n}
\n\n

Aqui, <T> é um parâmetro de tipo. T pode ser qualquer tipo: número, string, estrutura. Na chamada, o compilador descobre qual tipo usar. Isso funciona não por despacho dinâmico (como em Java ou Python), mas pela geração de código especializado em tempo de compilação. É por isso que Rust não sacrifica o desempenho.

\n\n

2. Restrições de tipo (Trait Bounds)

\n\n

Mas e se quisermos não apenas passar um valor, mas, por exemplo, comparar dois números? Apenas <T> não funciona, porque nem todos os tipos têm a operação de comparação definida. Precisamos especificar que T deve implementar o trait PartialOrd:

\n\n
fn max<T: PartialOrd>(a: T, b: T) -> T {\n    if a > b { a } else { b }\n}\n\nfn main() {\n    println!("{}", max(10, 20));        // 20\n    println!("{}", max(3.14, 2.71));    // 3.14\n}
\n\n

A notação T: PartialOrd é um trait bound. Ela diz ao compilador: "Use apenas tipos que tenham uma ordem de comparação definida". Tente passar duas strings para max — e funcionará, porque String também implementa PartialOrd. Mas se você passar uma estrutura sem implementação, receberá um erro de compilação.

\n\n

3. Múltiplas restrições

\n\n

Às vezes, é necessário que o tipo satisfaça vários traits ao mesmo tempo. Por exemplo, que seja comparável e clonável:

\n\n
fn clone_and_max<T: PartialOrd + Clone>(a: T, b: T) -> T {\n    let a_cloned = a.clone();\n    let b_cloned = b.clone();\n    if a_cloned > b_cloned { a } else { b }\n}
\n\n

A sintaxe T: PartialOrd + Clone significa que o tipo deve implementar ambos os traits. Isso é poderoso, mas não abuse: muitas restrições dificultam a legibilidade.

\n\n

Genéricos em estruturas e enumerações

\n\n

Estruturas genéricas

\n\n

Os genéricos funcionam muito bem não apenas com funções, mas também com tipos definidos pelo usuário. Um exemplo clássico é a estrutura Point, que pode armazenar coordenadas de qualquer tipo:

\n\n
struct Point<T> {\n    x: T,\n    y: T,\n}\n\nimpl<T> Point<T> {\n    fn new(x:                

Blogs

Book Recommendations