Portfólio no GitHub para Desenvolvedores Rust
Seu perfil no GitHub é, para muitos recrutadores e tech leads de Rust, mais importante que o próprio currículo. Em uma linguagem onde a comunidade open source é central e o código fala por si, um GitHub bem organizado pode abrir portas que nenhum diploma consegue. Este guia ensina como transformar seu perfil GitHub em uma vitrine profissional, desde a escolha dos projetos até a configuração de CI/CD e a otimização do seu profile README.
Por Que o GitHub é Tão Importante para Vagas Rust
A comunidade Rust nasceu no open source e valoriza profundamente a qualidade de código. Quando um recrutador ou tech lead avalia um candidato, o GitHub responde a perguntas que o currículo não consegue:
- Essa pessoa realmente sabe Rust? O código mostra a verdade
- Como ela lida com erros? Usa
unwrap()em tudo ou trata erros adequadamente? - Ela escreve testes? Tem suite de testes organizada?
- Como organiza o código? A estrutura de módulos faz sentido?
- Ela documenta? READMEs e doc comments estão presentes?
- Ela colabora? Tem PRs em outros projetos, responde issues?
Projetos que Impressionam Recrutadores
Critérios de um bom projeto de portfólio
Um projeto que vai impressionar precisa ter:
- Propósito claro: resolve um problema real ou demonstra uma habilidade específica
- Código limpo e idiomático: segue as convenções da comunidade Rust
- Testes: unitários, de integração e possivelmente benchmarks
- Documentação: README completo, doc comments, exemplos de uso
- CI/CD: builds e testes automatizados que passam
- Commits organizados: histórico legível com mensagens descritivas
Projetos recomendados por nível
Para Júnior (3-4 projetos)
| Projeto | O que demonstra | Complexidade |
|---|---|---|
| CLI tool (grep, wc, find) | I/O, args parsing, error handling | Baixa |
| API REST com banco de dados | Web, async, SQL, serialização | Média |
| Chat TCP/UDP | Networking, concorrência, protocolos | Média |
| Conversor/Parser de formatos | Parsing, serde, file I/O | Baixa-Média |
Para Pleno (2-3 projetos mais substanciais)
| Projeto | O que demonstra | Complexidade |
|---|---|---|
| Microserviço completo com Docker | Arquitetura, deployment, observability | Alta |
| Biblioteca publicada no crates.io | Design de API, documentação, versionamento | Alta |
| Sistema de cache/storage | Estruturas de dados, performance, benchmarks | Alta |
Para Sênior (1-2 projetos ambiciosos)
| Projeto | O que demonstra | Complexidade |
|---|---|---|
| Engine/Runtime/Compilador | Conhecimento profundo, design de sistemas | Muito alta |
| Contribuições significativas a projetos populares | Colaboração, código de produção | Variável |
Exemplo de projeto CLI impressionante
Veja como estruturar um projeto que demonstra competência:
meu-projeto-cli/
├── Cargo.toml
├── README.md
├── LICENSE
├── .github/
│ └── workflows/
│ └── ci.yml
├── src/
│ ├── main.rs
│ ├── lib.rs
│ ├── cli.rs
│ ├── config.rs
│ ├── error.rs
│ └── processor/
│ ├── mod.rs
│ ├── parser.rs
│ └── formatter.rs
├── tests/
│ ├── integration_test.rs
│ └── fixtures/
│ ├── input_valido.txt
│ └── input_invalido.txt
├── benches/
│ └── benchmark.rs
└── examples/
└── uso_basico.rs
Arquivo src/error.rs demonstrando tratamento de erros idiomático:
use std::fmt;
use std::io;
/// Erros possíveis durante o processamento de arquivos
#[derive(Debug)]
pub enum AppError {
/// Erro de leitura/escrita de arquivo
Io(io::Error),
/// Formato de entrada inválido
FormatoInvalido {
linha: usize,
detalhes: String,
},
/// Configuração ausente ou inválida
Config(String),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "Erro de I/O: {}", e),
AppError::FormatoInvalido { linha, detalhes } => {
write!(f, "Formato inválido na linha {}: {}", linha, detalhes)
}
AppError::Config(msg) => write!(f, "Erro de configuração: {}", msg),
}
}
}
impl std::error::Error for AppError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
AppError::Io(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for AppError {
fn from(e: io::Error) -> Self {
AppError::Io(e)
}
}
Arquivo src/lib.rs com documentação e testes:
//! # Meu Processador de Dados
//!
//! Biblioteca para processamento e transformação de arquivos
//! de dados estruturados.
//!
//! ## Exemplo de uso
//!
//! ```rust
//! use meu_processador::Processador;
//!
//! let proc = Processador::new();
//! let resultado = proc.processar("dados.csv").unwrap();
//! println!("Processados {} registros", resultado.total);
//! ```
pub mod config;
pub mod error;
pub mod processor;
pub use error::AppError;
/// Resultado do processamento contendo estatísticas
#[derive(Debug, Clone)]
pub struct Resultado {
/// Total de registros processados
pub total: usize,
/// Registros que passaram na validação
pub validos: usize,
/// Registros com erros
pub invalidos: usize,
/// Tempo de processamento em milissegundos
pub tempo_ms: u128,
}
/// Processador principal de dados
pub struct Processador {
config: config::Config,
}
impl Processador {
/// Cria um novo processador com configuração padrão
pub fn new() -> Self {
Self {
config: config::Config::default(),
}
}
/// Cria um processador com configuração customizada
pub fn with_config(config: config::Config) -> Self {
Self { config }
}
/// Processa um arquivo e retorna o resultado
///
/// # Errors
///
/// Retorna `AppError::Io` se o arquivo não puder ser lido
/// Retorna `AppError::FormatoInvalido` se o formato for inválido
pub fn processar(&self, caminho: &str) -> Result<Resultado, AppError> {
let inicio = std::time::Instant::now();
let conteudo = std::fs::read_to_string(caminho)?;
let mut validos = 0;
let mut invalidos = 0;
for (i, linha) in conteudo.lines().enumerate() {
if self.validar_linha(linha) {
validos += 1;
} else {
invalidos += 1;
if self.config.strict {
return Err(AppError::FormatoInvalido {
linha: i + 1,
detalhes: format!("Linha inválida: {}", linha),
});
}
}
}
Ok(Resultado {
total: validos + invalidos,
validos,
invalidos,
tempo_ms: inicio.elapsed().as_millis(),
})
}
fn validar_linha(&self, linha: &str) -> bool {
!linha.trim().is_empty() && linha.contains(self.config.delimitador)
}
}
impl Default for Processador {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
fn criar_arquivo_temp(conteudo: &str) -> NamedTempFile {
let mut arquivo = NamedTempFile::new().unwrap();
write!(arquivo, "{}", conteudo).unwrap();
arquivo
}
#[test]
fn test_processar_arquivo_valido() {
let arquivo = criar_arquivo_temp("campo1,campo2\nvalor1,valor2\n");
let proc = Processador::new();
let resultado = proc.processar(arquivo.path().to_str().unwrap()).unwrap();
assert_eq!(resultado.total, 2);
assert_eq!(resultado.validos, 2);
assert_eq!(resultado.invalidos, 0);
}
#[test]
fn test_processar_com_linhas_invalidas() {
let arquivo = criar_arquivo_temp("campo1,campo2\nlinha_sem_virgula\n");
let proc = Processador::new();
let resultado = proc.processar(arquivo.path().to_str().unwrap()).unwrap();
assert_eq!(resultado.total, 2);
assert_eq!(resultado.validos, 1);
assert_eq!(resultado.invalidos, 1);
}
#[test]
fn test_arquivo_inexistente() {
let proc = Processador::new();
let resultado = proc.processar("/caminho/inexistente.csv");
assert!(resultado.is_err());
assert!(matches!(resultado, Err(AppError::Io(_))));
}
}
Estrutura de README para Projetos Rust
O README é a primeira coisa que as pessoas veem. Siga esta estrutura:
Template de README
# Nome do Projeto
Breve descrição em uma frase do que o projeto faz.
[](https://github.com/usuario/projeto/actions)
[](https://crates.io/crates/nome-do-crate)
[](https://docs.rs/nome-do-crate)
[](https://opensource.org/licenses/MIT)
## Funcionalidades
- Funcionalidade 1: descrição breve
- Funcionalidade 2: descrição breve
- Funcionalidade 3: descrição breve
## Instalação
### Via cargo
cargo install nome-do-projeto
### Compilar do fonte
git clone https://github.com/usuario/projeto
cd projeto
cargo build --release
## Uso
### Exemplo básico
nome-do-projeto arquivo.txt --opcao valor
### Como biblioteca
use nome_do_projeto::Processador;
let proc = Processador::new();
let resultado = proc.processar("dados.csv")?;
## Arquitetura
Breve descrição da arquitetura do projeto.
## Contribuindo
Contribuições são bem-vindas! Veja CONTRIBUTING.md.
## Licença
MIT - veja LICENSE.
Dicas para um README eficaz
- Comece com o propósito: o que o projeto faz e por que ele existe
- Inclua badges: CI status, versão no crates.io, licença
- Mostre exemplos de uso: código funcional que pode ser copiado e rodado
- Documente a instalação: passos claros para começar a usar
- Inclua screenshots/GIFs: para ferramentas CLI, mostre o output
- Mantenha atualizado: README desatualizado é pior que README simples
CI/CD com GitHub Actions para Rust
Um projeto com CI configurado demonstra profissionalismo. Este é um workflow completo:
Workflow básico (recomendado para todos os projetos)
Crie o arquivo .github/workflows/ci.yml:
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo check --all-features
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --all-targets --all-features
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --all-features
docs:
name: Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo doc --no-deps --all-features
env:
RUSTDOCFLAGS: -Dwarnings
Workflow avançado (para projetos mais maduros)
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
rust: [stable, beta]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- uses: Swatinem/rust-cache@v2
- run: cargo test --all-features --verbose
msrv:
name: Minimum Supported Rust Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.75.0" # seu MSRV
- uses: Swatinem/rust-cache@v2
- run: cargo check --all-features
coverage:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install cargo-tarpaulin
run: cargo install cargo-tarpaulin
- name: Generate coverage
run: cargo tarpaulin --out xml
- name: Upload to codecov
uses: codecov/codecov-action@v3
with:
file: cobertura.xml
security:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
Workflow de release automatizado
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: x86_64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- run: cargo build --release --target ${{ matrix.target }}
- uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.target }}
path: target/${{ matrix.target }}/release/meu-projeto*
publish:
name: Publish to crates.io
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
Profile README: Sua Vitrine Pessoal
O Profile README aparece na página principal do seu GitHub. Crie um repositório com seu nome de usuário e adicione um README.md.
Exemplo de Profile README para dev Rust
# Olá! Sou [Seu Nome] 🦀
Desenvolvedor(a) Rust apaixonado(a) por sistemas de alta performance
e código seguro.
## Sobre mim
- Desenvolvedor(a) Rust com X anos de experiência
- Foco em backend, sistemas distribuídos e ferramentas CLI
- Contribuidor(a) open source ativo(a)
- Baseado(a) em [Cidade], Brasil
## Tecnologias
**Principal:** Rust
**Ecossistema Rust:** Tokio, Axum, Serde, SQLx, Clap, Tracing
**Também trabalho com:** PostgreSQL, Redis, Docker, AWS, GitHub Actions
## Projetos em destaque
| Projeto | Descrição | Status |
|---------|-----------|--------|
| [projeto-1](link) | Descrição curta | Em desenvolvimento |
| [projeto-2](link) | Descrição curta | Estável |
| [projeto-3](link) | Descrição curta | Publicado no crates.io |
## Contribuições Open Source
- [tokio](https://github.com/tokio-rs/tokio) - Melhoria de performance em X
- [serde](https://github.com/serde-rs/serde) - Documentação e exemplos
## Contato
- LinkedIn: [seu-linkedin](link)
- Blog: [seu-blog](link)
- Email: seu@email.com
Dicas para o Profile README
- Seja conciso: ninguém vai ler um README de 200 linhas no seu perfil
- Destaque Rust: deixe claro que é sua linguagem principal
- Link para projetos: facilite a navegação até seus melhores trabalhos
- Mantenha atualizado: perfil desatualizado passa má impressão
- Evite widgets excessivos: badges de streak e stats ficam poluídos se forem muitos
- Mostre personalidade: é um espaço profissional, mas pode ter seu toque pessoal
Organizando Seus Repositórios
Fixar repositórios estratégicos
O GitHub permite fixar até 6 repositórios no seu perfil. Escolha estrategicamente:
- Seu melhor projeto Rust completo (com testes, CI, README)
- Uma contribuição a projeto popular (fork com PR aceito)
- Uma biblioteca/crate publicada (se tiver)
- Um projeto que demonstre habilidade específica (async, CLI, web)
- Profile README (se estiver bem feito)
- Algo criativo ou divertido (demonstra paixão)
Limpeza de repositórios
- Archive projetos antigos ou abandonados que não representam seu nível atual
- Torne privados exercícios de curso ou código de baixa qualidade
- Delete forks que você nunca modificou
- Adicione descrições em todos os repositórios públicos
- Adicione tópicos/tags (rust, cli, web, async, etc.)
Gráfico de Contribuições e Atividade
O que recrutadores olham
O gráfico de contribuições (os quadradinhos verdes) não é tudo, mas conta uma história:
- Consistência: atividade regular é melhor que picos esporádicos
- Tipo de atividade: commits, PRs, issues e reviews contam
- Profundidade: contribuições significativas pesam mais que commits triviais
Estratégias para manter atividade consistente
- Comprometa-se com 30 minutos por dia: pequenos commits diários mantêm o gráfico verde
- Tenha um projeto ativo: sempre trabalhe em pelo menos um projeto pessoal
- Revise issues em projetos que usa: triagem de issues conta como contribuição
- Documente seu aprendizado: commits de documentação também contam
- Participe de code reviews: review de PRs de terceiros mostra colaboração
O que NÃO fazer
- Não faça commits vazios ou artificiais só para deixar o gráfico verde
- Não commite código ruim apressadamente
- Não force atividade quando não há o que contribuir genuinamente
- Não se estresse com o gráfico; qualidade importa mais
Escrevendo Doc Comments de Qualidade
Doc comments em Rust aparecem na documentação gerada pelo cargo doc e no docs.rs. Escrever boa documentação é um diferencial enorme.
/// Calcula a média ponderada de um conjunto de valores.
///
/// Cada valor é acompanhado de um peso. A média é calculada como
/// a soma dos produtos (valor * peso) dividida pela soma dos pesos.
///
/// # Arguments
///
/// * `valores` - Slice de tuplas (valor, peso)
///
/// # Returns
///
/// Retorna `Some(media)` se houver valores, ou `None` se o slice
/// estiver vazio ou a soma dos pesos for zero.
///
/// # Examples
///
/// ```
/// use minha_lib::media_ponderada;
///
/// let valores = vec![(8.0, 2.0), (9.0, 3.0), (7.0, 1.0)];
/// let media = media_ponderada(&valores).unwrap();
/// assert!((media - 8.33).abs() < 0.01);
/// ```
///
/// ```
/// use minha_lib::media_ponderada;
///
/// // Retorna None para entrada vazia
/// assert_eq!(media_ponderada(&[]), None);
/// ```
pub fn media_ponderada(valores: &[(f64, f64)]) -> Option<f64> {
if valores.is_empty() {
return None;
}
let soma_ponderada: f64 = valores.iter()
.map(|(v, p)| v * p)
.sum();
let soma_pesos: f64 = valores.iter()
.map(|(_, p)| *p)
.sum();
if soma_pesos == 0.0 {
None
} else {
Some(soma_ponderada / soma_pesos)
}
}
Publicando no crates.io
Ter uma crate publicada no crates.io é um excelente diferencial.
Checklist antes de publicar
# Cargo.toml completo para publicação
[package]
name = "minha-crate"
version = "0.1.0"
edition = "2021"
authors = ["Seu Nome <seu@email.com>"]
description = "Descrição curta e clara da crate"
license = "MIT"
repository = "https://github.com/usuario/minha-crate"
homepage = "https://github.com/usuario/minha-crate"
documentation = "https://docs.rs/minha-crate"
readme = "README.md"
keywords = ["rust", "keyword2", "keyword3"]
categories = ["command-line-utilities"]
rust-version = "1.75.0" # MSRV
[badges]
maintenance = { status = "actively-developed" }
Passos para publicar
# 1. Verificar se tudo está correto
cargo publish --dry-run
# 2. Rodar todos os testes
cargo test --all-features
# 3. Verificar a documentação
cargo doc --open
# 4. Publicar
cargo publish
Erros Comuns no Portfólio GitHub
- Repositórios sem README: um projeto sem documentação é quase invisível
- CI quebrado: badge vermelha de CI é pior que não ter badge
- Código com
unwrap()em toda parte: demonstra descuido com tratamento de erros - Sem testes: código sem testes levanta bandeiras vermelhas
- Commits tipo “fix”, “update”, “asdf”: histórico de commits ilegível
- Muitos forks sem modificação: polui o perfil sem agregar valor
- Projetos todos iguais: diversifique (CLI, web, lib, contribuição)
- Ignorar warnings do Clippy: ative
-Dwarningsno CI - Dependências desatualizadas: mostre que mantém o projeto saudável
- Não ter licença: sem licença, ninguém pode usar seu código legalmente
Conclusão
Seu portfólio no GitHub é a prova concreta das suas habilidades. Para construir um perfil que impressiona:
- Qualidade sobre quantidade: 3 projetos excelentes valem mais que 20 mediocres
- Código idiomático: siga as convenções da comunidade Rust
- Documentação completa: README, doc comments, exemplos
- CI/CD configurado: demonstre profissionalismo com automação
- Contribuições open source: mostre que sabe colaborar
- Profile README otimizado: crie uma boa primeira impressão
- Consistência: mantenha atividade regular e projetos atualizados
Invista tempo em polir seus melhores projetos. Cada repositório pinado é uma oportunidade de mostrar ao mundo o que você sabe fazer com Rust.