cargo-hack: Features e Powerset na CI Rust | Rust Brasil

Use cargo-hack para testar features, --each-feature e --feature-powerset em Rust. Guia com workspaces, CI, MSRV, nextest, exclusões e armadilhas comuns.

O cargo-hack é a ferramenta certa quando um crate Rust tem várias features opcionais e você precisa garantir que elas compilam e testam de verdade — sozinhas, juntas e sem o conjunto mágico que só passa no notebook do mantenedor. Instale com cargo install cargo-hack --locked, rode cargo hack check --each-feature na raiz do projeto e trate o resultado como o primeiro filtro de regressão de feature flags. Em bibliotecas, workspaces e crates publicados, isso costuma pegar mais bugs do que mais um cargo test com --all-features.

Este guia mostra o que o cargo-hack resolve, quando usar --each-feature ou --feature-powerset, como montar CI barata e útil, como combinar a ferramenta com cargo-nextest, cargo-msrv e workspaces, e quais armadilhas geram falsos verdes ou builds eternamente caros.

Resposta rápida: o fluxo recomendado

EtapaComando ou configuraçãoObjetivo
Instalarcargo install cargo-hack --lockedDisponibilizar o subcomando
Checagem diáriacargo hack check --each-featureValidar features isoladas
Testescargo hack test --each-featureExercitar a matriz no runtime
Powerset seletivocargo hack check --feature-powerset --depth 2Combinar poucas features
Workspacecargo hack check --workspace --each-featureCobrir vários pacotes
CI enxutacheck no PR, test/powerset no nightlyControlar custo

Antes de copiar flags para um pipeline crítico, confira cargo hack --help e o help do subcomando. A ferramenta evolui; fixar a versão na CI evita surpresas quando a sintaxe ou o comportamento padrão mudam.

Por que features Rust quebram sem ninguém perceber

Features do Cargo são poderosas e traiçoeiras. Elas permitem:

  • backends opcionais de TLS, banco ou runtime;
  • integrações com crates pesados sem inflar o default;
  • APIs experimentais atrás de flags;
  • builds enxutos para embedded, WASM ou CLI.

O problema é que a maioria dos times desenvolve com um conjunto fixo de features — normalmente as defaults do dia a dia ou --all-features no atalho local. Isso esconde regressões clássicas:

  1. uma feature opcional deixa de compilar sozinha;
  2. duas features juntas conflitam em tipos, features transitivas ou cfg;
  3. o default passa, mas --no-default-features quebra o consumidor minimalista;
  4. um exemplo, teste ou binário auxiliar só existe atrás de uma feature e nunca roda na CI;
  5. a feature A ativa uma dependência que eleva a MSRV sem aviso;
  6. o workspace compila o pacote app, mas a lib publicada fica inconsistente.

--all-features não resolve tudo. Em projetos com backends mutuamente exclusivos, ligar todas as features ao mesmo tempo pode ser uma configuração inválida. Em outros casos, --all-features mascara exatamente o bug de “feature sozinha não compila”.

O cargo-hack existe para transformar essa matriz informal em um comando repetível.

O que é cargo-hack

O cargo-hack é um subcomando do Cargo focado em executar o mesmo comando Cargo — check, test, clippy, build e afins — sob várias configurações. Em vez de escrever um shell script com loops de features, você declara a política:

  • cada feature isolada;
  • powerset completo ou limitado por profundidade;
  • exclusões e inclusões;
  • pacotes de um workspace;
  • toolchains diferentes.

Na prática, a ferramenta responde a perguntas de mantenedor:

  1. o crate compila sem features extras?
  2. cada feature opcional ainda compila sozinha?
  3. combinações importantes continuam válidas?
  4. o workspace inteiro respeita a mesma política?
  5. a matriz ainda passa na toolchain que a biblioteca promete suportar?

Ela não substitui bom desenho de features. Se o grafo de flags for caótico, o cargo-hack só vai revelar o caos mais cedo — o que já é uma vitória.

