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.

6 min de leitura Equipe python.dev.br

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çãoTempo (10M elementos)Speedup
Python puro (generator)~1.8s1x
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:

ProjetoO que fazPor que usa Rust
RuffLinter e formatador10-100x mais rápido que Flake8
PolarsDataFrame analyticsProcessamento paralelo sem GIL
Pydantic v2Validação de dadosCore de validação 5-50x mais rápido
uvGerenciador de pacotesResolução de dependências em segundos
tyType checkerAná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ísticaPyO3ctypescffi
LinguagemRustCC
Segurança de memóriaGarantida pelo compiladorManualManual
Conversão de tiposAutomáticaManualSemi-automática
Suporte a classesNativo (#[pyclass])LimitadoLimitado
Async/awaitSuportadoNãoNão
ErgonomiaAltaBaixaMé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

  1. Use maturin build --release para gerar wheels otimizadas para distribuição
  2. Aproveite o paralelismo — Rust pode usar threads sem se preocupar com o GIL
  3. Publique no PyPI — o maturin gera wheels compatíveis com pip install
  4. 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.

E

Equipe python.dev.br

Contribuidor do Python Brasil — Aprenda Python em Português