Rust

Generics em Rust: Funções e Structs Parametrizadas por Tipo: Do Básico ao Avançado

8 min de leitura

Generics em Rust: Funções e Structs Parametrizadas por Tipo: Do Básico ao Avançado

Introdução aos Generics em Rust Generics são um dos pilares da programação moderna e Rust oferece uma implementação particularmente robusta e segura. Em essência, generics permitem que você escreva código parametrizado por tipo, eliminando duplicação sem comprometer a segurança de tipos. Diferentemente de linguagens como Java ou C#, Rust realiza a monomorphização em tempo de compilação: para cada tipo concreto usado com um generic, o compilador gera uma cópia especializada do código. Isso significa zero custo em runtime e máxima performance. Neste artigo, exploraremos como trabalhar com generics em funções e structs, começando pelos conceitos fundamentais até padrões avançados. Você aprenderá a aproveitar o poder dos generics para escrever código reutilizável, type-safe e eficiente. Generics em Funções Sintaxe Básica Funções genéricas recebem um parâmetro de tipo entre colchetes angulares . O identificador é uma convenção (como em Java), mas você pode usar qualquer nome válido. Veja este exemplo simples: Aqui, é um trait bound — uma restrição indicando que deve

<h2>Introdução aos Generics em Rust</h2>

<p>Generics são um dos pilares da programação moderna e Rust oferece uma implementação particularmente robusta e segura. Em essência, generics permitem que você escreva código parametrizado por tipo, eliminando duplicação sem comprometer a segurança de tipos. Diferentemente de linguagens como Java ou C#, Rust realiza a monomorphização em tempo de compilação: para cada tipo concreto usado com um generic, o compilador gera uma cópia especializada do código. Isso significa zero custo em runtime e máxima performance.</p>

<p>Neste artigo, exploraremos como trabalhar com generics em funções e structs, começando pelos conceitos fundamentais até padrões avançados. Você aprenderá a aproveitar o poder dos generics para escrever código reutilizável, type-safe e eficiente.</p>

<h2>Generics em Funções</h2>

<h3>Sintaxe Básica</h3>

<p>Funções genéricas recebem um parâmetro de tipo entre colchetes angulares <code>&lt;T&gt;</code>. O identificador <code>T</code> é uma convenção (como em Java), mas você pode usar qualquer nome válido. Veja este exemplo simples:</p>

<pre><code class="language-rust">fn maior&lt;T: std::cmp::PartialOrd&gt;(a: T, b: T) -&gt; T {

if a &gt; b {

a

} else {

b

}

}

fn main() {

println!(&quot;{}&quot;, maior(10, 20)); // 20

println!(&quot;{}&quot;, maior(3.14, 2.71)); // 3.14

println!(&quot;{}&quot;, maior(&quot;apple&quot;, &quot;zebra&quot;)); // &quot;zebra&quot;

}</code></pre>

<p>Aqui, <code>T: std::cmp::PartialOrd</code> é um <strong>trait bound</strong> — uma restrição indicando que <code>T</code> deve implementar a trait <code>PartialOrd</code> (comparação). Sem isso, o compilador não saberia como comparar dois valores de tipo <code>T</code>.</p>

<h3>Múltiplos Parâmetros de Tipo</h3>

<p>Você pode parametrizar uma função com vários tipos simultaneamente. Isso é especialmente útil para operações que envolvem tipos diferentes:</p>

<pre><code class="language-rust">fn tupla_invertida&lt;T, U&gt;(a: T, b: U) -&gt; (U, T) {

(b, a)

}

fn main() {

let resultado = tupla_invertida(42, &quot;Rust&quot;);

println!(&quot;{:?}&quot;, resultado); // (&quot;Rust&quot;, 42)

}</code></pre>

<p>Cada parâmetro de tipo é independente e pode ter suas próprias restrições. Você pode combinar múltiplos trait bounds usando <code>+</code>:</p>

<pre><code class="language-rust">fn processar&lt;T: std::fmt::Display + std::clone::Clone&gt;(valor: T) {

let copia = valor.clone();

println!(&quot;Original: {}, Cópia: {}&quot;, valor, copia);

}

fn main() {

processar(String::from(&quot;Olá, Rust!&quot;));

}</code></pre>

<h2>Generics em Structs</h2>

<h3>Definindo Structs Genéricas</h3>

<p>Structs genéricas armazenam valores de tipo parametrizado. Isso é fundamental para criar contêineres reutilizáveis:</p>

<pre><code class="language-rust">struct Caixa&lt;T&gt; {

conteudo: T,

}

impl&lt;T&gt; Caixa&lt;T&gt; {

fn novo(conteudo: T) -&gt; Caixa&lt;T&gt; {

Caixa { conteudo }

}

fn obter_conteudo(&amp;self) -&gt; &amp;T {

&amp;self.conteudo

}

fn consumir(self) -&gt; T {

self.conteudo

}

}

fn main() {

let caixa_int = Caixa::novo(42);

let caixa_str = Caixa::novo(&quot;Rust é incrível&quot;);

println!(&quot;{}&quot;, caixa_int.obter_conteudo());

println!(&quot;{}&quot;, caixa_str.obter_conteudo());

let valor = caixa_int.consumir();

println!(&quot;Consumido: {}&quot;, valor);

}</code></pre>