Instalando e primeiros comandos

cargo install cargo-hack --locked
cargo hack --version

Na raiz de um crate ou workspace:

# compila a base + cada feature opcional isolada
cargo hack check --each-feature

# equivalente em modo teste
cargo hack test --each-feature

# também valida o build sem default features
cargo hack check --each-feature --no-dev-deps

O padrão mental é simples:

  1. escolha o comando base (check, test, clippy);
  2. escolha a matriz (--each-feature, --feature-powerset, pacotes, toolchain);
  3. restrinja o que for caro ou inválido;
  4. automatize na CI o recorte que o time aguenta pagar em minutos.

Para inspeção rápida de regressão de API/compilação, prefira check. Para lógica condicional com cfg(feature = ...), você precisa de test em algum ponto da esteira.

--each-feature versus --feature-powerset

Essas duas flags são o coração do cargo-hack. Escolher errado é o jeito mais comum de transformar uma boa ideia em CI de 40 minutos.

--each-feature

Testa, em linhas gerais:

  • a configuração base;
  • cada feature opcional isoladamente, em cima dessa base.

O custo cresce de forma aproximadamente linear com o número de features. É o padrão saudável para a maioria das bibliotecas e para pull requests.

cargo hack check --each-feature
cargo hack test --each-feature

Use quando:

  • há muitas features e o powerset seria explosivo;
  • a maior parte das flags é independente;
  • você quer um sinal barato e frequente.

--feature-powerset

Testa combinações entre features. Sem limites, o crescimento é exponencial: 8 features opcionais já podem gerar dezenas ou centenas de combinações, dependendo de defaults e filtros.

cargo hack check --feature-powerset
cargo hack check --feature-powerset --depth 2

Use quando:

  • features interagem de verdade (serde + async, json + yaml, rt-tokio + macros);
  • o número de flags relevantes é pequeno;
  • o job pode ser mais lento (nightly, pré-release, cron).

Uma regra prática

SituaçãoMatriz recomendada
App interno com 1–2 featurescargo test normal ou --all-features
Lib com várias flags independentes--each-feature no PR
Lib com 3–5 flags que interagem--feature-powerset --depth 2
Backends mutuamente exclusivosmatriz explícita, não powerset cego
Workspace grande--each-feature por pacote publicado

Se a CI ficou lenta demais, o problema raramente é o cargo-hack em si: é a ausência de --depth, de exclusões e de separação entre job barato e job completo.

Features mutuamente exclusivas e configurações inválidas

Nem toda combinação deve ser testada. Exemplos clássicos:

  • native-tls versus rustls;
  • runtime-tokio versus runtime-async-std;
  • postgres versus mysql em um conector;
  • features experimentais que não compõem com a API estável.

Nesses casos, um powerset ingênuo produz falhas “esperadas” e ensina o time a ignorar a CI. Em vez disso, modele a matriz:

# exemplo conceitual: valide cada backend de TLS isolado
cargo hack check --feature-powerset --depth 1 \
  --include-features native-tls,rustls

Ou documente jobs separados:

job default ........ cargo test
job tls-native ..... cargo test --no-default-features --features native-tls
job tls-rustls ..... cargo test --no-default-features --features rustls
job each-feature ... cargo hack check --each-feature

O objetivo não é “rodar o máximo de combinações possíveis”. É rodar as combinações que um consumidor real pode escolher.

Se o Cargo.toml já expressa exclusão por documentação ou por erros de compilação propositais, deixe isso explícito no README e na CI. Surpresa é pior do que restrição.

Workspaces, pacotes publicados e --workspace

Em monorepos, o erro clássico é testar só o binário da aplicação e publicar uma lib interna inconsistente. O cargo-hack ajuda a alinhar a política:

cargo hack check --workspace --each-feature
cargo hack test -p minha-lib --each-feature

