Rust para Programadores Python: Transição | Rust Brasil

Guia de transição de Python para Rust: comparações de sintaxe, conceitos e ferramentas lado a lado. Para devs Python.

Se você vem do mundo Python e quer aprender Rust, este guia foi feito para você. Vamos mapear os conceitos que você já conhece em Python para seus equivalentes em Rust, com exemplos práticos e comparações diretas. Ao final, você terá uma base sólida para começar a escrever código Rust com confiança.

Variáveis: Tipagem Dinâmica vs Tipagem Estática

Python usa tipagem dinâmica – você não precisa declarar tipos. Rust usa tipagem estática com inferência de tipos.

ConceitoPythonRust
Declaração de variávelx = 10let x = 10;
Variável mutávelx = 10; x = 20let mut x = 10; x = 20;
ConstantePI = 3.14 (convenção)const PI: f64 = 3.14;
Anotação de tipox: int = 10 (opcional)let x: i32 = 10;

Python:

nome = "Maria"
idade = 30
idade = 31        # reatribuição normal
nome = 42         # Python permite mudar o tipo

Rust:

let nome = "Maria";      // imutável por padrão
let mut idade = 30;       // mutável, permite reatribuição
idade = 31;               // ok, mesmo tipo
// idade = "trinta";      // ERRO: tipos incompatíveis

Em Rust, variáveis são imutáveis por padrão. Para permitir reatribuição, use mut. Além disso, Rust possui o conceito de shadowing, que permite redeclarar uma variável com o mesmo nome:

let x = 5;
let x = x + 1;    // shadowing: novo binding, pode mudar o tipo
let x = "texto";   // válido com shadowing

Strings: str vs String e &str

Python tem um único tipo str. Rust diferencia entre String (alocada no heap, mutável) e &str (referência a uma fatia de string, imutável).

OperaçãoPythonRust
Criar strings = "olá"let s = String::from("olá");
String literals = "olá"let s: &str = "olá";
Concatenars1 + s2format!("{}{}", s1, s2)
Tamanholen(s)s.len()
Fatiars[1:3]&s[1..3]
Maiúsculass.upper()s.to_uppercase()
Contém"ol" in ss.contains("ol")

Python:

nome = "Rust"
saudacao = f"Olá, {nome}! Bem-vindo ao Brasil."
partes = saudacao.split(" ")

Rust:

let nome = "Rust";
let saudacao = format!("Olá, {}! Bem-vindo ao Brasil.", nome);
let partes: Vec<&str> = saudacao.split(" ").collect();

Listas vs Vec<T>

O equivalente mais próximo da lista Python em Rust é o Vec<T>, mas com tipo homogêneo.

