Primeiros Passos com Rust: Hello World, Cargo e Estrutura de Projetos

Crie seu primeiro programa em Rust, aprenda a usar o Cargo para gerenciar projetos e entenda a estrutura de um projeto Rust.

Agora que você tem o Rust instalado, é hora de escrever seu primeiro programa! Neste tutorial, vamos explorar o Cargo (o gerenciador de projetos do Rust), criar nosso primeiro “Hello, World!” e entender como os projetos Rust são organizados.

Seu Primeiro Programa: Hello, World!

Vamos começar da forma mais simples possível. Crie um arquivo chamado main.rs em qualquer pasta:

fn main() {
    println!("Olá, mundo!");
}

Compile e execute diretamente com o rustc:

rustc main.rs
./main

Saída:

Olá, mundo!

Vamos entender cada parte:

  • fn main() — Define a função principal do programa. Todo programa Rust começa pela função main.
  • println! — É uma macro (note o ! no final) que imprime texto no terminal com uma quebra de linha. Macros em Rust são indicadas pelo ! e são diferentes de funções normais.
  • "Olá, mundo!" — Uma string literal entre aspas duplas.

Embora compilar com rustc funcione, para projetos reais você sempre vai usar o Cargo.

Conhecendo o Cargo

O Cargo é muito mais do que um compilador. Ele é o canivete suíço do desenvolvedor Rust:

  • Gerenciador de projetos — Cria e organiza a estrutura do projeto
  • Build system — Compila seu código e suas dependências
  • Gerenciador de dependências — Baixa e gerencia bibliotecas (chamadas de crates)
  • Executor de testes — Roda testes unitários e de integração
  • Gerador de documentação — Gera documentação a partir de comentários no código

Criando um Projeto com Cargo

Para criar um novo projeto, use cargo new:

cargo new meu-projeto
cd meu-projeto

O Cargo cria a seguinte estrutura:

meu-projeto/
├── Cargo.toml
├── .gitignore
└── src/
    └── main.rs

Vamos entender cada arquivo.

Cargo.toml

O Cargo.toml é o arquivo de configuração do projeto. Ele usa o formato TOML (Tom’s Obvious, Minimal Language):

[package]
name = "meu-projeto"
version = "0.1.0"
edition = "2021"

[dependencies]
  • [package] — Informações sobre o seu projeto
    • name — Nome do projeto (usado pelo Cargo e pelo crates.io)
    • version — Versão seguindo Semantic Versioning
    • edition — Edição do Rust (2015, 2018, 2021 ou 2024). Use sempre a mais recente
  • [dependencies] — Lista de dependências externas (vazia por enquanto)

src/main.rs

O arquivo principal do projeto já vem com o “Hello, World!”:

fn main() {
    println!("Hello, world!");
}

Vamos personalizá-lo:

fn main() {
    let nome = "Rustáceo";
    let linguagem = "Rust";
    println!("Olá, {}! Bem-vindo ao {}!", nome, linguagem);
    println!("Versão do programa: {}", env!("CARGO_PKG_VERSION"));
}

A macro println! aceita placeholders {} que são substituídos pelos valores na ordem em que aparecem. A macro env! acessa variáveis de ambiente em tempo de compilação — neste caso, a versão definida no Cargo.toml.

Comandos Essenciais do Cargo

cargo build

Compila o projeto sem executá-lo:

cargo build

O executável é gerado em target/debug/meu-projeto. A compilação no modo debug inclui informações de depuração e não aplica otimizações, o que torna a compilação mais rápida.

Para compilar em modo release (com otimizações):

cargo build --release

O executável otimizado fica em target/release/meu-projeto. Use o modo release para distribuição e benchmarks.

cargo run

Compila e executa o projeto em um só comando:

cargo run

Saída:

   Compiling meu-projeto v0.1.0 (/home/usuario/meu-projeto)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s
     Running `target/debug/meu-projeto`
Olá, Rustáceo! Bem-vindo ao Rust!
Versão do programa: 0.1.0

Se o código não foi alterado desde a última compilação, o Cargo não recompila — ele simplesmente executa o binário existente. Isso torna o cargo run muito eficiente no dia a dia.

cargo check

Verifica se o código compila sem gerar o executável:

cargo check

Este comando é muito mais rápido que cargo build porque pula a etapa de geração de código. Use-o frequentemente durante o desenvolvimento para verificar erros rapidamente.

cargo test

Executa os testes do projeto:

cargo test

Vamos criar um teste simples. Modifique o src/main.rs:

fn somar(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let resultado = somar(3, 4);
    println!("3 + 4 = {}", resultado);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn teste_somar() {
        assert_eq!(somar(2, 3), 5);
        assert_eq!(somar(-1, 1), 0);
        assert_eq!(somar(0, 0), 0);
    }

    #[test]
    fn teste_somar_negativos() {
        assert_eq!(somar(-5, -3), -8);
    }
}

Execute cargo test:

   Compiling meu-projeto v0.1.0 (/home/usuario/meu-projeto)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
     Running unittests src/main.rs (target/debug/deps/meu-projeto-abc123)

