---
title: "Migrar de Python para Rust: Guia 2026 | Rust Brasil"
url: "https://rustlang.com.br/artigos/migracao-python-para-rust/"
markdown_url: "https://rustlang.com.br/artigos/migracao-python-para-rust.MD"
description: "Guia de migração de Python para Rust com PyO3: FFI, bindings, performance e estratégia incremental."
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# Migrar de Python para Rust: Guia 2026 | Rust Brasil

Guia de migração de Python para Rust com PyO3: FFI, bindings, performance e estratégia incremental.


## Introdução

Python é uma das linguagens mais populares do mundo, adorada por sua simplicidade e ecossistema rico. Porém, quando performance e uso de memória se tornam críticos — processamento de dados em larga escala, computação científica, serviços de baixa latência — os limites do Python ficam evidentes. Rust oferece performance de linguagem de sistemas com garantias de segurança de memória, tornando-a a escolha ideal para complementar ou substituir código Python em hot paths.

Este artigo é um guia prático para desenvolvedores Python que querem adotar Rust, seja migrando código existente ou criando extensões Python escritas em Rust. Vamos cobrir desde o mapeamento mental entre as duas linguagens até a integração direta via PyO3.

## O Problema: Limites de Performance do Python

### Python — Código Típico que Precisa de Performance

```python
# Python: Processamento de dados que demora minutos
import json
from typing import List, Dict

def processar_logs(arquivo: str) -> Dict[str, int]:
    """Conta ocorrências de cada status code em um arquivo de log JSON."""
    contagem: Dict[str, int] = {}

    with open(arquivo, 'r') as f:
        for linha in f:
            try:
                log = json.loads(linha)
                status = str(log.get('status', 'desconhecido'))
                contagem[status] = contagem.get(status, 0) + 1
            except json.JSONDecodeError:
                continue

    return contagem

# Com 10 milhões de linhas: ~45 segundos em Python
resultado = processar_logs("access.log")
print(resultado)
```

### Rust — Mesmo Algoritmo, Muito Mais Rápido

```rust
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use serde::Deserialize;

#[derive(Deserialize)]
struct LogEntry {
    #[serde(default = "status_padrao")]
    status: u16,
}

fn status_padrao() -> u16 {
    0
}

fn processar_logs(arquivo: &str) -> io::Result<HashMap<u16, u64>> {
    let file = File::open(arquivo)?;
    let reader = BufReader::with_capacity(64 * 1024, file);
    let mut contagem: HashMap<u16, u64> = HashMap::new();

    for linha in reader.lines() {
        let linha = linha?;
        if let Ok(log) = serde_json::from_str::<LogEntry>(&linha) {
            *contagem.entry(log.status).or_insert(0) += 1;
        }
    }

    Ok(contagem)
}

fn main() -> io::Result<()> {
    // Com 10 milhões de linhas: ~3 segundos em Rust
    let resultado = processar_logs("access.log")?;
    for (status, count) in &resultado {
        println!("{status}: {count}");
    }
    Ok(())
}
```

**Dependências do exemplo Rust:**

```toml
# Cargo.toml
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```

## Mapeamento de Conceitos: Python vs Rust

### Tipos Básicos

| Python | Rust | Notas |
|---|---|---|
| `int` | `i32`, `i64`, `u32`, `u64` | Rust tem tamanho fixo |
| `float` | `f32`, `f64` | |
| `str` | `String` ou `&str` | `String` é owned, `&str` é referência |
| `bool` | `bool` | |
| `None` | `Option::None` | |
| `list` | `Vec<T>` | |
| `dict` | `HashMap<K, V>` | |
| `tuple` | `(T1, T2, ...)` | |
| `set` | `HashSet<T>` | |
| `bytes` | `Vec<u8>` ou `&[u8]` | |

### Estruturas de Dados

**Python: Classes**

```python
from dataclasses import dataclass
from typing import Optional

@dataclass
class Usuario:
    id: int
    nome: str
    email: str
    idade: Optional[int] = None

    def saudacao(self) -> str:
        return f"Olá, {self.nome}!"

    def is_adulto(self) -> bool:
        if self.idade is None:
            return False
        return self.idade >= 18
```

**Rust: Structs com impl**

```rust
#[derive(Debug, Clone)]
pub struct Usuario {
    pub id: u64,
    pub nome: String,
    pub email: String,
    pub idade: Option<u8>,
}

impl Usuario {
    pub fn new(id: u64, nome: String, email: String) -> Self {
        Usuario { id, nome, email, idade: None }
    }

    pub fn saudacao(&self) -> String {
        format!("Olá, {}!", self.nome)
    }

    pub fn is_adulto(&self) -> bool {
        self.idade.map_or(false, |i| i >= 18)
    }
}
```

