PyO3: Integrando Rust e Python para Performance
Aprenda a usar PyO3 e maturin para criar extensões Python em Rust. Tutorial completo com exemplos práticos, benchmarks e casos de uso como Polars e Ruff.
Se existe uma combinação que tem revolucionado o ecossistema Python nos últimos anos, é Python + Rust. Ferramentas que você provavelmente já usa — como Ruff, Polars, Pydantic v2 e o ty type checker — são escritas em Rust e expostas para Python através do PyO3.
O PyO3 é um framework que permite escrever módulos nativos para Python usando Rust, com uma ergonomia surpreendente. Neste artigo, você vai aprender a configurar um projeto PyO3, criar funções e classes acessíveis pelo Python e entender por que essa abordagem supera alternativas tradicionais como ctypes e cffi.
Por que usar Rust a partir do Python?
Python é excelente para produtividade, mas seus gargalos de performance são conhecidos. Quando o multiprocessing não resolve ou o código precisa de controle fino sobre memória, as extensões nativas entram em cena.
Rust oferece vantagens únicas para esse cenário:
- Performance previsível — sem garbage collector, sem overhead de runtime
- Segurança de memória — o compilador previne data races e acessos inválidos em tempo de compilação
- Binários pequenos — extensões Rust são compactas e não exigem runtime adicional
- Ecossistema maduro — crates para parsing, criptografia, compressão e muito mais
Comparado com C/C++ via ctypes ou cffi, o PyO3 oferece uma experiência muito mais segura e ergonômica, com conversão automática de tipos entre Rust e Python.
Configurando o ambiente
Você precisa de Rust (via rustup) e do maturin, a ferramenta de build para projetos PyO3. Recomendamos usar o uv para gerenciar o ambiente Python:
# Instalar Rust (se ainda não tem)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Criar ambiente virtual e instalar maturin
uv venv .venv
source .venv/bin/activate
uv pip install maturin
Agora crie um novo projeto PyO3:
# Criar projeto com template pyo3
maturin init --bindings pyo3
# Estrutura gerada:
# ├── Cargo.toml
# ├── pyproject.toml
# └── src/
# └── lib.rs
Estrutura do Cargo.toml
O Cargo.toml gerado já vem configurado para PyO3:
[package]
name = "meu_modulo"
version = "0.1.0"
edition = "2021"
[lib]
name = "meu_modulo"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.23", features = ["extension-module"] }
O crate-type = ["cdylib"] indica que o Rust vai gerar uma biblioteca dinâmica compatível com Python. A feature extension-module garante que o módulo funcione corretamente quando importado.
Criando funções Python em Rust
Vamos criar uma função que calcula a soma de quadrados de uma lista — um exemplo simples que demonstra a diferença de performance:
use pyo3::prelude::*;
/// Calcula a soma dos quadrados de um vetor de inteiros.
#[pyfunction]
fn soma_quadrados(numeros: Vec<i64>) -> i64 {
numeros.iter().map(|n| n * n).sum()
}
/// Módulo Python gerado pelo PyO3.
#[pymodule]
fn meu_modulo(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(soma_quadrados, m)?)?;
Ok(())
}
O macro #[pyfunction] transforma uma função Rust comum em uma função chamável pelo Python. O #[pymodule] define o ponto de entrada do módulo.
Compilando e testando
Com o maturin, a compilação e instalação são feitas em um único comando:
# Compilar e instalar no ambiente virtual ativo
maturin develop --release
Agora você pode usar o módulo diretamente no Python:
import meu_modulo
resultado = meu_modulo.soma_quadrados([1, 2, 3, 4, 5])
print(resultado) # 55
Criando classes com #[pyclass]
O PyO3 também permite expor structs Rust como classes Python:
use pyo3::prelude::*;
#[pyclass]
struct Contador {
valor: i64,
}
#[pymethods]
impl Contador {
#[new]
fn new(inicio: i64) -> Self {
Contador { valor: inicio }
}
fn incrementar(&mut self, n: i64) {
self.valor += n;
}
fn obter(&self) -> i64 {
self.valor
}
}
#[pymodule]
fn meu_modulo(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Contador>()?;
Ok(())
}
No Python, a classe funciona naturalmente:
from meu_modulo import Contador
c = Contador(0)
c.incrementar(10)
print(c.obter()) # 10
Benchmark: Rust vs Python puro
Vamos comparar a função soma_quadrados em ambas as linguagens com uma lista de 10 milhões de elementos:
import time
def soma_quadrados_py(numeros):
return sum(n * n for n in numeros)
dados = list(range(10_000_000))
# Python puro
inicio = time.perf_counter()
soma_quadrados_py(dados)
tempo_py = time.perf_counter() - inicio
# Rust via PyO3
import meu_modulo
inicio = time.perf_counter()
meu_modulo.soma_quadrados(dados)
tempo_rs = time.perf_counter() - inicio
print(f"Python: {tempo_py:.3f}s")
print(f"Rust: {tempo_rs:.3f}s")
print(f"Speedup: {tempo_py / tempo_rs:.1f}x")
| Implementação | Tempo (10M elementos) | Speedup |
|---|---|---|
| Python puro (generator) | ~1.8s | 1x |
| NumPy | ~0.04s | ~45x |
| Rust via PyO3 | ~0.02s | ~90x |
A extensão Rust supera até o NumPy nesse cenário, porque evita a alocação de arrays intermediários e opera diretamente sobre os dados.
Casos de uso reais
Os projetos Python mais influentes de 2024-2026 usam PyO3:
| Projeto | O que faz | Por que usa Rust |
|---|---|---|
| Ruff | Linter e formatador | 10-100x mais rápido que Flake8 |
| Polars | DataFrame analytics | Processamento paralelo sem GIL |
| Pydantic v2 | Validação de dados | Core de validação 5-50x mais rápido |
| uv | Gerenciador de pacotes | Resolução de dependências em segundos |
| ty | Type checker | Análise de tipos ultrarrápida |
Esses projetos demonstram um padrão claro: manter a interface Pythonica enquanto o trabalho pesado roda em Rust.
PyO3 vs ctypes vs cffi
| Característica | PyO3 | ctypes | cffi |
|---|---|---|---|
| Linguagem | Rust | C | C |
| Segurança de memória | Garantida pelo compilador | Manual | Manual |
| Conversão de tipos | Automática | Manual | Semi-automática |
| Suporte a classes | Nativo (#[pyclass]) | Limitado | Limitado |
| Async/await | Suportado | Não | Não |
| Ergonomia | Alta | Baixa | Média |
O PyO3 é a escolha mais moderna e segura para extensões nativas. Se você já usa Rust ou está disposto a aprender, a curva de aprendizado compensa rapidamente.
Dicas para produção
- Use
maturin build --releasepara gerar wheels otimizadas para distribuição - Aproveite o paralelismo — Rust pode usar threads sem se preocupar com o GIL
- Publique no PyPI — o maturin gera wheels compatíveis com
pip install - Comece pequeno — identifique o gargalo com profiling antes de reescrever em Rust
Para gerenciar seus ambientes virtuais durante o desenvolvimento, o uv simplifica bastante o fluxo com PyO3.
Se você se interessa por linguagens compiladas de alta performance, vale conhecer também o ecossistema Rust no rustlang.com.br, que cobre o lado puro da linguagem. Desenvolvedores que trabalham com backends de alta concorrência podem comparar a abordagem de goroutines em golang.com.br com o modelo async do Rust. Já quem desenvolve para JVM pode explorar as extensões nativas via JNI em kotlin.dev.br.
Perguntas frequentes
O que é o PyO3?
PyO3 é um framework open source que permite escrever módulos nativos para Python usando a linguagem Rust. Ele fornece macros como #[pyfunction] e #[pyclass] que convertem automaticamente tipos entre Rust e Python.
Preciso saber Rust para usar PyO3?
Sim, conhecimento básico de Rust é necessário. Porém, para funções simples de processamento de dados, a curva de aprendizado é acessível. Muitos desenvolvedores aprendem Rust justamente para criar extensões Python mais rápidas.
O PyO3 substitui o Cython?
Para novos projetos, o PyO3 é geralmente uma escolha melhor que o Cython. Ele oferece segurança de memória garantida pelo compilador Rust, melhor tooling e performance comparável ou superior. Projetos existentes em Cython não precisam migrar imediatamente.
Como distribuir um pacote PyO3 no PyPI?
O maturin gera wheels pré-compiladas para diversas plataformas. Basta rodar maturin build --release e fazer upload com twine upload ou configurar CI/CD para publicação automática. O processo é similar a publicar qualquer outro pacote Python.
Equipe python.dev.br
Contribuidor do Python Brasil — Aprenda Python em Português