Rust Admin

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

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

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 Cargo.toml, você define features na seção [features]. 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 Cargo.toml. Aqui está um exemplo prático:

[package]
name = "minha-lib"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", optional = true }
tokio = { version = "1.0", optional = true, features = ["full"] }

[features]
default = ["serde-support"]
serde-support = ["serde"]
async-runtime = ["tokio"]
full = ["serde-support", "async-runtime"]

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

Ativando Features na Compilação

Para compilar com features específicas:

cargo build --features "serde-support"
cargo build --features "serde-support,async-runtime"
cargo build --all-features
cargo build --no-default-features

No seu código Rust, use o atributo #[cfg(feature = "nome")] para compilar condicionalmente:

#[cfg(feature = "serde-support")]
use serde::{Serialize, Deserialize};

pub struct Usuario {
    id: u32,
    nome: String,
}

#[cfg(feature = "serde-support")]
impl Serialize for Usuario {
    // implementação
}

#[cfg(feature = "async-runtime")]
pub async fn buscar_dados() {
    println!("Usando Tokio!");
}

Compilação Condicional em Rust

Atributos de Compilação Condicional

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

// Compilação específica para plataforma
#[cfg(target_os = "windows")]
fn obter_caminho() -> &'static str {
    "C:\\"
}

#[cfg(target_os = "unix")]
fn obter_caminho() -> &'static str {
    "/"
}

// Compilação em modo debug apenas
#[cfg(debug_assertions)]
fn debug_info() {
    println!("Modo debug ativo");
}

// Compilação condicional com macro cfg!()
fn main() {
    if cfg!(feature = "serde-support") {
        println!("Serde está habilitado");
    }

    if cfg!(target_pointer_width = "64") {
        println!("Sistema 64-bit");
    }
}

Combinando Múltiplas Condições

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

#[cfg(all(feature = "async-runtime", target_os = "linux"))]
pub async fn rotina_especifica_linux() {
    println!("Apenas em Linux com async-runtime");
}

#[cfg(any(feature = "serde-support", feature = "json-support"))]
pub fn serializar() {
    println!("Um dos suportes de serialização está ativo");
}

#[cfg(not(debug_assertions))]
pub fn otimizacoes_release() {
    println!("Compilado em release");
}

Exemplo Prático: Uma Biblioteca com Features Múltiplas

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

// src/lib.rs
use std::collections::HashMap;

#[cfg(feature = "serde-support")]
use serde::{Serialize, Deserialize};

#[cfg_attr(feature = "serde-support", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct Produto {
    pub id: u32,
    pub nome: String,
    pub preco: f64,
}

pub struct Catalogo {
    produtos: HashMap<u32, Produto>,
}

impl Catalogo {
    pub fn novo() -> Self {
        Catalogo {
            produtos: HashMap::new(),
        }
    }

    pub fn adicionar(&mut self, produto: Produto) {
        self.produtos.insert(produto.id, produto);
    }

    pub fn listar(&self) -> Vec<&Produto> {
        self.produtos.values().collect()
    }

    #[cfg(feature = "serde-support")]
    pub fn para_json(&self) -> Result<String, serde_json::Error> {
        serde_json::to_string_pretty(&self.produtos)
    }

    #[cfg(feature = "banco-dados")]
    pub async fn salvar_banco(&self) -> Result<(), Box<dyn std::error::Error>> {
        println!("Salvando no banco de dados...");
        Ok(())
    }
}

#[cfg(test)]
mod testes {
    use super::*;

    #[test]
    fn teste_catalogo() {
        let mut cat = Catalogo::novo();
        cat.adicionar(Produto {
            id: 1,
            nome: "Mouse".to_string(),
            preco: 50.0,
        });
        assert_eq!(cat.listar().len(), 1);
    }
}

Seu Cargo.toml:

[package]
name = "loja-lib"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", optional = true, features = ["derive"] }
serde_json = { version = "1.0", optional = true }

[features]
default = ["serde-support"]
serde-support = ["serde", "serde_json"]
banco-dados = []

Compilação:

cargo build --features "serde-support"
cargo build --no-default-features
cargo test --all-features

Conclusão

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

Referências


Artigos relacionados