Boas práticas de workspace:

  1. Separe pacotes publicados de ferramentas internas. Nem todo crate do monorepo precisa da mesma matriz.
  2. Foque a matriz cara nos crates da API pública. Apps e crates de integração podem usar um subconjunto.
  3. Evite features “fantasma” só para acomodar o workspace. Se a feature existe, alguém vai ativá-la.
  4. Combine com a organização de workspaces. O guia de Cargo workspaces e monorepos cobre a estrutura; o cargo-hack cobre a verificação da matriz.

Em repositórios com dezenas de crates, comece por:

cargo hack check --workspace --each-feature --exclude crate-lento,crate-experimental

Depois reintroduza os excluídos em jobs dedicados.

Montando CI útil sem quebrar o orçamento

Uma esteira saudável costuma ter camadas:

Camada 1 — PR rápido

cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo hack check --each-feature
cargo nextest run

Aqui o cargo-hack usa check para baratear. O cargo-nextest cobre os testes da configuração principal com melhor paralelismo.

Camada 2 — PR completo ou merge queue

cargo hack test --each-feature

Suba para test quando houver lógica condicional relevante ou histórico de regressão em features.

Camada 3 — nightly / pré-release

cargo hack check --feature-powerset --depth 2
cargo hack test --feature-powerset --depth 2 --exclude-features unstable

É o lugar certo para powerset, toolchains extras e combinações caras.

Camada 4 — release

Antes de publicar no crates.io:

  1. rode a matriz que define o contrato da crate;
  2. verifique MSRV com cargo-msrv;
  3. rode cargo-semver-checks se a API pública importar;
  4. limpe dependências mortas com cargo-machete / cargo-udeps;
  5. revise supply chain com cargo-deny e SBOM.

O cargo-hack não é a única ferramenta da release, mas é a que responde: “as flags que documentamos ainda formam um produto coerente?”

Combinando cargo-hack com nextest, clippy e MSRV

nextest

cargo hack nextest run --each-feature

Confirme na versão instalada se o subcomando e os argumentos encaminhados estão como você espera. O ganho é rodar a matriz de features com o runner moderno de testes, em vez de reescrever a orquestração.

clippy

cargo hack clippy --each-feature -- -D warnings

Útil para bibliotecas em que uma feature opcional introduz padrões que o Clippy só enxerga com aquele cfg. Em PRs, isso pode ser caro; em muitos times fica no nightly.

MSRV e toolchains

O cargo-hack também ajuda a exercitar a mesma matriz em toolchains distintas. A divisão de responsabilidades fica clara:

FerramentaPergunta
cargo-msrvQual é a versão mínima que ainda compila o contrato?
cargo-hackAs combinações de features/pacotes continuam válidas?
cargo test / nextestO comportamento está correto em uma configuração?

Não use uma para fazer o trabalho da outra. Descobrir MSRV com powerset completo em toda PR é desperdiçar minutos de runner.

Armadilhas comuns

1. Tratar --all-features como prova suficiente

--all-features valida uma superposição. Não prova que cada feature funciona sozinha nem que o consumidor sem defaults consegue compilar.

2. Powerset sem --depth em crate “só um pouco complexa”

A complexidade explode rápido. Se o job passou de orçamento, limite profundidade, exclua features instáveis e separe backends exclusivos.

3. Features só de dev misturadas na matriz pública

Flags usadas apenas para testes internos, mocks ou benchmarks não deveriam definir o contrato do usuário final. Mantenha a matriz pública legível.

4. Exemplos e bins esquecidos

Uma feature pode compilar a lib e quebrar um exemplo. Quando exemplos fazem parte do produto, inclua targets relevantes na política de CI.

5. Falso verde por cache mal compreendido

Caches de CI aceleram, mas uma chave que ignora a matriz de features pode reutilizar artefatos no momento errado. Inclua features, target e toolchain na chave, ou aceite rebuilds mais honestos.

6. Documentação desencontrada do Cargo.toml

