Introdução
Python domina o ecossistema de data science e machine learning, e isso não vai mudar tão cedo. Porém, um segredo que poucos conhecem é que muitas das bibliotecas Python mais rápidas já são escritas em Rust por baixo. O Polars (DataFrames ultrarrápidos), o tokenizers da Hugging Face, o Ruff (linter Python mais rápido do mundo) e o uv (gerenciador de pacotes Python) são todos projetos Rust que expõem interfaces Python via PyO3.
Rust está ocupando um nicho estratégico em data science: o backend de alta performance. Quando seus pipelines de dados precisam processar bilhões de registros, quando seu modelo de ML precisa de inferência em tempo real com latência de milissegundos, ou quando você precisa deployar em ambientes com memória limitada (edge/IoT), Rust oferece uma alternativa que pode ser 10-100x mais rápida que Python puro.
O Ecossistema Rust para Data Science
Manipulação de Dados
| Biblioteca | Equivalente Python | Descrição |
|---|---|---|
| Polars | pandas | DataFrames com execução lazy, multithreaded e sem GIL |
| ndarray | NumPy | Arrays N-dimensionais com operações vetorizadas |
| csv | csv module | Leitura/escrita de CSV extremamente rápida |
| arrow-rs | PyArrow | Implementação do Apache Arrow em Rust |
Machine Learning
| Biblioteca | Equivalente Python | Descrição |
|---|---|---|
| Burn | PyTorch | Framework de deep learning nativo Rust |
| Candle | PyTorch (inferência) | Framework ML minimalista da Hugging Face |
| Linfa | scikit-learn | ML clássico (regressão, clustering, classificação) |
| smartcore | scikit-learn | Alternativa com foco em simplicidade |
| tch-rs | PyTorch | Bindings para libtorch (PyTorch C++) |
Interoperabilidade com Python
| Ferramenta | Função |
|---|---|
| PyO3 | Escrever extensões Python em Rust |
| maturin | Build tool para publicar crates Rust como pacotes pip |
Cargo.toml Base para Data Science
[package]
name = "analise-dados"
version = "0.1.0"
edition = "2021"
[dependencies]
polars = { version = "0.46", features = ["lazy", "csv", "json", "dtype-date", "dtype-datetime"] }
ndarray = "0.16"
linfa = "0.7"
linfa-linear = "0.7"
csv = "1"
serde = { version = "1", features = ["derive"] }
anyhow = "1"
Exemplo Prático: Análise de Dados com Polars
Polars é a estrela do ecossistema Rust para data science. Oferece uma API expressiva para manipulação de DataFrames com performance que supera pandas em praticamente todos os benchmarks.
use polars::prelude::*;
use anyhow::Result;
fn main() -> Result<()> {
// === Carregar dados de CSV ===
let df = CsvReadOptions::default()
.with_has_header(true)
.try_into_reader_with_file_path(Some("vendas.csv".into()))?
.finish()?;
println!("Shape: {:?}", df.shape());
println!("Primeiras linhas:\n{}", df.head(Some(5)));
// === Análise com API Lazy (otimização automática de queries) ===
let resultado = df.clone().lazy()
// Filtrar vendas acima de R$ 100
.filter(col("valor").gt(lit(100.0)))
// Agrupar por categoria
.group_by([col("categoria")])
.agg([
// Total de vendas por categoria
col("valor").sum().alias("total_vendas"),
// Média de valor
col("valor").mean().alias("media_valor"),
// Quantidade de transações
col("valor").count().alias("quantidade"),
// Maior venda
col("valor").max().alias("maior_venda"),
])
// Ordenar por total de vendas (decrescente)
.sort(["total_vendas"], SortMultipleOptions::default().with_order_descending(true))
.collect()?;
println!("\nAnálise por categoria:\n{}", resultado);
// === Criar novas colunas (feature engineering) ===
let df_features = df.lazy()
.with_columns([
// Desconto estimado
(col("valor") * lit(0.1)).alias("desconto"),
// Categorização de valor
when(col("valor").gt(lit(500.0)))
.then(lit("premium"))
.when(col("valor").gt(lit(100.0)))
.then(lit("medio"))
.otherwise(lit("basico"))
.alias("segmento"),
])
.collect()?;
println!("\nCom features:\n{}", df_features.head(Some(5)));
// === Estatísticas descritivas ===
let stats = df.describe(None)?;
println!("\nEstatísticas descritivas:\n{}", stats);
// === Exportar resultado ===
let mut arquivo = std::fs::File::create("resultado_analise.csv")?;
CsvWriter::new(&mut arquivo)
.finish(&mut resultado.clone())?;
println!("\nResultado exportado para resultado_analise.csv");
Ok(())
}
Polars: Performance Comparada com pandas
| Operação (10M linhas) | pandas | Polars (Rust) | Speedup |
|---|---|---|---|
| Leitura CSV | 8.2s | 0.9s | 9x |
| Group by + agregação | 3.1s | 0.2s | 15x |
| Join de dois DataFrames | 5.5s | 0.4s | 14x |
| Filter + sort | 2.8s | 0.15s | 19x |
| Uso de memória | 4.2 GB | 1.1 GB | 4x menos |
Machine Learning com Linfa
Linfa é o equivalente Rust do scikit-learn, oferecendo algoritmos clássicos de ML:
use linfa::prelude::*;
use linfa_linear::LinearRegression;
use ndarray::{array, Array1, Array2};
fn main() -> anyhow::Result<()> {
// Dados de treino: área (m²) -> preço (R$ mil)
let features: Array2<f64> = array![
[45.0],
[60.0],
[75.0],
[90.0],
[110.0],
[130.0],
[150.0],
[180.0],
];
let targets: Array1<f64> = array![
180.0, 240.0, 310.0, 380.0, 460.0, 540.0, 620.0, 750.0
];
// Criar dataset
let dataset = Dataset::new(features.clone(), targets.clone());
// Treinar modelo de regressão linear
let modelo = LinearRegression::default()
.fit(&dataset)?;
// Fazer previsões
let novos_imoveis: Array2<f64> = array![
[50.0],
[100.0],
[200.0],
];
let previsoes = modelo.predict(&novos_imoveis);
println!("=== Previsão de Preços de Imóveis ===\n");
let areas = [50.0, 100.0, 200.0];
for (area, preco) in areas.iter().zip(previsoes.iter()) {
println!(" Área: {}m² → Preço estimado: R$ {:.0} mil", area, preco);
}
// Avaliar modelo nos dados de treino
let previsoes_treino = modelo.predict(&features);
let residuos: Array1<f64> = &targets - &previsoes_treino;
let mse = residuos.mapv(|r| r * r).mean().unwrap();
let rmse = mse.sqrt();
println!("\n=== Métricas do Modelo ===");
println!(" RMSE: R$ {:.2} mil", rmse);
println!(" Erro médio: R$ {:.2} mil", residuos.mapv(f64::abs).mean().unwrap());
Ok(())
}
Deep Learning com Burn
Burn é um framework de deep learning nativo em Rust, com suporte a múltiplos backends (CPU, CUDA, Wasm):
[dependencies]
burn = { version = "0.16", features = ["train", "ndarray"] }
use burn::prelude::*;
use burn::module::Module;
use burn::nn;
use burn::tensor::backend::Backend;
/// Rede neural para classificação de dígitos (MNIST-like).
#[derive(Module, Debug)]
pub struct ClassificadorDigitos<B: Backend> {
conv1: nn::conv::Conv2d<B>,
conv2: nn::conv::Conv2d<B>,
pool: nn::pool::AdaptiveAvgPool2d,
dropout: nn::Dropout,
linear1: nn::Linear<B>,
linear2: nn::Linear<B>,
activation: nn::Relu,
}
impl<B: Backend> ClassificadorDigitos<B> {
pub fn new(device: &B::Device) -> Self {
let conv1 = nn::conv::Conv2dConfig::new([1, 8], [3, 3])
.init(device);
let conv2 = nn::conv::Conv2dConfig::new([8, 16], [3, 3])
.init(device);
let pool = nn::pool::AdaptiveAvgPool2dConfig::new([8, 8])
.init();
let dropout = nn::DropoutConfig::new(0.5).init();
let linear1 = nn::LinearConfig::new(16 * 8 * 8, 128)
.init(device);
let linear2 = nn::LinearConfig::new(128, 10)
.init(device);
let activation = nn::Relu::new();
Self {
conv1,
conv2,
pool,
dropout,
linear1,
linear2,
activation,
}
}
pub fn forward(&self, images: Tensor<B, 4>) -> Tensor<B, 2> {
let x = self.conv1.forward(images);
let x = self.activation.forward(x);
let x = self.conv2.forward(x);
let x = self.activation.forward(x);
let x = self.pool.forward(x);
let x = self.dropout.forward(x);
// Flatten
let batch_size = x.dims()[0];
let x = x.reshape([batch_size, 16 * 8 * 8]);
let x = self.linear1.forward(x);
let x = self.activation.forward(x);
let x = self.dropout.forward(x);
self.linear2.forward(x)
}
}
Interoperabilidade Python com PyO3
A forma mais pragmática de usar Rust em data science é escrevendo extensões Python. PyO3 permite expor funções Rust como módulos Python nativos:
[package]
name = "meu-modulo-rapido"
version = "0.1.0"
edition = "2021"
[lib]
name = "meu_modulo_rapido"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.23", features = ["extension-module"] }
rayon = "1"
use pyo3::prelude::*;
use rayon::prelude::*;
/// Calcula estatísticas descritivas de forma paralela.
/// Muito mais rápido que Python puro para arrays grandes.
#[pyfunction]
fn estatisticas_rapidas(dados: Vec<f64>) -> PyResult<(f64, f64, f64, f64, f64)> {
if dados.is_empty() {
return Err(pyo3::exceptions::PyValueError::new_err(
"Array vazio"
));
}
let n = dados.len() as f64;
// Soma e média (paralelo)
let soma: f64 = dados.par_iter().sum();
let media = soma / n;
// Variância (paralelo)
let variancia: f64 = dados
.par_iter()
.map(|x| (x - media).powi(2))
.sum::<f64>()
/ n;
let desvio_padrao = variancia.sqrt();
// Min e max (paralelo)
let min = dados.par_iter().cloned().reduce(|| f64::INFINITY, f64::min);
let max = dados.par_iter().cloned().reduce(|| f64::NEG_INFINITY, f64::max);
Ok((media, desvio_padrao, min, max, soma))
}
/// Normaliza um array usando z-score (paralelo).
#[pyfunction]
fn normalizar_zscore(dados: Vec<f64>) -> PyResult<Vec<f64>> {
let n = dados.len() as f64;
let media: f64 = dados.par_iter().sum::<f64>() / n;
let desvio: f64 = (dados
.par_iter()
.map(|x| (x - media).powi(2))
.sum::<f64>()
/ n)
.sqrt();
if desvio == 0.0 {
return Ok(vec![0.0; dados.len()]);
}
Ok(dados
.par_iter()
.map(|x| (x - media) / desvio)
.collect())
}
/// Módulo Python exposto via PyO3.
#[pymodule]
fn meu_modulo_rapido(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(estatisticas_rapidas, m)?)?;
m.add_function(wrap_pyfunction!(normalizar_zscore, m)?)?;
Ok(())
}
Usando em Python
# Instalar: pip install maturin && maturin develop
import meu_modulo_rapido
import numpy as np
import time
# Gerar dados
dados = np.random.randn(10_000_000).tolist()
# Benchmark: Rust vs NumPy
inicio = time.time()
media_rust, std_rust, min_rust, max_rust, soma_rust = meu_modulo_rapido.estatisticas_rapidas(dados)
tempo_rust = time.time() - inicio
inicio = time.time()
arr = np.array(dados)
media_np, std_np, min_np, max_np, soma_np = arr.mean(), arr.std(), arr.min(), arr.max(), arr.sum()
tempo_numpy = time.time() - inicio
print(f"Rust: {tempo_rust:.3f}s")
print(f"NumPy: {tempo_numpy:.3f}s")
print(f"Speedup: {tempo_numpy/tempo_rust:.1f}x")
Empresas Usando Rust em Data Science
- Hugging Face: Tokenizers library (10x mais rápido que Python), Candle (inferência ML)
- Pola.rs: Empresa por trás do Polars, financiada para competir com pandas
- Astral: uv (gerenciador de pacotes Python) e ruff (linter) — ferramentas para o ecossistema Python, escritas em Rust
- Databricks: Componentes de processamento de dados de alta performance
- Cloudflare: Modelos de ML para detecção de ameaças no edge
- Discord: Pipeline de processamento de dados em tempo real
Como Começar
- Aprenda Rust: Tutorial de primeiros passos — domine a linguagem antes de mergulhar em data science
- Comece com CSV: Use nossa receita de leitura de CSV para entender I/O de dados
- Explore Polars: Comece substituindo scripts pandas por Polars em Rust
- PyO3 para pragmatismo: Se seu time usa Python, escreva os gargalos em Rust com PyO3
- Linfa para ML clássico: Experimente regressão e classificação antes de partir para deep learning
- Burn para deep learning: Quando precisar de redes neurais nativas em Rust
Conclusão
Rust não vai substituir Python como a interface de data science — e não precisa. O papel de Rust é ser o motor de alta performance por trás das ferramentas que cientistas de dados usam todos os dias. Se você é um data scientist, aprender Rust com PyO3 permite acelerar seus pipelines em ordens de magnitude. Se você é um desenvolvedor Rust, o ecossistema de data science oferece oportunidades crescentes para construir as ferramentas da próxima geração.
Veja Também
- Receita: Ler CSV — Entrada de dados tabulares em Rust
- Receita: Parse JSON — Trabalhe com dados semi-estruturados
- Rust para WebAssembly — Deploy modelos ML no navegador via Wasm
- Tutorial: Concorrência — Processamento paralelo de dados
- Rust para Desenvolvimento Web — APIs para servir modelos ML
- Empresas que Usam Rust — Veja quem usa Rust em data engineering