running 2 tests
test tests::teste_somar ... ok
test tests::teste_somar_negativos ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Vamos entender a estrutura de testes:

  • #[cfg(test)] — Este módulo só é compilado quando rodamos testes
  • mod tests — Um módulo para agrupar os testes
  • use super::* — Importa tudo do módulo pai (onde somar está definida)
  • #[test] — Marca uma função como teste
  • assert_eq! — Macro que verifica se dois valores são iguais

cargo fmt

Formata o código seguindo o estilo oficial do Rust:

cargo fmt

Isso garante que todo o código do projeto siga um estilo consistente. Não há discussão sobre tabs vs. espaços ou onde colocar as chaves — o rustfmt decide por você!

cargo clippy

Roda o linter Clippy, que dá sugestões inteligentes para melhorar seu código:

cargo clippy

O Clippy detecta padrões comuns que podem ser simplificados, potenciais bugs e código idiomático. É altamente recomendado rodar o Clippy regularmente.

Adicionando Dependências

Uma das maiores forças do ecossistema Rust é o crates.io, o repositório oficial de bibliotecas. Vamos adicionar uma dependência como exemplo.

Adicione a crate colored ao Cargo.toml:

[package]
name = "meu-projeto"
version = "0.1.0"
edition = "2021"

[dependencies]
colored = "2"

Ou use o comando cargo add (se você instalou o cargo-edit):

cargo add colored

Agora use no seu código:

use colored::*;

fn main() {
    println!("{}", "Olá, Rust Brasil!".green().bold());
    println!("{}", "Este texto é vermelho!".red());
    println!("{}", "E este é azul com fundo amarelo!".blue().on_yellow());
}

Na próxima vez que executar cargo run, o Cargo vai baixar e compilar a dependência automaticamente.

Projetos Binários vs. Bibliotecas

O Cargo suporta dois tipos de projetos:

Projeto binário (executável)

cargo new meu-app          # cria um projeto binário (padrão)

Contém src/main.rs com a função main().

Projeto de biblioteca

cargo new minha-lib --lib   # cria um projeto de biblioteca

Contém src/lib.rs em vez de src/main.rs:

/// Soma dois números inteiros.
///
/// # Exemplos
///
/// ```
/// let resultado = minha_lib::somar(2, 3);
/// assert_eq!(resultado, 5);
/// ```
pub fn somar(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn funciona() {
        assert_eq!(somar(2, 2), 4);
    }
}

Note o comentário com /// — isso é um doc comment. Você pode gerar documentação HTML com:

cargo doc --open

Estrutura de um Projeto Real

Conforme seu projeto cresce, a estrutura se expande:

meu-projeto/
├── Cargo.toml
├── Cargo.lock              # Versões exatas das dependências (gerado automaticamente)
├── .gitignore
├── src/
│   ├── main.rs             # Ponto de entrada do binário
│   ├── lib.rs              # Código da biblioteca (opcional)
│   └── modulo/
│       ├── mod.rs          # Declaração do módulo
│       └── funcoes.rs      # Funções do módulo
├── tests/
│   └── integration_test.rs # Testes de integração
├── benches/
│   └── benchmark.rs        # Benchmarks
└── examples/
    └── exemplo.rs          # Exemplos executáveis
  • Cargo.lock — Gerado automaticamente pelo Cargo. Registra as versões exatas de todas as dependências. Inclua no Git para projetos binários.
  • tests/ — Testes de integração que testam seu código como um usuário externo faria.
  • examples/ — Exemplos que podem ser executados com cargo run --example exemplo.
  • benches/ — Benchmarks de performance.

Exercício Prático

Crie um projeto que funciona como uma calculadora simples:

fn somar(a: f64, b: f64) -> f64 {
    a + b
}

fn subtrair(a: f64, b: f64) -> f64 {
    a - b
}

fn multiplicar(a: f64, b: f64) -> f64 {
    a * b
}

fn dividir(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

fn main() {
    let a = 10.0;
    let b = 3.0;

    println!("Calculadora Rust");
    println!("================");
    println!("{} + {} = {}", a, b, somar(a, b));
    println!("{} - {} = {}", a, b, subtrair(a, b));
    println!("{} * {} = {}", a, b, multiplicar(a, b));

    match dividir(a, b) {
        Some(resultado) => println!("{} / {} = {:.2}", a, b, resultado),
        None => println!("Erro: divisão por zero!"),
    }

    // Testando divisão por zero
    match dividir(a, 0.0) {
        Some(resultado) => println!("{} / 0 = {:.2}", a, resultado),
        None => println!("Erro: divisão por zero!"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn teste_somar() {
        assert_eq!(somar(2.0, 3.0), 5.0);
    }

    #[test]
    fn teste_dividir() {
        assert_eq!(dividir(10.0, 2.0), Some(5.0));
    }

    #[test]
    fn teste_dividir_por_zero() {
        assert_eq!(dividir(10.0, 0.0), None);
    }
}

Execute com cargo run e teste com cargo test para ver tudo funcionando.

Próximos Passos

Agora que você sabe criar e gerenciar projetos com o Cargo, é hora de aprender os fundamentos da linguagem. No próximo tutorial, vamos explorar variáveis, tipos primitivos e funções em Rust.

Acesse o tutorial Variáveis, Tipos e Funções para continuar.