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.
| Conceito | Python | Rust |
|---|---|---|
| Declaração de variável | x = 10 | let x = 10; |
| Variável mutável | x = 10; x = 20 | let mut x = 10; x = 20; |
| Constante | PI = 3.14 (convenção) | const PI: f64 = 3.14; |
| Anotação de tipo | x: 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ção | Python | Rust |
|---|---|---|
| Criar string | s = "olá" | let s = String::from("olá"); |
| String literal | s = "olá" | let s: &str = "olá"; |
| Concatenar | s1 + s2 | format!("{}{}", s1, s2) |
| Tamanho | len(s) | s.len() |
| Fatiar | s[1:3] | &s[1..3] |
| Maiúsculas | s.upper() | s.to_uppercase() |
| Contém | "ol" in s | s.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ção | Python | Rust |
|---|---|---|
| Criar | nums = [1, 2, 3] | let nums = vec![1, 2, 3]; |
| Adicionar | nums.append(4) | nums.push(4); |
| Acessar | nums[0] | nums[0] |
| Tamanho | len(nums) | nums.len() |
| Fatiar | nums[1:3] | &nums[1..3] |
| Iterar | for n in nums: | for n in &nums { |
| Remover último | nums.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
| Tarefa | Python | Rust |
|---|---|---|
| Gerenciador de pacotes | pip | cargo |
| Arquivo de dependências | requirements.txt / pyproject.toml | Cargo.toml |
| Instalar dependências | pip install -r requirements.txt | cargo build |
| Adicionar pacote | pip install requests | cargo add reqwest |
| Executar projeto | python main.py | cargo run |
| Executar testes | pytest | cargo test |
| Criar novo projeto | – | cargo new meu_projeto |
| Repositório de pacotes | PyPI | crates.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:
- Comece pequeno – reescreva scripts simples em Rust antes de projetos grandes.
- Abrace o compilador – as mensagens de erro do Rust são excelentes e guiam você para o código correto.
- Pense em ownership – esse é o conceito mais novo para quem vem de Python. Dedique tempo para entender borrow checker.
- Use
cargo clippy– é como um linter avançado que ensina Rust idiomático. - Consulte a documentação –
cargo doc --opengera 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.