### Tratamento de Erros

**Python: try/except**

```python
def ler_numero(caminho: str) -> int:
    try:
        with open(caminho, 'r') as f:
            conteudo = f.read().strip()
        return int(conteudo)
    except FileNotFoundError:
        print(f"Arquivo {caminho} não encontrado")
        return 0
    except ValueError:
        print(f"Conteúdo de {caminho} não é um número")
        return 0
```

**Rust: Result e pattern matching**

```rust
use std::fs;
use std::num::ParseIntError;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum LeituraError {
    #[error("Arquivo não encontrado: {caminho}")]
    ArquivoNaoEncontrado {
        caminho: String,
        #[source]
        fonte: std::io::Error,
    },
    #[error("Conteúdo não é um número válido")]
    ParseError(#[from] ParseIntError),
}

fn ler_numero(caminho: &str) -> Result<i64, LeituraError> {
    let conteudo = fs::read_to_string(caminho)
        .map_err(|e| LeituraError::ArquivoNaoEncontrado {
            caminho: caminho.to_string(),
            fonte: e,
        })?;

    let numero: i64 = conteudo.trim().parse()?;
    Ok(numero)
}

fn main() {
    match ler_numero("numero.txt") {
        Ok(n) => println!("Número lido: {n}"),
        Err(e) => eprintln!("Erro: {e}"),
    }
}
```

```toml
# Cargo.toml
[dependencies]
thiserror = "2"
```

### Iteradores e Compreensões

**Python: List comprehension**

```python
# Python
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# List comprehension
pares_dobrados = [n * 2 for n in numeros if n % 2 == 0]
# [4, 8, 12, 16, 20]

# Generator expression
soma = sum(n * 2 for n in numeros if n % 2 == 0)
```

**Rust: Iterator chain**

```rust
fn main() {
    let numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // Equivalente à list comprehension
    let pares_dobrados: Vec<i32> = numeros
        .iter()
        .filter(|&&n| n % 2 == 0)
        .map(|&n| n * 2)
        .collect();
    // [4, 8, 12, 16, 20]

    // Equivalente ao generator expression com sum
    let soma: i32 = numeros
        .iter()
        .filter(|&&n| n % 2 == 0)
        .map(|&n| n * 2)
        .sum();

    println!("Pares dobrados: {:?}", pares_dobrados);
    println!("Soma: {soma}");
}
```

## Migração Gradual com PyO3 e Maturin

A estratégia mais eficiente é migrar gradualmente, reescrevendo apenas os hot paths em Rust e mantendo o restante em Python.

### Passo 1: Configurar o Projeto

```bash
# Instalar maturin
pip install maturin

# Criar projeto Rust como extensão Python
maturin init --bindings pyo3
```

```toml
# Cargo.toml
[package]
name = "minha_lib_rust"
version = "0.1.0"
edition = "2024"

[lib]
name = "minha_lib_rust"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.23", features = ["extension-module"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```

### Passo 2: Escrever a Função Rust

```rust
// src/lib.rs
use pyo3::prelude::*;
use std::collections::HashMap;

/// Processa um arquivo de log e retorna contagem de status codes.
/// Esta função é chamada a partir do Python.
#[pyfunction]
fn processar_logs(arquivo: &str) -> PyResult<HashMap<u16, u64>> {
    let conteudo = std::fs::read_to_string(arquivo)
        .map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))?;

    let mut contagem: HashMap<u16, u64> = HashMap::new();

    for linha in conteudo.lines() {
        if let Ok(valor) = serde_json::from_str::<serde_json::Value>(linha) {
            if let Some(status) = valor.get("status").and_then(|s| s.as_u64()) {
                *contagem.entry(status as u16).or_insert(0) += 1;
            }
        }
    }

    Ok(contagem)
}

/// Calcula a média de uma lista de números.
#[pyfunction]
fn media(numeros: Vec<f64>) -> PyResult<f64> {
    if numeros.is_empty() {
        return Err(pyo3::exceptions::PyValueError::new_err(
            "Lista não pode estar vazia",
        ));
    }
    Ok(numeros.iter().sum::<f64>() / numeros.len() as f64)
}

/// Classe Rust exposta ao Python.
#[pyclass]
struct Contador {
    valores: HashMap<String, u64>,
}

#[pymethods]
impl Contador {
    #[new]
    fn new() -> Self {
        Contador { valores: HashMap::new() }
    }

    fn incrementar(&mut self, chave: &str) {
        *self.valores.entry(chave.to_string()).or_insert(0) += 1;
    }

    fn obter(&self, chave: &str) -> u64 {
        *self.valores.get(chave).unwrap_or(&0)
    }

    fn top_n(&self, n: usize) -> Vec<(String, u64)> {
        let mut items: Vec<_> = self.valores.iter()
            .map(|(k, v)| (k.clone(), *v))
            .collect();
        items.sort_by(|a, b| b.1.cmp(&a.1));
        items.truncate(n);
        items
    }
}

/// Módulo Python
#[pymodule]
fn minha_lib_rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(processar_logs, m)?)?;
    m.add_function(wrap_pyfunction!(media, m)?)?;
    m.add_class::<Contador>()?;
    Ok(())
}
```