OperaçãoPythonRust
Criarnums = [1, 2, 3]let nums = vec![1, 2, 3];
Adicionarnums.append(4)nums.push(4);
Acessarnums[0]nums[0]
Tamanholen(nums)nums.len()
Fatiarnums[1:3]&nums[1..3]
Iterarfor n in nums:for n in &nums {
Remover últimonums.pop()nums.pop()

Python:

frutas = ["maçã", "banana", "laranja"]
frutas.append("uva")
for fruta in frutas:
    print(fruta)

Rust:

let mut frutas = vec!["maçã", "banana", "laranja"];
frutas.push("uva");
for fruta in &frutas {
    println!("{}", fruta);
}

Dicionários vs HashMap<K, V>

O dict do Python corresponde ao HashMap em Rust.

Python:

capitais = {
    "Brasil": "Brasília",
    "Argentina": "Buenos Aires",
    "Chile": "Santiago",
}
capitais["Uruguai"] = "Montevidéu"

for pais, capital in capitais.items():
    print(f"{pais}: {capital}")

capital = capitais.get("Peru", "Desconhecida")

Rust:

use std::collections::HashMap;

let mut capitais = HashMap::new();
capitais.insert("Brasil", "Brasília");
capitais.insert("Argentina", "Buenos Aires");
capitais.insert("Chile", "Santiago");
capitais.insert("Uruguai", "Montevidéu");

for (pais, capital) in &capitais {
    println!("{}: {}", pais, capital);
}

let capital = capitais.get("Peru").unwrap_or(&"Desconhecida");

None vs Option<T>

Python usa None para representar ausência de valor. Rust usa o enum Option<T>, que força o tratamento explícito.

Python:

def buscar_usuario(id: int) -> dict | None:
    if id == 1:
        return {"nome": "Ana"}
    return None

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

Rust:

fn buscar_usuario(id: u32) -> Option<String> {
    if id == 1 {
        Some(String::from("Ana"))
    } else {
        None
    }
}

// Tratamento com match
match buscar_usuario(1) {
    Some(nome) => println!("{}", nome),
    None => println!("Usuário não encontrado"),
}

// Ou com if let
if let Some(nome) = buscar_usuario(1) {
    println!("{}", nome);
}

Exceções vs Result<T, E>

Python usa exceções com try/except. Rust usa o tipo Result<T, E> e o operador ?.

Python:

def ler_arquivo(caminho: str) -> str:
    try:
        with open(caminho, "r") as f:
            return f.read()
    except FileNotFoundError:
        return "Arquivo não encontrado"
    except PermissionError:
        return "Sem permissão"

Rust:

use std::fs;
use std::io;

fn ler_arquivo(caminho: &str) -> Result<String, io::Error> {
    fs::read_to_string(caminho)
}

// Uso com match
match ler_arquivo("dados.txt") {
    Ok(conteudo) => println!("{}", conteudo),
    Err(e) => eprintln!("Erro: {}", e),
}

// Ou propagando o erro com ?
fn processar() -> Result<(), io::Error> {
    let conteudo = ler_arquivo("dados.txt")?;
    println!("{}", conteudo);
    Ok(())
}

O operador ? em Rust é equivalente a deixar a exceção propagar em Python – mas de forma explícita e verificada em tempo de compilação.

Classes vs Structs + impl + Traits

Python usa classes com herança. Rust usa structs, blocos impl e traits.

Python:

class Animal:
    def __init__(self, nome: str, peso: float):
        self.nome = nome
        self.peso = peso

    def falar(self) -> str:
        return f"{self.nome} faz um som"

    def __str__(self) -> str:
        return f"Animal({self.nome}, {self.peso}kg)"

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

Rust:

struct Animal {
    nome: String,
    peso: f64,
}

trait Falante {
    fn falar(&self) -> String;
}

impl Animal {
    fn new(nome: &str, peso: f64) -> Self {
        Animal {
            nome: nome.to_string(),
            peso,
        }
    }
}

impl std::fmt::Display for Animal {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "Animal({}, {}kg)", self.nome, self.peso)
    }
}

impl Falante for Animal {
    fn falar(&self) -> String {
        format!("{} faz um som", self.nome)
    }
}

struct Cachorro {
    animal: Animal,
}

impl Falante for Cachorro {
    fn falar(&self) -> String {
        format!("{} late: Au au!", self.animal.nome)
    }
}

Rust não tem herança. Em vez disso, usa composição e traits para polimorfismo. Traits definem comportamentos compartilhados, semelhante a interfaces ou protocolos.

pip vs Cargo

TarefaPythonRust
Gerenciador de pacotespipcargo
Arquivo de dependênciasrequirements.txt / pyproject.tomlCargo.toml
Instalar dependênciaspip install -r requirements.txtcargo build
Adicionar pacotepip install requestscargo add reqwest
Executar projetopython main.pycargo run
Executar testespytestcargo test
Criar novo projetocargo new meu_projeto
Repositório de pacotesPyPIcrates.io

virtualenv vs Cargo Workspaces

Python usa ambientes virtuais para isolar dependências. Em Rust, cada projeto tem seu próprio Cargo.toml e as dependências são gerenciadas por projeto automaticamente. Para monorepos, Rust oferece workspaces:

Python:

python -m venv .venv
source .venv/bin/activate
pip install flask requests

Rust (Cargo.toml de workspace):

[workspace]
members = [
    "api",
    "core",
    "utils",
]

Cada membro do workspace tem seu próprio Cargo.toml, mas compartilham um diretório target/ e um Cargo.lock.

List Comprehensions vs Iteradores

As list comprehensions do Python mapeiam diretamente para a API de iteradores do Rust.

Python:

# Filtrar e transformar
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pares_dobro = [n * 2 for n in numeros if n % 2 == 0]
# [4, 8, 12, 16, 20]

# Achatar listas
matriz = [[1, 2], [3, 4], [5, 6]]
plano = [x for linha in matriz for x in linha]
# [1, 2, 3, 4, 5, 6]

# Dicionário comprehension
quadrados = {n: n**2 for n in range(1, 6)}

Rust:

// Filtrar e transformar
let numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let pares_dobro: Vec<i32> = numeros.iter()
    .filter(|&n| n % 2 == 0)
    .map(|n| n * 2)
    .collect();
// [4, 8, 12, 16, 20]

