Documentação com rustdoc e doc tests

1. Introdução ao rustdoc e à documentação em Rust

Rust possui um sistema de documentação integrado e poderoso chamado rustdoc. Através do comando cargo doc, qualquer crate pode gerar documentação HTML automaticamente a partir de comentários especiais no código-fonte. Esse ecossistema promove a documentação como parte integrante do desenvolvimento, não como uma tarefa separada.

A sintaxe básica utiliza /// para documentar itens públicos (funções, structs, enums, traits) e //! para documentar o crate ou módulo como um todo. O conteúdo é escrito em Markdown, permitindo formatação rica.

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

//! Este crate fornece utilitários matemáticos básicos.

Para gerar a documentação localmente, execute cargo doc --open. O navegador abrirá uma página HTML completa com navegação, busca e links entre itens.

2. Escrevendo documentação rica e estruturada

Comentários de documentação suportam Markdown completo: cabeçalhos, listas, tabelas, blocos de código e links. Use [crate], [super], [crate::module::Item] para referências internas que se tornam links clicáveis na documentação gerada.

/// Uma struct que representa um ponto no espaço 2D.
///
/// # Campos
///
/// * `x` - Coordenada horizontal
/// * `y` - Coordenada vertical
///
/// # Métodos
///
/// Veja [`Ponto::distancia`] para calcular a distância até outro ponto.
///
/// # Exemplos
///
/// ```
/// use meu_crate::Ponto;
///
/// let p = Ponto { x: 3.0, y: 4.0 };
/// assert_eq!(p.x, 3.0);
/// ```
pub struct Ponto {
    pub x: f64,
    pub y: f64,
}

impl Ponto {
    /// Calcula a distância euclidiana entre dois pontos.
    pub fn distancia(&self, outro: &Ponto) -> f64 {
        let dx = self.x - outro.x;
        let dy = self.y - outro.y;
        (dx * dx + dy * dy).sqrt()
    }
}

Documente módulos com //! para descrever o propósito e a estrutura:

//! Módulo de operações geométricas.
//!
//! Contém structs e funções para trabalhar com formas geométricas
//! básicas como pontos, retas e círculos.
pub mod geometria {
    // ...
}

3. Doc tests: testes embutidos na documentação

Doc tests são blocos de código dentro da documentação que o Rust compila e executa como testes. Eles garantem que os exemplos na documentação estejam sempre corretos e funcionais.

/// Divide dois números.
///
/// # Exemplo
///
/// ```
/// let resultado = divide(10, 2);
/// assert_eq!(resultado, 5);
/// ```
///
/// # Panics
///
/// A função panic se o divisor for zero:
///
/// ```should_panic
/// divide(10, 0);
/// ```
pub fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Divisão por zero");
    }
    a / b
}

Para executar apenas os doc tests: cargo test --doc. O Rust compila cada bloco ``` como um crate separado e executa os asserts.

Doc tests também suportam o operador ? para funções que retornam Result:

/// Lê um arquivo e retorna seu conteúdo como string.
///
/// # Exemplo
///
/// ```rust,no_run
/// # use std::fs;
/// let conteudo = ler_arquivo("exemplo.txt")?;
/// # Ok::<(), std::io::Error>(())
/// ```
pub fn ler_arquivo(caminho: &str) -> Result<String, std::io::Error> {
    std::fs::read_to_string(caminho)
}

4. Boas práticas e dicas para doc tests

  • Exemplos compiláveis: Sempre escreva exemplos que compilem e executem sem erros. Use assert_eq! ou assert! para demonstrar resultados esperados.
  • Linhas ocultas com #: Para esconder código de setup que não é relevante para o entendimento do exemplo, prefixe com #:
/// ```
/// # let mut v = Vec::new();
/// v.push(1);
/// assert_eq!(v.len(), 1);
/// ```
  • Doc tests condicionais: Use #[cfg(feature = "alguma_feature")] dentro do doc test para exemplos que dependem de features:
/// ```
/// # #[cfg(feature = "serde")]
/// # {
/// use serde::{Serialize, Deserialize};
/// # }
/// ```
  • Evite ignore: Prefira no_run (compila mas não executa) ou compile_fail (espera-se erro de compilação) em vez de ignore, que desativa completamente o teste.

5. Personalizando a saída da documentação

Atributos especiais controlam como os itens aparecem na documentação:

/// Item público mas escondido da documentação.
#[doc(hidden)]
pub fn interna() {}

/// Permite busca por "soma" ou "adição".
#[doc(alias = "soma")]
#[doc(alias = "adição")]
pub fn somar(a: i32, b: i32) -> i32 { a + b }

/// Disponível apenas com a feature "avancado".
#[doc(cfg(feature = "avancado"))]
pub fn funcao_avancada() {}

No lib.rs, configure a aparência global:

#![doc(html_logo_url = "https://example.com/logo.png")]
#![doc(html_favicon_url = "https://example.com/favicon.ico")]
#![doc(html_root_url = "https://docs.rs/meu-crate")]

Para documentar que um bloco de código deve falhar ao compilar:

/// ```compile_fail
/// let x: i32 = "string"; // Erro intencional
/// ```

6. Documentação de crates e módulos

No lib.rs, use //! para descrever o crate inteiro:

//! # Meu Crate
//!
//! `meu_crate` é uma biblioteca para cálculos geométricos.
//!
//! ## Estrutura
//!
//! - [`geometria`] - Módulo com formas básicas
//! - [`algebra`] - Módulo com operações vetoriais
//!
//! ## Exemplo
//!
//! ```rust
//! use meu_crate::geometria::Ponto;
//! let p = Ponto::novo(1.0, 2.0);
//! ```

Para workspaces, cada crate interno deve ter sua própria documentação. Use crate::modulo::Item para referências cruzadas entre módulos do mesmo crate.

7. Ferramentas complementares e integração

  • cargo doc --no-deps: Gera documentação apenas do crate atual, ignorando dependências.
  • cargo doc --document-private-items: Inclui itens privados na documentação (útil para desenvolvimento interno).
  • cargo doc --open: Abre a documentação no navegador após gerar.
  • Linting de documentação: Para garantir que não haja warnings:
    bash RUSTDOCFLAGS="-D warnings" cargo doc
  • Publicação no docs.rs: Ao publicar no crates.io, a documentação é automaticamente gerada e hospedada em docs.rs/nome-do-crate.

8. Exemplo prático completo

Crie um crate simples com documentação rica:

//! # Calculadora
//!
//! Biblioteca para operações aritméticas básicas.
//!
//! ## Exemplo rápido
//!
//! ```rust
//! use calculadora::soma;
//! assert_eq!(soma(2, 2), 4);
//! ```

/// Soma dois números.
///
/// # Exemplo
///
/// ```
/// use calculadora::soma;
/// assert_eq!(soma(5, 3), 8);
/// ```
pub fn soma(a: i32, b: i32) -> i32 {
    a + b
}

/// Divide dois números.
///
/// Retorna `None` se o divisor for zero.
///
/// # Exemplo
///
/// ```
/// use calculadora::divide;
/// assert_eq!(divide(10, 2), Some(5));
/// assert_eq!(divide(10, 0), None);
/// ```
pub fn divide(a: i32, b: i32) -> Option<i32> {
    if b == 0 {
        None
    } else {
        Some(a / b)
    }
}

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

    #[test]
    fn test_soma() {
        assert_eq!(soma(1, 1), 2);
    }
}

Execute cargo test --doc para ver os doc tests passarem, e cargo doc --open para navegar pela documentação gerada. Note como os links funcionam e os exemplos são testados automaticamente.

Referências