### Passo 3: Compilar e Usar no Python

```bash
# Compilar em modo desenvolvimento (rápido)
maturin develop --release

# Ou instalar como pacote
maturin build --release
pip install target/wheels/minha_lib_rust-*.whl
```

```python
# app.py — usando a extensão Rust no Python
import minha_lib_rust

# Funções Rust chamadas naturalmente do Python
resultado = minha_lib_rust.processar_logs("access.log")
print(f"Status codes: {resultado}")

media = minha_lib_rust.media([1.5, 2.5, 3.5, 4.5])
print(f"Média: {media}")

# Classe Rust usada como classe Python
contador = minha_lib_rust.Contador()
for palavra in ["rust", "python", "rust", "rust", "python", "go"]:
    contador.incrementar(palavra)

print(f"Rust: {contador.obter('rust')}")
print(f"Top 2: {contador.top_n(2)}")
```

### Passo 4: Benchmark para Validar Ganho

```python
# benchmark.py
import time
import json
import minha_lib_rust

def processar_python(arquivo: str) -> dict:
    contagem = {}
    with open(arquivo, 'r') as f:
        for linha in f:
            try:
                log = json.loads(linha)
                status = str(log.get('status', 0))
                contagem[status] = contagem.get(status, 0) + 1
            except json.JSONDecodeError:
                continue
    return contagem

# Benchmark Python
inicio = time.time()
resultado_py = processar_python("access.log")
tempo_py = time.time() - inicio
print(f"Python: {tempo_py:.3f}s")

# Benchmark Rust
inicio = time.time()
resultado_rs = minha_lib_rust.processar_logs("access.log")
tempo_rs = time.time() - inicio
print(f"Rust: {tempo_rs:.3f}s")

print(f"Speedup: {tempo_py / tempo_rs:.1f}x")
```

Resultado típico: **10-50x** mais rápido para tarefas CPU-bound.

## Armadilhas Comuns para Pythonistas

### 1. Ownership — O Conceito Mais Difícil

```python
# Python: tudo é referência, cópia é explícita
lista = [1, 2, 3]
outra = lista          # Ambas apontam para a mesma lista
outra.append(4)
print(lista)           # [1, 2, 3, 4] — modificou a original!
```

```rust
fn main() {
    let lista = vec![1, 2, 3];
    let outra = lista;       // MOVE — lista não é mais válida

    // println!("{:?}", lista);  // ERRO: valor foi movido

    println!("{:?}", outra);  // OK: outra é a dona agora

    // Para compartilhar, use referências:
    let lista = vec![1, 2, 3];
    let referencia = &lista;  // Empréstimo — lista continua válida
    println!("{:?}", lista);
    println!("{:?}", referencia);
}
```

### 2. Strings em Rust São Mais Complexas

```python
# Python: um tipo de string (str), simples e direto
nome = "Maria"
nome_upper = nome.upper()
print(f"Olá, {nome_upper}")
```

```rust
fn main() {
    // Rust tem DOIS tipos principais de string:
    let nome: &str = "Maria";           // &str: fatia de string, emprestada
    let nome_owned: String = "Maria".to_string(); // String: owned, no heap

    // Funções geralmente aceitam &str (mais flexível)
    fn saudacao(nome: &str) -> String {
        format!("Olá, {}", nome.to_uppercase())
    }

    println!("{}", saudacao(nome));          // &str direto
    println!("{}", saudacao(&nome_owned));   // &String → &str automaticamente
}
```

### 3. Não Existe `None` Direto — Use `Option`

```python
# Python
def buscar_usuario(id: int) -> dict | None:
    if id == 42:
        return {"nome": "Maria"}
    return None

usuario = buscar_usuario(42)
if usuario is not None:
    print(usuario["nome"])
```

```rust
use std::collections::HashMap;

fn buscar_usuario(id: u64) -> Option<HashMap<String, String>> {
    if id == 42 {
        let mut user = HashMap::new();
        user.insert("nome".to_string(), "Maria".to_string());
        Some(user)
    } else {
        None
    }
}

fn main() {
    // Pattern matching (idiomático)
    if let Some(usuario) = buscar_usuario(42) {
        println!("{}", usuario["nome"]);
    }

    // Ou com map/unwrap_or
    let nome = buscar_usuario(42)
        .and_then(|u| u.get("nome").cloned())
        .unwrap_or_else(|| "Desconhecido".to_string());
    println!("{nome}");
}
```

