Projetos práticos para aprender Rust
Quatro projetos técnicos e objetivos que ajudam você a consolidar conceitos de Rust, padrões de projeto, ergonomia do ecossistema e boas práticas de desenvolvimento.
Projeto 1 — CLI de Gerenciamento de Tarefas
Neste projeto eu foco em um CLI simples que permite adicionar, listar e marcar tarefas como concluídas. O objetivo é consolidar o fluxo de entrada/saída, persistência mínima e manejo de erros, usando clap para parsing de argumentos e serde para serialização em JSON.
- Stack: Rust estável, cargo, clap, serde/serde_json, serde_derive (ou derive com feature).
- Persistência: gravação de tarefas em um arquivo JSON local (tasks.json) para manter o estado entre execuções.
- Estrutura: separação entre modelo de dados, lógica de domínio e camada de I/O.
- Observabilidade: mensagens claras de erro e mensagens de confirmação simples para o usuário.
use clap::{Arg, Command};
use serde::{Deserialize, Serialize};
use std::fs::{self, OpenOptions};
use std::io::Write;
#[derive(Serialize, Deserialize, Debug)]
struct Tarefa {
id: u32,
titulo: String,
concluida: bool,
}
fn carregar_tarefas(path: &str) -> Vec<Tarefa> {
if let Ok(data) = fs::read_to_string(path) {
serde_json::from_str(&data).unwrap_or_else(|_| Vec::new())
} else {
Vec::new()
}
}
fn salvar_tarefas(path: &str, tarefas: &[Tarefa]) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(tarefas).unwrap();
let mut f = OpenOptions::new().write(true).create(true).truncate(true).open(path)?;
f.write_all(json.as_bytes())?;
Ok(())
}
fn main() {
let m = Command::new("taskr")
.version("0.1.0")
.about("Gerenciador de tarefas em CLI")
.arg(Arg::new("add")
.long("add")
.about("Adiciona uma tarefa")
.takes_value(false))
.arg(Arg::new("name")
.long("name")
.about("Nome da tarefa")
.takes_value(true))
.get_matches();
let path = "tasks.json";
let mut tarefas = carregar_tarefas(path);
if m.is_present("add") {
if let Some(nome) = m.value_of("name") {
let id = tarefas.iter().map(|t| t.id).max().unwrap_or(0) + 1;
let t = Tarefa { id, titulo: nome.to_string(), concluida: false };
tarefas.push(t);
salvar_tarefas(path, &tarefas).expect("falha ao salvar");
println!("Tarefa adicionada com sucesso.");
} else {
eprintln!("Forneça o nome da tarefa com --name .");
}
return;
}
// Listar tarefas como comportamento padrão
println!("Tarefas ({})", tarefas.len());
for t in &tarefas {
println!("- [{}] #{} {}", if t.concluida { "x" } else { " " }, t.id, t.titulo);
}
}
Observação: o objetivo aqui é que você implemente rapidamente a lógica de parseamento de argumentos, leitura/gravação simples e feedback ao usuário. Conforme evoluímos, você pode substituir a persistência por um banco leve (SQLite) ou adaptar para um formato de configuração mais complexo.
Projeto 2 — API HTTP simples com Actix Web
Este projeto apresenta uma API mínima para expor dados em formato JSON. O foco é entender o ciclo de requests, serialização/deserialização e configuração de rotas de forma clara e performática, usando Actix Web, uma das opções mais utilizadas no ecossistema Rust para serviços rápidos e estáveis.
- End points básicos: GET /health, GET /items e POST /items com payload JSON.
- Serialização com serde para entrada/saída de dados.
- Estrutura simples de servidor com estado imutável para leitura de dados simulados.
use actix_web::{get, post, web, App, HttpServer, Responder, HttpResponse};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Item { id: u32, name: String }
#[get("/health")]
async fn health() -> impl Responder {
HttpResponse::Ok().json(&{"status": "ok"})
}
#[get("/items")]
async fn list() -> impl Responder {
let items = vec![
Item { id: 1, name: "Item A".into() },
Item { id: 2, name: "Item B".into() }
];
HttpResponse::Ok().json(items)
}
#[post("/items")]
async fn add(item: web::Json- ) -> impl Responder {
HttpResponse::Ok().json(item.0)
}
#[tokio::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(health)
.service(list)
.service(add)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Para testar: rode o servidor e envie requisições HTTP com cURL ou um cliente de API de sua preferência. O objetivo é entender a diferença entre structs de domínio, mensagens em JSON e as rotas da aplicação.
Projeto 3 — Parser de CSV com streaming
Este projeto demonstra como ler arquivos CSV grandes sem carregá-los inteiramente na memória. Usamos o crate csv para leitura em streaming, combinando com BufRead para manter o uso de memória sob controle e processar registros linha a linha.
- Uso eficiente de memória com streaming e lazy parsing.
- Configuração básica do CSV: cabeçalhos, delimitação e uso de records().
- Exemplo de processamento simples para agregar ou transformar dados ao vivo.
use std::error::Error;
use std::fs::File;
use csv::ReaderBuilder;
fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("data.csv")?;
let mut rdr = ReaderBuilder::new()
.has_headers(true)
.from_reader(f);
for result in rdr.records() {
let record = result?;
// Processa cada linha conforme a estrutura do seu CSV
println!("{:?}", record);
}
Ok(())
}
Este padrão facilita a scalabilidade quando trabalhamos com datasets volumosos, mantendo o código simples e direto ao ponto.
Projeto 4 — Concorrência e async com Tokio
A última proposta foca em lidar com concorrência de forma eficiente em Rust, usando o runtime Tokio. Você aprende a estruturar tarefas assíncronas, sincronização básica e composição de futures, explorando cenários de I/O bound e CPU bound com utilitários simples.
- Conceito de tasks assíncronas e await points
- Uso de tokio::spawn para executar tarefas concorrentes
- Coleta de resultados com join ou FuturesUnordered
#[tokio::main]
async fn main() {
let tasks = vec![
tokio::spawn(async { 1 + 1 }),
tokio::spawn(async { 2 * 3 }),
];
for t in tasks {
let v = t.await.unwrap();
println!("resultado: {}", v);
}
}
O objetivo aqui é entender como estruturar código assíncrono de forma clara, mantendo o foco na composição de tarefas e na tratativa de erros assíncronos sem bloquear o thread principal.
Continue explorando Rust
Se você gostou deste conjunto de projetos, vale a pena mergulhar nos conteúdos adicionais que eu já produzi. Eles complementam o que você viu aqui, oferecendo fundamentos sólidos, padrões de projeto e práticas de alto desempenho.
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!