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

Generics em Rust: para que servem e como usar

Generics (genéricos) — um dos conceitos-chave do Rust, que permite escrever código flexível, reutilizável e seguro. Se você está começando a aprender Rust, entender os genéricos abrirá caminho para escrever bibliotecas e aplicações eficientes. Neste artigo, vamos explicar o que são Generics, para que servem e como aplicá-los na prática.



O que são Generics e para que servem?

Imagine que você precisa escrever uma função que encontra o elemento máximo em um array. Para números, uma implementação; para strings, outra. Para não copiar código para cada tipo, o Rust oferece programação genérica com Generics.

Genéricos permitem definir estruturas, funções, enumerações ou métodos que funcionam com qualquer tipo de dado, sem se prender a um específico. O compilador, durante a compilação, substitui pelo tipo real — esse processo é chamado de monomorfização.

Sem genéricos, você escreveria:

fn max_i32(list: &[i32]) -> i32 {    let mut max = list[0];    for &item in list {        if item > max {            max = item;        }    }    max}

fn max_f64(list: &[f64]) -> f64 { let mut max = list[0]; for &item in list { if item > max { max = item; } } max}

Com genéricos, isso se transforma em uma única função universal.



Fundamentos do uso de Generics

Funções genéricas

Para tornar uma função genérica, é necessário especificar um parâmetro de tipo entre colchetes angulares <T>:

fn max<T>(list: &[T]) -> T {    let mut max = &list[0];    for item in list {        if item > max {            max = item;        }    }    *max}

Mas esse código não compilará — o compilador dirá que para o tipo T não foi implementado o trait PartialOrd. É aqui que entram as restrições de traits.



Estruturas e enumerações genéricas

Estruturas também podem ser genéricas:

struct Point<T> {    x: T,    y: T,}

fn main() { let int_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.5, y: 3.8 };}

Você pode especificar vários parâmetros de tipo:

struct Pair<T, U> {    first: T,    second: U,}

Enumerações também suportam genéricos — lembre-se de Option<T> ou Result<T, E> da biblioteca padrão.



Métodos genéricos

Para estruturas com genéricos, é possível definir métodos:

impl<T> Point<T> {    fn x(&self) -> &T {        &self.x    }}

Ou apenas para um tipo específico:

impl Point<f64> {    fn distance_from_origin(&self) -> f64 {        (self.x.powi(2) + self.y.powi(2)).sqrt()    }}


Restrições de traits (Trait Bounds)

Para usar operações de comparação, adição ou outros métodos, é necessário especificar quais traits o tipo implementa. Voltemos à função max:

fn max<T: PartialOrd>(list: &[T]) -> T {    let mut max = &list[0];    for item in list {        if item > max {            max = item;        }    }    *max}

Agora o código compila — dissemos que T deve suportar ordenação parcial (PartialOrd).

Você pode combinar traits com +:

fn clone_and_compare<T: Clone + PartialOrd>(a: T, b: T) -> bool {    a.clone() > b.clone()}

Ou usar a sintaxe where para legibilidade:

fn some_function<T, U>(t: &T, u: &U) -> i32where    T:                

Blogs

Book Recommendations