Feature Flags em Rust: a arma secreta para desenvolvimento flexível
\n\nImagine que você está escrevendo uma biblioteca para processamento de imagens. Uns usuários querem apenas redimensionamento básico, outros querem correção de cor avançada, e outros ainda querem integração com redes neurais. Como deixar todos felizes, sem inflar o código e sem criar dezenas de versões? A resposta é simples — feature flags (ou feature toggles).
\n\nNo mundo Rust, essa técnica se transforma em uma verdadeira arte. Graças ao poderoso sistema de macros e compilação condicional, você pode criar aplicações modulares e configuráveis que se adaptam a qualquer cenário. E sim, você não precisará recompilar o projeto inteiro por causa de uma pequena funcionalidade.
\n\nNeste artigo, vamos explorar como funcionam os feature flags em Rust, mostrar suas vantagens com exemplos reais e dar dicas práticas. Vamos lá!
\n\nO que são feature flags e para que servem?
\n\nFeature flags (ou compilação condicional) é um mecanismo que permite ativar ou desativar partes específicas do código durante a compilação. Em Rust, isso é implementado através do sistema features no Cargo.toml e das diretivas #[cfg(feature = "...")].
Para que serve? Aqui estão três cenários principais:
\n\n- \n
- Modularidade de bibliotecas — o usuário decide quais funcionalidades precisa. \n
- Funcionalidades experimentais — permite testar novas funcionalidades sem risco para produção. \n
- Dependência de plataforma — código diferente para Windows, Linux e macOS. \n
Ao contrário dos flags em tempo de execução (que são verificados durante a execução), as Cargo features funcionam em tempo de compilação. Isso significa que o código não utilizado nem entra no binário — você obtém desempenho máximo e tamanho mínimo.
\n\nComo funcionam os feature flags em Rust: sintaxe básica
\n\nVamos direto ao código. Suponha que estamos escrevendo uma biblioteca image_tools com suporte opcional a WebP.
Primeiro, definimos a feature no Cargo.toml:
[package]\nname = "image_tools"\nversion = "0.1.0"\n\n[features]\ndefault = ["png"]\npng = []\nwebp = []\n\n\nAgora, no código, usamos o atributo #[cfg]:
// src/lib.rs\n\n#[cfg(feature = "png")]\npub mod png_processor;\n\n#[cfg(feature = "webp")]\npub mod webp_processor;\n\npub mod common;\n\n// Dentro do módulo:\n#[cfg(feature = "webp")]\npub fn encode_webp(data: &[u8]) -> Vec<u8> {\n // implementação para WebP\n println!("Codificando WebP...");\n data.to_vec()\n}\n\n#[cfg(not(feature = "webp"))]\npub fn encode_webp(data: &[u8]) -> Vec<u8> {\n panic!("Suporte a WebP não ativado. Ative a feature 'webp'.");\n}\n\n\nObserve o truque inteligente: fornecemos um placeholder para o caso de a feature estar desativada. Isso permite manter a API unificada, mas ao tentar usar uma função não suportada — obter um erro claro.
\n\nTécnicas avançadas: combinações e dependências
\n\nFeature flags em Rust permitem criar hierarquias complexas. Por exemplo, você pode criar uma feature "full" que ativa várias de uma vez:
[features]\ndefault = ["basic"]\nbasic = ["resize", "crop"]\nadvanced = ["color_correction", "filters"]\nfull = ["basic", "advanced", "webp"]\n\n\nE também é possível especificar dependências de crates externos:
\n\n[features]\nwebp = ["dep:libwebp-sys"] # inclui dependência do sistema\n\n[dependencies]\nlibwebp-sys = { version = "0.1", optional = true }\n\n\nAqui, optional = true informa ao Cargo que o crate só é incluído quando a feature correspondente é ativada. Isso economiza tempo de compilação e espaço em disco.
Feature flags em um projeto real: exemplo de servidor HTTP
\n\nVamos imaginar que estamos escrevendo um mini-framework p