Processar consultas de CPF de forma assíncrona em Rust com Tokio permite executar dezenas de chamadas à API da CPFHub.io em paralelo, aproveitando o modelo de concorrência sem threads pesadas do SO. Com semáforos do Tokio você controla quantas requisições correm simultaneamente, distribuindo o volume ao longo do tempo — sem precisar se preocupar com bloqueio por quota, já que a API nunca retorna HTTP 429: consultas além do plano são simplesmente cobradas a R$0,15 cada.
Introdução
Quando é necessário processar um grande volume de consultas de CPF, a execução sequencial pode se tornar um gargalo significativo. O Tokio, runtime assíncrono mais popular de Rust, permite executar múltiplas consultas simultaneamente sem o overhead de threads do sistema operacional.
Configurando o ambiente assíncrono
Adicione as dependências necessárias para processamento assíncrono:
// Cargo.toml
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
futures = "0.3"
| Crate | Papel no processamento |
|---|---|
tokio | Runtime assíncrono e primitivas de concorrência |
reqwest | Cliente HTTP assíncrono |
futures | Utilitários para trabalhar com futures e streams |
serde | Desserialização da resposta JSON |
Para a documentação completa do Tokio e seu modelo de concorrência, consulte o guia oficial do Tokio.
Criando o cliente compartilhado
Utilize um reqwest::Client compartilhado para reutilizar conexões TCP entre as consultas:
use reqwest::header::{HeaderMap, HeaderValue};
use serde::Deserialize;
use std::sync::Arc;
#[derive(Debug, Deserialize)]
pub struct CpfResponse {
pub success: bool,
pub data: CpfData,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CpfData {
pub cpf: String,
pub name: String,
pub name_upper: String,
pub gender: String,
pub birth_date: String,
pub day: String,
pub month: String,
pub year: String,
}
pub struct CpfClient {
client: reqwest::Client,
api_key: String,
}
impl CpfClient {
pub fn new(api_key: String) -> Arc<Self> {
let client = reqwest::Client::builder()
.pool_max_idle_per_host(20)
.timeout(std::time::Duration::from_secs(10))
.build()
.expect("Falha ao criar cliente HTTP");
Arc::new(Self { client, api_key })
}
pub async fn consultar(&self, cpf: &str) -> Result<CpfResponse, String> {
let mut headers = HeaderMap::new();
headers.insert(
"x-api-key",
HeaderValue::from_str(&self.api_key).map_err(|e| e.to_string())?,
);
let url = format!("https://api.cpfhub.io/cpf/{}", cpf);
let response = self.client
.get(&url)
.headers(headers)
.send()
.await
.map_err(|e| format!("Erro na requisição: {}", e))?;
response.json::<CpfResponse>()
.await
.map_err(|e| format!("Erro ao desserializar: {}", e))
}
}
Processamento em lote com join_all
Para processar um lote de CPFs em paralelo, utilize futures::future::join_all:
use futures::future::join_all;
async fn processar_lote(
client: &Arc<CpfClient>,
cpfs: Vec<String>,
) -> Vec<(String, Result<CpfResponse, String>)> {
let futures: Vec<_> = cpfs.into_iter().map(|cpf| {
let client = Arc::clone(client);
async move {
let resultado = client.consultar(&cpf).await;
(cpf, resultado)
}
}).collect();
join_all(futures).await
}
Controlando concorrência com semáforo
Use um semáforo do Tokio para limitar a concorrência e distribuir o volume de consultas de forma controlada:
use tokio::sync::Semaphore;
async fn processar_com_limite(
client: &Arc<CpfClient>,
cpfs: Vec<String>,
max_concurrent: usize,
) -> Vec<(String, Result<CpfResponse, String>)> {
let semaphore = Arc::new(Semaphore::new(max_concurrent));
let mut handles = Vec::new();
for cpf in cpfs {
let client = Arc::clone(client);
let semaphore = Arc::clone(&semaphore);
let handle = tokio::spawn(async move {
let _permit = semaphore.acquire().await
.expect("Semáforo fechado");
let resultado = client.consultar(&cpf).await;
(cpf, resultado)
});
handles.push(handle);
}
let mut resultados = Vec::new();
for handle in handles {
if let Ok(resultado) = handle.await {
resultados.push(resultado);
}
}
resultados
}
Exemplo completo com relatório
Monte o programa que processa um lote e gera um relatório dos resultados:
#[tokio::main]
async fn main() {
let api_key = std::env::var("CPFHUB_API_KEY")
.expect("Defina CPFHUB_API_KEY");
let client = CpfClient::new(api_key);
let cpfs: Vec<String> = vec![
"12345678900", "98765432100", "11122233344",
"55566677788", "99988877766",
].into_iter().map(String::from).collect();
let total = cpfs.len();
println!("Processando {} CPFs com limite de 3 simultâneos...", total);
let inicio = std::time::Instant::now();
let resultados = processar_com_limite(&client, cpfs, 3).await;
let duracao = inicio.elapsed();
let mut sucessos = 0;
let mut falhas = 0;
for (cpf, resultado) in &resultados {
match resultado {
Ok(r) if r.success => {
println!("[OK] {} -> {}", cpf, r.data.name);
sucessos += 1;
}
Ok(_) => {
println!("[--] {} -> Não encontrado", cpf);
falhas += 1;
}
Err(e) => {
println!("[ER] {} -> {}", cpf, e);
falhas += 1;
}
}
}
println!("\nRelatório:");
println!(" Total: {}", total);
println!(" Sucessos: {}", sucessos);
println!(" Falhas: {}", falhas);
println!(" Tempo: {:.2?}", duracao);
}
Perguntas frequentes
A API CPFHub.io retorna HTTP 429 quando muitas consultas assíncronas são disparadas ao mesmo tempo?
Não. A API da CPFHub.io nunca retorna HTTP 429 nem bloqueia requisições por volume, independentemente de quantas chamadas assíncronas você dispare em paralelo. Quando o volume mensal do plano é ultrapassado, as consultas extras são cobradas a R$0,15 cada. O semáforo do Tokio neste artigo serve para controlar o consumo de recursos locais (conexões, memória), não para evitar bloqueio da API.
Por que usar Arc<CpfClient> em vez de criar um cliente por tarefa assíncrona?
O reqwest::Client reutiliza um pool de conexões TCP, o que reduz significativamente a latência e o consumo de recursos. Criar um cliente por tarefa desperdiça esse pool e pode esgotar as portas do SO em volume alto. Com Arc, todas as tasks compartilham o mesmo cliente de forma segura entre threads.
Qual é a diferença prática entre join_all e o semáforo com tokio::spawn?
join_all dispara todas as futures simultaneamente, sem limite de concorrência — adequado para lotes pequenos. O semáforo com tokio::spawn limita quantas consultas rodam ao mesmo tempo, o que é mais adequado para lotes grandes onde você quer controlar o uso de memória e conexões. Para a maioria dos casos de produção com a CPFHub.io, use o semáforo.
Como medir o throughput real de consultas assíncronas de CPF em Rust?
Use std::time::Instant::now() antes e elapsed() depois do lote, como no exemplo completo deste artigo. Para métricas mais detalhadas em produção, considere integrar com a crate metrics ou exportar para Prometheus — veja o artigo sobre observabilidade na integração com API de CPF.
Conclusão
O processamento assíncrono com Tokio permite escalar consultas de CPF de forma eficiente, mantendo controle sobre a concorrência e o consumo de recursos. A combinação de semáforos, futures e o cliente HTTP compartilhado resulta em alto throughput com uso mínimo de memória e conexões. A API da CPFHub.io nunca bloqueia por volume — o que significa que sua lógica de retry não precisa lidar com HTTP 429, simplificando o código.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a processar consultas de CPF em escala com Rust e Tokio.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