<p>Observe que <code>impl&lt;T&gt;</code> indica que os métodos funcionam para <em>qualquer</em> tipo <code>T</code>. A sintaxe <code>Caixa&lt;T&gt;::novo()</code> retorna uma instância parametrizada.</p>

<h3>Structs com Múltiplos Generics</h3>

<p>Assim como funções, structs podem ter vários parâmetros de tipo:</p>

<pre><code class="language-rust">struct Par&lt;T, U&gt; {

primeiro: T,

segundo: U,

}

impl&lt;T: std::fmt::Debug, U: std::fmt::Debug&gt; Par&lt;T, U&gt; {

fn exibir(&amp;self) {

println!(&quot;Primeiro: {:?}, Segundo: {:?}&quot;, self.primeiro, self.segundo);

}

}

fn main() {

let par = Par {

primeiro: 10,

segundo: &quot;texto&quot;,

};

par.exibir();

}</code></pre>

<p>Aqui, o trait bound <code>Debug</code> é aplicado apenas aos métodos que precisam exibir os valores. Outras operações na struct podem não exigir essa restrição.</p>

<h2>Trait Bounds e Implementações Especializadas</h2>

<h3>Restrições Avançadas</h3>

<p>Rust permite definir implementações especializadas para casos específicos. Isso é chamado de <strong>specialization</strong> (ainda em fase experimental) ou uso estratégico de trait bounds:</p>

<pre><code class="language-rust">struct Conteiner&lt;T&gt; {

items: Vec&lt;T&gt;,

}

// Implementação geral

impl&lt;T&gt; Conteiner&lt;T&gt; {

fn novo() -&gt; Self {

Conteiner { items: Vec::new() }

}

fn adicionar(&amp;mut self, item: T) {

self.items.push(item);

}

}

// Implementação especializada para tipos Clone

impl&lt;T: Clone&gt; Conteiner&lt;T&gt; {

fn duplicar_primeiro(&amp;mut self) {

if let Some(primeiro) = self.items.first() {

self.items.push(primeiro.clone());

}

}

}

fn main() {

let mut cont = Conteiner::novo();

cont.adicionar(5);

cont.adicionar(10);

cont.duplicar_primeiro();

println!(&quot;{:?}&quot;, cont.items); // [5, 10, 5]

}</code></pre>

<p>A implementação de <code>duplicar_primeiro()</code> está disponível apenas se <code>T</code> implementa <code>Clone</code>. Isso oferece flexibilidade sem comprometer a segurança.</p>

<h3>Where Clauses</h3>

<p>Para trait bounds complexos, a syntax <code>where</code> melhora a legibilidade:</p>

<pre><code class="language-rust">fn processar_pares&lt;T, U&gt;(a: T, b: U)

where

T: std::fmt::Display + std::cmp::PartialOrd&lt;U&gt;,

U: std::fmt::Display,

{

println!(&quot;Processando: {} e {}&quot;, a, b);

}</code></pre>

<p>A cláusula <code>where</code> deixa claro quais restrições se aplicam a cada tipo, especialmente útil em assinaturas complexas.</p>

<h2>Conclusão</h2>

<p>Generics em Rust permitem escrever código altamente reutilizável mantendo segurança de tipos total. Os três pontos-chave são: <strong>(1) funções genéricas reduzem duplicação</strong> parametrizando operações comuns; <strong>(2) structs genéricas criam contêineres flexíveis</strong> que funcionam com qualquer tipo, da mesma forma que <code>Vec&lt;T&gt;</code> e <code>Option&lt;T&gt;</code>; <strong>(3) trait bounds garantem que operações específicas estejam disponíveis</strong>, evitando erros em tempo de compilação. Dominando generics, você terá as ferramentas para escrever bibliotecas robustas e extensíveis.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://doc.rust-lang.org/book/ch10-00-generics.html" target="_blank" rel="noopener noreferrer">Rust Book - Generic Types, Traits, and Lifetimes</a></li>

<li><a href="https://doc.rust-lang.org/rust-by-example/generics.html" target="_blank" rel="noopener noreferrer">Rust by Example - Generics</a></li>

<li><a href="https://doc.rust-lang.org/reference/items/generics.html" target="_blank" rel="noopener noreferrer">Rust Reference - Generics</a></li>

<li><a href="https://doc.rust-lang.org/book/ch17-02-using-trait-objects.html" target="_blank" rel="noopener noreferrer">Trait Objects vs Generics</a></li>

</ul>

Comentários

Mais em Rust

Drop e o Ciclo de Vida de Recursos em Rust: Do Básico ao Avançado
Drop e o Ciclo de Vida de Recursos em Rust: Do Básico ao Avançado

O Trait Drop e o Ciclo de Vida de Recursos em Rust Rust gerencia memória sem...

Channels em Rust: Comunicação entre Threads com mpsc na Prática
Channels em Rust: Comunicação entre Threads com mpsc na Prática

Entendendo Channels e o Padrão MPSC Channels (canais) em Rust são primitivas...

Como Usar Enums em Rust: Definição, Variantes e Dados Associados em Produção
Como Usar Enums em Rust: Definição, Variantes e Dados Associados em Produção

Introdução: O Que São Enums em Rust? Enums (enumerações) são um dos pilares d...