Introdução
Documentação em Rust não é um luxo, é uma funcionalidade de primeira classe da linguagem. O sistema de documentação do Rust, rustdoc, é integrado diretamente ao compilador e ao Cargo, permitindo que seus exemplos de código sejam compilados e testados automaticamente. Isso significa que sua documentação nunca fica desatualizada — se o código no exemplo não compila, o teste falha.
Este artigo cobre desde a sintaxe básica de documentação até recursos avançados como doc tests, publicação no docs.rs e criação de guias com mdBook. Se você mantém uma biblioteca Rust, dominar a documentação é essencial para a adoção pelo ecossistema.
O Problema: Código sem Documentação (ou com Documentação Falha)
Não Faça Isso: Funções Públicas sem Documentação
// ERRADO: API pública sem nenhuma documentação
pub fn calcular(dados: &[f64], modo: u8) -> Option<f64> {
match modo {
0 => Some(dados.iter().sum::<f64>() / dados.len() as f64),
1 => {
let mut sorted = dados.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
Some(sorted[sorted.len() / 2])
}
_ => None,
}
}
// Perguntas que ficam sem resposta:
// - O que é "modo"? Quais valores são válidos?
// - O que acontece se dados estiver vazio?
// - Quando retorna None?
Não Faça Isso: Documentação Desatualizada
/// Calcula a soma de todos os elementos.
///
/// # Exemplo
///
/// ```
/// let resultado = meu_crate::calcular(&[1.0, 2.0, 3.0]);
/// assert_eq!(resultado, 6.0);
/// ```
// A assinatura mudou mas a documentação não foi atualizada:
pub fn calcular(dados: &[f64], modo: u8) -> Option<f64> {
// ... implementação
# None
}
// O doc test vai FALHAR porque a assinatura não confere!
// Isso é uma VANTAGEM do Rust — a documentação quebrada é detectada.
A Solução: Documentação Idiomática com Doc Tests
/// vs //! — Tipos de Comentários de Documentação
//! # Minha Biblioteca de Estatística
//!
//! Esta crate fornece funções para cálculos estatísticos básicos.
//!
//! ## Exemplo Rápido
//!
//! ```
//! use minha_lib::media;
//!
//! let dados = vec![1.0, 2.0, 3.0, 4.0, 5.0];
//! assert_eq!(media(&dados), Some(3.0));
//! ```
//!
//! ## Módulos
//!
//! - [`basico`] — Média, mediana, moda
//! - [`avancado`] — Desvio padrão, correlação
/// Calcula a média aritmética de uma fatia de números.
///
/// Retorna `None` se a fatia estiver vazia, evitando divisão por zero.
///
/// # Argumentos
///
/// * `dados` — Fatia de valores `f64` para calcular a média
///
/// # Exemplos
///
/// ```
/// use minha_lib::media;
///
/// // Caso normal
/// assert_eq!(media(&[1.0, 2.0, 3.0]), Some(2.0));
///
/// // Fatia vazia retorna None
/// assert_eq!(media(&[]), None);
///
/// // Um único elemento
/// assert_eq!(media(&[42.0]), Some(42.0));
/// ```
///
/// # Performance
///
/// Complexidade O(n) onde n é o tamanho da fatia.
pub fn media(dados: &[f64]) -> Option<f64> {
if dados.is_empty() {
return None;
}
Some(dados.iter().sum::<f64>() / dados.len() as f64)
}
A diferença:
///documenta o item seguinte (função, struct, enum, etc.)//!documenta o item que contém (crate, módulo)
Doc Tests: Exemplos que Compilam
Todo bloco de código na documentação é um teste automático:
/// Divide dois números com verificação de divisão por zero.
///
/// # Exemplos
///
/// ```
/// use minha_lib::dividir;
///
/// assert_eq!(dividir(10.0, 2.0), Ok(5.0));
/// assert!(dividir(1.0, 0.0).is_err());
/// ```
///
/// # Errors
///
/// Retorna `Err` com mensagem descritiva se o divisor for zero.
///
/// ```
/// use minha_lib::dividir;
///
/// let erro = dividir(1.0, 0.0).unwrap_err();
/// assert_eq!(erro, "Divisão por zero");
/// ```
pub fn dividir(a: f64, b: f64) -> Result<f64, &'static str> {
if b == 0.0 {
return Err("Divisão por zero");
}
Ok(a / b)
}
Execute os doc tests com:
cargo test --doc
Seções Padrão de Documentação
A comunidade Rust usa seções convencionais. Siga este padrão:
/// Breve descrição em uma linha.
///
/// Descrição mais longa com detalhes sobre o comportamento,
/// pressupostos e uso esperado.
///
/// # Argumentos
///
/// * `param1` — Descrição do primeiro parâmetro
/// * `param2` — Descrição do segundo parâmetro
///
/// # Exemplos
///
/// ```
/// // Exemplo de uso básico
/// ```
///
/// # Errors
///
/// Descreva quando a função retorna `Err`.
///
/// # Panics
///
/// Descreva quando a função pode entrar em panic (se aplicável).
///
/// # Safety
///
/// Para funções `unsafe`, descreva as invariantes que o chamador deve garantir.
pub fn minha_funcao(param1: &str, param2: u32) -> Result<String, MyError> {
// ...
# Ok(String::new())
}
# #[derive(Debug)]
# struct MyError;
Documentando Structs e Enums
/// Representa uma conexão com um banco de dados PostgreSQL.
///
/// # Exemplos
///
/// ```no_run
/// use minha_lib::DatabaseConfig;
///
/// let config = DatabaseConfig::builder()
/// .host("localhost")
/// .porta(5432)
/// .banco("meu_app")
/// .build()
/// .expect("Configuração inválida");
/// ```
pub struct DatabaseConfig {
/// Hostname ou IP do servidor de banco de dados.
pub host: String,
/// Porta de conexão (padrão: 5432).
pub porta: u16,
/// Nome do banco de dados.
pub banco: String,
/// Número máximo de conexões simultâneas no pool.
///
/// O valor padrão é 10. Valores acima de 100 não são recomendados
/// para a maioria das aplicações.
pub max_conexoes: u32,
}
/// Tipos de erro que podem ocorrer durante operações no banco de dados.
///
/// # Exemplos
///
/// ```
/// use minha_lib::DatabaseError;
///
/// fn tratar_erro(err: DatabaseError) {
/// match err {
/// DatabaseError::Conexao(msg) => eprintln!("Falha na conexão: {msg}"),
/// DatabaseError::Query(msg) => eprintln!("Query falhou: {msg}"),
/// DatabaseError::Timeout => eprintln!("Operação expirou"),
/// }
/// }
/// ```
pub enum DatabaseError {
/// Falha ao estabelecer conexão com o banco de dados.
Conexao(String),
/// Erro durante a execução de uma query SQL.
Query(String),
/// A operação excedeu o tempo limite configurado.
Timeout,
}
Recursos Avançados de Doc Tests
/// Função que demonstra recursos avançados de doc tests.
///
/// Exemplo que deve compilar mas não executar (útil para código de rede):
///
/// ```no_run
/// use std::net::TcpListener;
/// let listener = TcpListener::bind("0.0.0.0:8080").unwrap();
/// ```
///
/// Exemplo que deve falhar na compilação (para demonstrar o que NÃO fazer):
///
/// ```compile_fail
/// let x: i32 = "não é um número";
/// ```
///
/// Exemplo que deve entrar em panic:
///
/// ```should_panic
/// # fn exemplo() {
/// panic!("Este panic é esperado");
/// # }
/// # exemplo();
/// ```
///
/// Código oculto na documentação (setup que não é relevante):
///
/// ```
/// # fn setup() -> Vec<i32> { vec![1, 2, 3] }
/// # let dados = setup();
/// // O leitor vê apenas isso:
/// let soma: i32 = dados.iter().sum();
/// assert_eq!(soma, 6);
/// ```
pub fn funcao_exemplo() {}
Atributos de Documentação
// Negar warnings de documentação ausente (recomendado para bibliotecas)
#![deny(missing_docs)]
// Habilitar features de documentação
#![doc = include_str!("../README.md")]
/// Função com link para outra documentação.
///
/// Veja também [`outra_funcao`] e o módulo [`utils`].
///
/// Para detalhes sobre o formato, consulte [`std::fmt`].
pub fn minha_funcao() {}
/// Outra função documentada.
pub fn outra_funcao() {}
/// Módulo de utilitários.
pub mod utils {
//! Funções utilitárias diversas.
/// Formata um número como moeda brasileira.
///
/// ```
/// # // Em um doc test real, seria: use minha_lib::utils::formatar_brl;
/// fn formatar_brl(valor: f64) -> String {
/// format!("R$ {:.2}", valor)
/// }
/// assert_eq!(formatar_brl(1500.5), "R$ 1500.50");
/// ```
pub fn formatar_brl(valor: f64) -> String {
format!("R$ {:.2}", valor)
}
}
Guia Passo a Passo: Documentação Profissional
Passo 1: Gerar Documentação Localmente
# Gerar e abrir no navegador
cargo doc --open
# Incluir dependências privadas
cargo doc --document-private-items
# Verificar links quebrados
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
Passo 2: Configurar Cargo.toml para docs.rs
# Cargo.toml
[package]
name = "minha-lib"
version = "1.0.0"
description = "Biblioteca de estatística para Rust"
documentation = "https://docs.rs/minha-lib"
repository = "https://github.com/usuario/minha-lib"
readme = "README.md"
license = "MIT"
keywords = ["estatística", "matemática", "cálculo"]
categories = ["mathematics", "science"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
Passo 3: Badges no README
<!-- No README.md -->
[](https://crates.io/crates/minha-lib)
[](https://docs.rs/minha-lib)
[](https://github.com/usuario/minha-lib/actions)
[](https://opensource.org/licenses/MIT)
Passo 4: mdBook para Guias Longos
Para documentação que vai além do rustdoc (tutoriais, guias, arquitetura):
cargo install mdbook
# Criar estrutura
mdbook init docs
# Editar docs/src/SUMMARY.md
<!-- docs/src/SUMMARY.md -->
# Summary
- [Introdução](./introducao.md)
- [Início Rápido](./inicio-rapido.md)
- [Guia do Usuário](./guia/README.md)
- [Configuração](./guia/configuracao.md)
- [Uso Básico](./guia/uso-basico.md)
- [Recursos Avançados](./guia/avancado.md)
- [Referência da API](./api.md)
- [FAQ](./faq.md)
# Construir e servir
mdbook serve docs --open
Passo 5: Integrar Doc Tests no CI
# .github/workflows/ci.yml
- name: Doc tests
run: cargo test --doc
- name: Verificar documentação
run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
Armadilhas Comuns
1. Esquecer # use nos Doc Tests
/// ```
/// // ERRADO: Não vai compilar porque falta o `use`
/// let resultado = minha_funcao(42);
/// ```
///
/// ```
/// // CORRETO: Inclua o import (pode ser oculto com #)
/// # use minha_lib::minha_funcao;
/// let resultado = minha_funcao(42);
/// # assert_eq!(resultado, 84);
/// ```
pub fn minha_funcao(x: i32) -> i32 {
x * 2
}
2. Links Internos Quebrados
/// Veja [`outra_funcao`] para mais detalhes.
///
/// // Se `outra_funcao` não existir, rustdoc dará warning.
/// // Com RUSTDOCFLAGS="-D warnings", vira erro.
pub fn minha_funcao() {}
3. Exemplos Que Não Demonstram o Uso Real
/// # Exemplos
///
/// ```
/// // ERRADO: Exemplo trivial que não ajuda
/// use minha_lib::ordenar;
/// let mut v = vec![1, 2, 3];
/// ordenar(&mut v);
/// ```
///
/// ```
/// // CORRETO: Mostra um caso de uso real
/// use minha_lib::ordenar;
///
/// let mut notas = vec![7.5, 9.0, 6.8, 8.3, 5.5];
/// ordenar(&mut notas);
/// assert_eq!(notas, vec![5.5, 6.8, 7.5, 8.3, 9.0]);
/// ```
pub fn ordenar(dados: &mut Vec<f64>) {
dados.sort_by(|a, b| a.partial_cmp(b).unwrap());
}
4. Documentação Apenas em Inglês
Se o público-alvo inclui falantes de português, considere documentação bilíngue:
/// Calcula o fatorial de um número.
///
/// Calculates the factorial of a number.
///
/// # Exemplos / Examples
///
/// ```
/// # fn fatorial(n: u64) -> u64 { if n <= 1 { 1 } else { n * fatorial(n - 1) } }
/// assert_eq!(fatorial(5), 120);
/// assert_eq!(fatorial(0), 1);
/// ```
pub fn fatorial(n: u64) -> u64 {
if n <= 1 { 1 } else { n * fatorial(n - 1) }
}
Exemplo do Mundo Real: Biblioteca com Documentação Completa
//! # minha_lib — Utilitários de Texto para Português Brasileiro
//!
//! Esta biblioteca fornece funções para manipulação de texto com suporte
//! a particularidades do português brasileiro.
//!
//! ## Início Rápido
//!
//! ```
//! use minha_lib::TextoBR;
//!
//! let texto = TextoBR::new("São Paulo é a maior cidade do Brasil.");
//! assert_eq!(texto.contar_palavras(), 8);
//! assert!(texto.contem_acentos());
//! ```
//!
//! ## Features
//!
//! - Contagem de palavras com suporte a hifens e apóstrofos
//! - Detecção de acentos e caracteres especiais do português
//! - Normalização de texto (remoção de acentos)
#![deny(missing_docs)]
/// Wrapper para operações em texto brasileiro.
///
/// Fornece métodos otimizados para análise de texto em português.
///
/// # Exemplos
///
/// ```
/// use minha_lib::TextoBR;
///
/// let texto = TextoBR::new("Olá, mundo!");
/// assert_eq!(texto.contar_palavras(), 2);
/// assert!(texto.contem_acentos());
/// assert_eq!(texto.como_str(), "Olá, mundo!");
/// ```
pub struct TextoBR {
conteudo: String,
}
impl TextoBR {
/// Cria uma nova instância a partir de uma string.
///
/// # Argumentos
///
/// * `texto` — O texto em português para analisar
///
/// # Exemplos
///
/// ```
/// use minha_lib::TextoBR;
///
/// let t = TextoBR::new("Bom dia!");
/// assert_eq!(t.como_str(), "Bom dia!");
/// ```
pub fn new(texto: &str) -> Self {
TextoBR {
conteudo: texto.to_string(),
}
}
/// Retorna a referência ao conteúdo interno.
pub fn como_str(&self) -> &str {
&self.conteudo
}
/// Conta o número de palavras no texto.
///
/// Palavras são delimitadas por espaços em branco. Pontuação
/// adjacente não é contada como parte da palavra.
///
/// # Exemplos
///
/// ```
/// use minha_lib::TextoBR;
///
/// assert_eq!(TextoBR::new("Uma frase simples").contar_palavras(), 3);
/// assert_eq!(TextoBR::new("").contar_palavras(), 0);
/// assert_eq!(TextoBR::new(" ").contar_palavras(), 0);
/// ```
pub fn contar_palavras(&self) -> usize {
self.conteudo.split_whitespace().count()
}
/// Verifica se o texto contém caracteres acentuados do português.
///
/// Detecta: á, à, â, ã, é, ê, í, ó, ô, õ, ú, ü, ç (maiúsculas e minúsculas).
///
/// # Exemplos
///
/// ```
/// use minha_lib::TextoBR;
///
/// assert!(TextoBR::new("café").contem_acentos());
/// assert!(TextoBR::new("ação").contem_acentos());
/// assert!(!TextoBR::new("hello world").contem_acentos());
/// ```
pub fn contem_acentos(&self) -> bool {
self.conteudo.chars().any(|c| {
matches!(
c,
'á' | 'à' | 'â' | 'ã' | 'é' | 'ê' | 'í' | 'ó' | 'ô' | 'õ' | 'ú' | 'ü' | 'ç'
| 'Á' | 'À' | 'Â' | 'Ã' | 'É' | 'Ê' | 'Í' | 'Ó' | 'Ô' | 'Õ' | 'Ú' | 'Ü' | 'Ç'
)
})
}
}
Checklist de Documentação
#![deny(missing_docs)]— Exija documentação em itens públicos- Doc tests em toda função pública — Exemplos que compilam
- Seções padronizadas — Arguments, Examples, Errors, Panics
cargo test --docno CI — Nunca deixe doc tests quebrarem- Links internos
[item]— Navegação entre itens da API RUSTDOCFLAGS="-D warnings"— Links quebrados viram erros#[doc = include_str!("../README.md")]— README como documentação do crate- docs.rs metadata — Configure features e flags para docs.rs
- Badges — Crates.io, docs.rs, CI status
- mdBook para guias — Documentação longa fora do rustdoc
Veja Também
- Instalação: Cargo — Como usar o Cargo para gerar documentação
- CI/CD para Projetos Rust — Automatize verificação de documentação
- Boas Práticas de Error Handling — Documente erros corretamente
- Gerenciamento de Dependências — Cargo.toml e metadados de publicação
- Padrões de Projeto em Rust — Documente padrões com exemplos