Features & Optional Dependencies
Features do Cargo são a maneira padrão de compilar caminhos de código opcionais, conectar dependências opcionais e permitir que os consumidores paguem apenas pelo que habilitam.
Receita
[features]
default = ["std"]
std = ["dep:log"]
metrics = ["dep:prometheus"]
[dependencies]
log = { version = "0.4", optional = true }
prometheus = { version = "0.13", optional = true }#[cfg(feature = "metrics")]
pub fn export_metrics() { /* ... */ }Quando usar isso: Backends opcionais, código específico da plataforma ou para reduzir o tempo de compilação para consumidores da biblioteca.
Exemplo de Trabalho
Uma biblioteca com backends de banco de dados selecionados por feature:
[features]
default = ["postgres"]
postgres = ["dep:sqlx", "sqlx/postgres"]
sqlite = ["dep:sqlx", "sqlx/sqlite"]
[dependencies]
sqlx = { version = "0.8", optional = true, default-features = false }#[cfg(feature = "postgres")]
pub async fn connect(url: &str) -> sqlx::Result<sqlx::PgPool> {
sqlx::PgPool::connect(url).await
}
#[cfg(feature = "sqlite")]
pub async fn connect(url: &str) -> sqlx::Result<sqlx::SqlitePool> {
sqlx::SqlitePool::connect(url).await
}cargo build --no-default-features --features sqlite
cargo test --all-featuresO que isso demonstra:
- Dependências opcionais ficam inativas até que uma feature as habilite
- Os nomes das features devem descrever a capacidade, não o detalhe de implementação
- A sintaxe
dep:sqlxvincula uma feature diretamente a um crate opcional - O CI deve testar
--no-default-featurese--all-features
Mergulho Profundo
Composição de Features
[features]
full = ["json", "metrics"]
json = ["dep:serde", "serde/derive"]- Features podem habilitar outras features (semântica de união)
- Habilitar
fullinclui tudo o que ele lista - Evite ciclos: a feature A não deve habilitar B se B habilitar A
cfg no Código
#[cfg(all(feature = "postgres", not(feature = "sqlite")))]
compile_error!("enable at most one database backend");- Combine
feature,target_ose flagscfgpersonalizadas - Use
compile_error!para conjuntos de features mutuamente exclusivos - Documente as combinações de features necessárias na documentação do crate
Notas de Rust
#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(not(feature = "std"))]
use hashbrown::HashMap;#[cfg(test)]é separado das features do Cargocfg_attranexa atributos condicionalmenterequired-featuresem[[example]]controla as demonstrações executáveis
Armadilhas
- Features padrão implícitas em dependências - você puxa código pesado não intencionalmente. Correção:
default-features = falsee lista explícita de features. - Unificação de features em todo o workspace - um membro que habilita uma feature a habilita para todos os crates que compartilham essa dependência. Correção: separe backends incompatíveis em crates separados, se necessário.
- Renomeações de features que quebram -
features = ["old"]downstream quebra silenciosamente em tempo de compilação. Correção: trate renomeações de features como semver-major para bibliotecas. - Sem features para módulos internos - usar
pub+ feature para cada helper privado cria confusão na API. Correção: mantenha os módulos internos privados; controle apenas a superfície pública. - Testar apenas features padrão - caminhos opcionais se deterioram. Correção: matriz de CI com
--all-featurese jobs por feature.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Crates separados | Backends são grandes e mutuamente exclusivos | Módulo opcional pequeno de 50 linhas |
Apenas target_cfg | Código específico do sistema operacional sem dependências opcionais | Funcionalidade selecionável pelo usuário |
Crate cfg-if | Muitos branches cfg aninhados | Gates simples de uma flag |
FAQs
Features podem ser habilitadas em tempo de execução?
Não. Features são apenas para tempo de compilação. Alternadores em tempo de execução usam arquivos de configuração ou variáveis de ambiente após a compilação do conjunto correto de features.
Como os dependentes habilitam minhas features?
my_crate = { version = "1", features = ["metrics"] } em seu Cargo.toml.
O que é sintaxe de dependência fraca?
dep:serde em uma tabela de features habilita explicitamente a dependência opcional serde sem habilitar as features padrão do serde, a menos que você também as liste.
As features padrão devem ser mínimas?
Para bibliotecas, sim, quando o tempo de compilação é importante. Para aplicativos, os padrões podem incluir tudo o que você envia.
Como testar uma única feature?
cargo test --no-default-features --features sqlite compila apenas essa configuração.
Binários e bibliotecas podem compartilhar features?
[workspace.metadata] do Workspace ou nomes de features compartilhados em [workspace.dependencies] mantêm a nomenclatura consistente entre os membros.
E sobre bugs de unificação de features?
Se dois crates precisarem de versões incompatíveis da mesma dependência opcional, divida em pacotes de workspace separados ou use nomes de pacotes diferentes por meio de renomeação (avançado).
Como documentar features?
Liste cada feature no README e no rustdoc com exemplos de comandos de compilação. crates.io mostra flags de feature automaticamente a partir do manifesto.
Os nomes das features são API estável?
Trate-os como API pública para bibliotecas. Renomear ou remover features é uma mudança que quebra para consumidores que as habilitaram.
Como isso interage com build.rs?
build.rs pode emitir cargo:rustc-cfg=feature="foo" para capacidades de plataforma detectadas, complementando as features do manifesto.
Relacionados
- Cargo.toml Explicado - tabelas do manifesto
- Perfis e Configurações de Compilação - tamanho vs velocidade
- Scripts de Compilação (build.rs) - flags
cfgpersonalizadas - Design de Configuração e Features - design em nível de aplicativo
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4 e Polars 0.46+.