Rust

Boas Práticas de Features e Compilação Condicional em Rust com Cargo para Times Ágeis

8 min de leitura

Boas Práticas de Features e Compilação Condicional em Rust com Cargo para Times Ágeis

Features em Rust: O Que São e Por Que Importam As features (características) em Rust são flags de compilação que permitem ativar ou desativar funcionalidades de uma crate. Pense nelas como interruptores que ligam ou desligam partes do seu código durante a compilação. Isso é especialmente útil quando você quer distribuir uma biblioteca com diferentes níveis de funcionalidade, reduzir tamanho de binário ou gerenciar dependências opcionais. No arquivo , você define features na seção . Cada feature pode ativar dependências extras, modificar o comportamento da compilação ou incluir módulos específicos. Isso oferece controle fino sobre o que é compilado e com que proposito. Definindo e Usando Features no Cargo.toml Estrutura Básica de Features O primeiro passo é declarar suas features no . Aqui está um exemplo prático: No exemplo acima, e são dependências opcionais. A feature ativa a dependência , enquanto ativa . A feature ativa ambas. Por padrão, é sempre compilada. Ativando Features na Compilação Para compilar com features

<h2>Features em Rust: O Que São e Por Que Importam</h2>

<p>As features (características) em Rust são flags de compilação que permitem ativar ou desativar funcionalidades de uma crate. Pense nelas como interruptores que ligam ou desligam partes do seu código durante a compilação. Isso é especialmente útil quando você quer distribuir uma biblioteca com diferentes níveis de funcionalidade, reduzir tamanho de binário ou gerenciar dependências opcionais.</p>

<p>No arquivo <code>Cargo.toml</code>, você define features na seção <code>[features]</code>. Cada feature pode ativar dependências extras, modificar o comportamento da compilação ou incluir módulos específicos. Isso oferece controle fino sobre o que é compilado e com que proposito.</p>

<h2>Definindo e Usando Features no Cargo.toml</h2>

<h3>Estrutura Básica de Features</h3>

<p>O primeiro passo é declarar suas features no <code>Cargo.toml</code>. Aqui está um exemplo prático:</p>

<pre><code class="language-toml">[package]

name = &quot;minha-lib&quot;

version = &quot;0.1.0&quot;

edition = &quot;2021&quot;

[dependencies]

serde = { version = &quot;1.0&quot;, optional = true }

tokio = { version = &quot;1.0&quot;, optional = true, features = [&quot;full&quot;] }

[features]

default = [&quot;serde-support&quot;]

serde-support = [&quot;serde&quot;]

async-runtime = [&quot;tokio&quot;]

full = [&quot;serde-support&quot;, &quot;async-runtime&quot;]</code></pre>

<p>No exemplo acima, <code>serde</code> e <code>tokio</code> são dependências opcionais. A feature <code>serde-support</code> ativa a dependência <code>serde</code>, enquanto <code>async-runtime</code> ativa <code>tokio</code>. A feature <code>full</code> ativa ambas. Por padrão, <code>serde-support</code> é sempre compilada.</p>

<h3>Ativando Features na Compilação</h3>

<p>Para compilar com features específicas:</p>

<pre><code class="language-bash">cargo build --features &quot;serde-support&quot;

cargo build --features &quot;serde-support,async-runtime&quot;

cargo build --all-features

cargo build --no-default-features</code></pre>

<p>No seu código Rust, use o atributo <code>#[cfg(feature = &quot;nome&quot;)]</code> para compilar condicionalmente:</p>

<pre><code class="language-rust">#[cfg(feature = &quot;serde-support&quot;)]

use serde::{Serialize, Deserialize};

pub struct Usuario {

id: u32,

nome: String,

}

#[cfg(feature = &quot;serde-support&quot;)]

impl Serialize for Usuario {

// implementação

}

#[cfg(feature = &quot;async-runtime&quot;)]

pub async fn buscar_dados() {

println!(&quot;Usando Tokio!&quot;);

}</code></pre>

<h2>Compilação Condicional em Rust</h2>

<h3>Atributos de Compilação Condicional</h3>

<p>Rust oferece vários atributos para controlar compilação além de features. Os principais são <code>#[cfg()]</code>, <code>#[cfg_attr()]</code> e a macro <code>cfg!()</code>.</p>

<pre><code class="language-rust">// Compilação específica para plataforma

#[cfg(target_os = &quot;windows&quot;)]

fn obter_caminho() -&gt; &amp;&#039;static str {

&quot;C:\\&quot;

}

#[cfg(target_os = &quot;unix&quot;)]

fn obter_caminho() -&gt; &amp;&#039;static str {

&quot;/&quot;

}

// Compilação em modo debug apenas

#[cfg(debug_assertions)]

fn debug_info() {

println!(&quot;Modo debug ativo&quot;);

}

// Compilação condicional com macro cfg!()

fn main() {

if cfg!(feature = &quot;serde-support&quot;) {

println!(&quot;Serde está habilitado&quot;);

}

if cfg!(target_pointer_width = &quot;64&quot;) {

println!(&quot;Sistema 64-bit&quot;);

}

}</code></pre>

<h3>Combinando Múltiplas Condições</h3>

<p>Você pode combinar múltiplas condições com <code>all()</code>, <code>any()</code> e <code>not()</code>:</p>