Se o README promete “ative serde e tls”, a CI precisa conhecer essa combinação. Documentação sem matriz é marketing; matriz sem documentação é surpresa.

7. Ignorar impacto em tempo de build e tamanho

Features opcionais podem puxar dependências enormes. Depois de estabilizar a matriz, use cargo-bloat e cargo-llvm-lines para entender o custo de cada caminho de build — especialmente em CLI, WASM e embedded.

Checklist para adotar cargo-hack

  1. Liste as features do Cargo.toml e classifique: default, opcional isolada, interdependente, mutuamente exclusiva, experimental.
  2. Escreva em uma frase o contrato de cada feature para o consumidor.
  3. Adote cargo hack check --each-feature localmente.
  4. Coloque a checagem barata no PR.
  5. Decida se alguma combinação exige powerset limitado.
  6. Exclua explicitamente combinações inválidas.
  7. Separe jobs de app, lib publicada e crates internos no workspace.
  8. Ligue a matriz de release a MSRV, semver e supply chain.
  9. Meça o tempo de CI antes e depois; ajuste --depth e exclusões com dados.
  10. Atualize README/changelog quando a política de features mudar.

cargo-hack e carreira Rust

Em vagas e entrevistas de backend, sistemas e plataforma, feature flags bem desenhadas são sinal de maturidade. Saber explicar:

  • por que --all-features não basta;
  • como montar uma matriz barata de PR;
  • como evitar backends exclusivos no mesmo powerset;
  • como a política de features interage com MSRV e semver;

diferencia quem só “usa Cargo” de quem mantém biblioteca ou monorepo de verdade. Se você publica crates, contribui com open source ou lidera a esteira de um time Rust no Brasil, cargo-hack é uma das ferramentas que mais devolvem confiança por minuto de CI.

Para quem está montando portfólio, um bom projeto é pegar uma lib pequena com 3–4 features, quebrar de propósito uma combinação e mostrar no README o job de cargo-hack que captura a regressão. Isso combina bem com o discurso de carreira em Rust e com práticas de testes em Rust.

Perguntas frequentes

O cargo-hack altera meu Cargo.toml?

Não. Ele orquestra comandos e combinações; a fonte da verdade continua sendo o Cargo.toml, os targets e a CI.

Preciso rodar powerset em todo pull request?

Na maioria dos projetos, não. Prefira --each-feature no PR e deixe powerset limitado para nightly, main ou pré-release.

E se meu projeto não tiver features opcionais?

Provavelmente você não precisa do cargo-hack agora. A ferramenta brilha quando existe matriz. Sem flags, invista em testes, lint e boa política de dependências.

Posso usar cargo-hack só com check e nunca testar features?

Pode como primeiro passo, mas é incompleto se o comportamento runtime depende de cfg(feature = ...). Compile cedo, teste o que importa.

Qual a relação com cargo-minimal-versions ou resoluções antigas?

São eixos diferentes. cargo-hack cobre features/pacotes/toolchains; políticas de versões mínimas de dependências resolvem outro risco de compatibilidade. Em crates sérios, os dois eixos aparecem cedo ou tarde.

Conclusão

Feature flags são parte do produto em Rust, não um detalhe de Cargo.toml. O cargo-hack transforma essa matriz em verificação automatizada: --each-feature para o dia a dia, powerset controlado para interações reais, workspace consciente do que é publicado, e CI em camadas para não pagar custo de release em todo commit.

Adote a ferramenta quando o crate já tiver — ou estiver prestes a ter — mais de um caminho de build relevante. Comece barato, documente o contrato, exclua o inválido e só então expanda a matriz.

Continue a trilha de tooling com cargo-nextest, cargo-msrv, cargo-semver-checks, cargo-machete e cargo-udeps e segurança de supply chain com cargo-deny. Juntas, essas peças ajudam a entregar crates que compilam no seu laptop, na CI e no projeto de quem depende de você.