// Achatar vetores
let matriz = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
let plano: Vec<i32> = matriz.into_iter()
    .flatten()
    .collect();
// [1, 2, 3, 4, 5, 6]

// HashMap a partir de iterador
use std::collections::HashMap;
let quadrados: HashMap<i32, i32> = (1..=5)
    .map(|n| (n, n.pow(2)))
    .collect();

Os iteradores de Rust são lazy (avaliação preguiçosa) e altamente otimizados – muitas vezes tão rápidos quanto loops escritos manualmente.

Decoradores vs Macros

Decoradores em Python e macros de atributo em Rust servem a propósitos semelhantes: modificar ou estender o comportamento de funções e structs.

Python:

import time

def medir_tempo(func):
    def wrapper(*args, **kwargs):
        inicio = time.time()
        resultado = func(*args, **kwargs)
        fim = time.time()
        print(f"{func.__name__} levou {fim - inicio:.2f}s")
        return resultado
    return wrapper

@medir_tempo
def processar_dados():
    time.sleep(1)
    return "pronto"

Rust:

// Macros deriváveis (derive macros) - as mais comuns
#[derive(Debug, Clone, PartialEq)]
struct Ponto {
    x: f64,
    y: f64,
}

// Macros de atributo com bibliotecas como serde
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Usuario {
    nome: String,
    email: String,
}

// Macro simples para medir tempo
macro_rules! medir_tempo {
    ($nome:expr, $bloco:block) => {{
        let inicio = std::time::Instant::now();
        let resultado = $bloco;
        let duracao = inicio.elapsed();
        println!("{} levou {:?}", $nome, duracao);
        resultado
    }};
}

// Uso
let resultado = medir_tempo!("processar_dados", {
    std::thread::sleep(std::time::Duration::from_secs(1));
    "pronto"
});

Exemplo Completo: API REST

Para finalizar, vamos comparar um servidor HTTP simples nas duas linguagens.

Python (Flask):

from flask import Flask, jsonify, request

app = Flask(__name__)
tarefas = []

@app.route("/tarefas", methods=["GET"])
def listar_tarefas():
    return jsonify(tarefas)

@app.route("/tarefas", methods=["POST"])
def criar_tarefa():
    tarefa = request.get_json()
    tarefas.append(tarefa)
    return jsonify(tarefa), 201

if __name__ == "__main__":
    app.run(port=8080)

Rust (Actix-web):

use actix_web::{web, App, HttpServer, HttpResponse};
use serde::{Deserialize, Serialize};
use std::sync::Mutex;

#[derive(Serialize, Deserialize, Clone)]
struct Tarefa {
    titulo: String,
    concluida: bool,
}

struct Estado {
    tarefas: Mutex<Vec<Tarefa>>,
}

async fn listar_tarefas(data: web::Data<Estado>) -> HttpResponse {
    let tarefas = data.tarefas.lock().unwrap();
    HttpResponse::Ok().json(tarefas.clone())
}

async fn criar_tarefa(
    data: web::Data<Estado>,
    tarefa: web::Json<Tarefa>,
) -> HttpResponse {
    let mut tarefas = data.tarefas.lock().unwrap();
    tarefas.push(tarefa.into_inner());
    HttpResponse::Created().finish()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let estado = web::Data::new(Estado {
        tarefas: Mutex::new(Vec::new()),
    });

    HttpServer::new(move || {
        App::new()
            .app_data(estado.clone())
            .route("/tarefas", web::get().to(listar_tarefas))
            .route("/tarefas", web::post().to(criar_tarefa))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Conclusão

A transição de Python para Rust exige uma mudança de mentalidade: do dinamismo e flexibilidade para segurança e desempenho em tempo de compilação. Os conceitos centrais se traduzem bem entre as duas linguagens, mas Rust exige que você seja explícito sobre propriedade de memória, mutabilidade e tratamento de erros.

Dicas para a transição:

  1. Comece pequeno – reescreva scripts simples em Rust antes de projetos grandes.
  2. Abrace o compilador – as mensagens de erro do Rust são excelentes e guiam você para o código correto.
  3. Pense em ownership – esse é o conceito mais novo para quem vem de Python. Dedique tempo para entender borrow checker.
  4. Use cargo clippy – é como um linter avançado que ensina Rust idiomático.
  5. Consulte a documentaçãocargo doc --open gera documentação local de todas as suas dependências.

Bem-vindo ao Rust! A curva de aprendizado é real, mas os ganhos em segurança e desempenho valem o esforço.