<pre><code class="language-rust">#[cfg(all(feature = &quot;async-runtime&quot;, target_os = &quot;linux&quot;))]

pub async fn rotina_especifica_linux() {

println!(&quot;Apenas em Linux com async-runtime&quot;);

}

#[cfg(any(feature = &quot;serde-support&quot;, feature = &quot;json-support&quot;))]

pub fn serializar() {

println!(&quot;Um dos suportes de serialização está ativo&quot;);

}

#[cfg(not(debug_assertions))]

pub fn otimizacoes_release() {

println!(&quot;Compilado em release&quot;);

}</code></pre>

<h2>Exemplo Prático: Uma Biblioteca com Features Múltiplas</h2>

<p>Vou demonstrar uma biblioteca completa que gerencia dados com suporte opcional a serialização e banco de dados:</p>

<pre><code class="language-rust">// src/lib.rs

use std::collections::HashMap;

#[cfg(feature = &quot;serde-support&quot;)]

use serde::{Serialize, Deserialize};

#[cfg_attr(feature = &quot;serde-support&quot;, derive(Serialize, Deserialize))]

#[derive(Debug, Clone)]

pub struct Produto {

pub id: u32,

pub nome: String,

pub preco: f64,

}

pub struct Catalogo {

produtos: HashMap&lt;u32, Produto&gt;,

}

impl Catalogo {

pub fn novo() -&gt; Self {

Catalogo {

produtos: HashMap::new(),

}

}

pub fn adicionar(&amp;mut self, produto: Produto) {

self.produtos.insert(produto.id, produto);

}

pub fn listar(&amp;self) -&gt; Vec&lt;&amp;Produto&gt; {

self.produtos.values().collect()

}

#[cfg(feature = &quot;serde-support&quot;)]

pub fn para_json(&amp;self) -&gt; Result&lt;String, serde_json::Error&gt; {

serde_json::to_string_pretty(&amp;self.produtos)

}

#[cfg(feature = &quot;banco-dados&quot;)]

pub async fn salvar_banco(&amp;self) -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {

println!(&quot;Salvando no banco de dados...&quot;);

Ok(())

}

}

#[cfg(test)]

mod testes {

use super::*;

#[test]

fn teste_catalogo() {

let mut cat = Catalogo::novo();

cat.adicionar(Produto {

id: 1,

nome: &quot;Mouse&quot;.to_string(),

preco: 50.0,

});

assert_eq!(cat.listar().len(), 1);

}

}</code></pre>

<p>Seu <code>Cargo.toml</code>:</p>

<pre><code class="language-toml">[package]

name = &quot;loja-lib&quot;

version = &quot;0.1.0&quot;

edition = &quot;2021&quot;

[dependencies]

serde = { version = &quot;1.0&quot;, optional = true, features = [&quot;derive&quot;] }

serde_json = { version = &quot;1.0&quot;, optional = true }

[features]

default = [&quot;serde-support&quot;]

serde-support = [&quot;serde&quot;, &quot;serde_json&quot;]

banco-dados = []</code></pre>

<p>Compilação:</p>

<pre><code class="language-bash">cargo build --features &quot;serde-support&quot;

cargo build --no-default-features

cargo test --all-features</code></pre>

<h2>Conclusão</h2>

<p>Dominando features e compilação condicional, você ganha três superpoderes em Rust: <strong>flexibilidade na distribuição</strong> de bibliotecas (ativar apenas o necessário), <strong>otimização de tamanho</strong> (remover código não utilizado em release), e <strong>controle multiplataforma</strong> (adaptar código para diferentes sistemas). Use features para dependências opcionais grandes, e <code>#[cfg()]</code> para pequenas variações de comportamento. Combine-as estrategicamente para criar bibliotecas profissionais e eficientes.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://doc.rust-lang.org/cargo/reference/features.html" target="_blank" rel="noopener noreferrer">The Rust Book - Features</a></li>

<li><a href="https://doc.rust-lang.org/cargo/reference/manifest.html" target="_blank" rel="noopener noreferrer">Cargo Manifest Documentation</a></li>

<li><a href="https://doc.rust-lang.org/reference/conditional-compilation.html" target="_blank" rel="noopener noreferrer">Conditional Compilation - Rust Reference</a></li>

<li><a href="https://github.com/rust-lang/rustlings" target="_blank" rel="noopener noreferrer">Rustlings - Features Exercise</a></li>

<li><a href="https://www.lurklurk.org/effective-rust/" target="_blank" rel="noopener noreferrer">Effective Rust - Feature Flags</a></li>

</ul>

Comentários

Mais em Rust

O que Todo Dev Deve Saber sobre WebAssembly com Rust: Compilando para o Navegador com wasm-pack
O que Todo Dev Deve Saber sobre WebAssembly com Rust: Compilando para o Navegador com wasm-pack

Introdução ao WebAssembly com Rust WebAssembly (WASM) é um formato binário qu...

O que Todo Dev Deve Saber sobre Streams Assíncronos e Concorrência Avançada com Tokio
O que Todo Dev Deve Saber sobre Streams Assíncronos e Concorrência Avançada com Tokio

Introdução ao Tokio: Fundações de Assincronismo em Rust Tokio é o runtime ass...

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