### 4. Sem Herança — Use Composição e Traits

```python
# Python: Herança
class Animal:
    def __init__(self, nome: str):
        self.nome = nome

    def falar(self) -> str:
        raise NotImplementedError

class Cachorro(Animal):
    def falar(self) -> str:
        return f"{self.nome} diz: Au au!"

class Gato(Animal):
    def falar(self) -> str:
        return f"{self.nome} diz: Miau!"
```

```rust
// Rust: Traits (sem herança)
trait Animal {
    fn nome(&self) -> &str;
    fn falar(&self) -> String;
}

struct Cachorro {
    nome: String,
}

impl Animal for Cachorro {
    fn nome(&self) -> &str {
        &self.nome
    }
    fn falar(&self) -> String {
        format!("{} diz: Au au!", self.nome)
    }
}

struct Gato {
    nome: String,
}

impl Animal for Gato {
    fn nome(&self) -> &str {
        &self.nome
    }
    fn falar(&self) -> String {
        format!("{} diz: Miau!", self.nome)
    }
}

fn apresentar(animal: &dyn Animal) {
    println!("{}", animal.falar());
}

fn main() {
    let rex = Cachorro { nome: "Rex".into() };
    let mimi = Gato { nome: "Mimi".into() };

    apresentar(&rex);
    apresentar(&mimi);
}
```

### 5. Async/Await: Conceito Similar, Execução Diferente

```python
# Python: asyncio
import asyncio
import aiohttp

async def buscar_url(url: str) -> str:
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()

async def main():
    resultado = await buscar_url("https://httpbin.org/get")
    print(resultado[:100])

asyncio.run(main())
```

```rust
// Rust: tokio + reqwest
use anyhow::Result;

async fn buscar_url(url: &str) -> Result<String> {
    let resp = reqwest::get(url).await?;
    let texto = resp.text().await?;
    Ok(texto)
}

#[tokio::main]
async fn main() -> Result<()> {
    let resultado = buscar_url("https://httpbin.org/get").await?;
    println!("{}", &resultado[..100.min(resultado.len())]);
    Ok(())
}
```

```toml
# Cargo.toml
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
anyhow = "1"
```

## Estratégia de Migração Recomendada

1. **Identifique hot paths** — Use profiling (cProfile, py-spy) para encontrar os gargalos
2. **Isole a interface** — Defina tipos claros de entrada/saída
3. **Reescreva em Rust com PyO3** — Mantenha a mesma API
4. **Benchmark** — Confirme o ganho de performance
5. **Itere** — Migre mais partes conforme necessário

| Fase | Python | Rust |
|---|---|---|
| **Fase 1** | Tudo | Nada |
| **Fase 2** | Orquestração, I/O | Hot paths (CPU-bound) |
| **Fase 3** | Scripts, glue code | Core do sistema |
| **Fase 4** | Opcional | Tudo |

## Checklist de Migração

1. **Profile primeiro** — Não adivinhe, meça
2. **PyO3 + maturin** — Integração Python-Rust sem dor
3. **Comece pelos hot paths** — Maior impacto, menor risco
4. **Mantenha a API Python** — Migração transparente
5. **Teste em ambos** — pytest e cargo test
6. **Benchmark comparativo** — Valide o ganho real
7. **Ownership é seu amigo** — Abrace, não lute contra
8. **Use `Result` e `Option`** — Nunca `unwrap()` em produção
9. **Iteradores, não loops** — Mais idiomático e performático
10. **Documente a interface** — PyO3 gera docstrings automaticamente

---

## Veja Também

- [Tutorial: Primeiros Passos com Rust](/tutoriais/primeiros-passos/) — Fundamentos da linguagem para iniciantes
- [Receita: Parse de JSON](/receitas/parse-json/) — Serde para processar JSON como em Python
- [Otimização de Performance](/artigos/otimizacao-performance/) — Extraia o máximo de performance
- [Boas Práticas de Error Handling](/artigos/boas-praticas-error-handling/) — Result e Option para Pythonistas
- [Padrões de Projeto em Rust](/artigos/padroes-projeto-rust/) — Alternativas a herança e classes
- [Migração de Node.js para Rust](/artigos/migracao-nodejs-para-rust/) — Guia similar para desenvolvedores Node.js
- [Python Brasil](https://python.dev.br) — Portal completo sobre Python em português, com tutoriais e carreira
