Comentários e documentação são fundamentais para a legibilidade e manutenção do código. Em Rust, existem diferentes tipos de comentários: os comentários de linha e bloco, que são ignorados pelo compilador, e os doc comments, que geram documentação automaticamente. Além disso, o Rust oferece a ferramenta cargo doc para gerar documentação HTML a partir dos doc comments, e suporte a exemplos testáveis que garantem que a documentação esteja sempre atualizada.

Nesta aula, exploraremos cada um desses tópicos em detalhes, com exemplos práticos e boas práticas.

Comentários de linha e bloco

Comentários são trechos de texto no código que são ignorados pelo compilador. Eles servem para explicar o propósito de partes do código, deixar notas ou desabilitar temporariamente blocos de código. Em Rust, há dois tipos principais: comentários de linha e comentários de bloco.

Os comentários de linha começam com // e se estendem até o final da linha. São usados para comentários curtos ou explicações rápidas. Já os comentários de bloco começam com /* e terminam com */, podendo abranger múltiplas linhas. São úteis para comentários mais longos ou para desabilitar trechos de código.

// Este é um comentário de linha

/*
 * Este é um comentário de bloco
 * que pode ter várias linhas
 */

fn main() {
    // Comentário explicando a próxima linha
    let x = 42; // Comentário no final da linha
    /*
     * Bloco de código desabilitado:
     * println!("Isso não será executado");
     */
    println!("x = {}", x);
}

É importante lembrar que comentários de bloco podem ser aninhados? Em Rust, eles não podem ser aninhados, ou seja, você não pode colocar um /* dentro de outro /* */. Por isso, para comentar grandes blocos de código que já contenham comentários de bloco, prefira usar // no início de cada linha ou usar a ferramenta cargo com #[cfg(comment)].

Doc comments (///)

Doc comments são um tipo especial de comentário que gera documentação para o código. Eles são escritos com três barras /// para documentar itens (funções, structs, módulos, etc.) e são processados pelo comando cargo doc para gerar páginas HTML. Além disso, suportam Markdown para formatação e podem incluir exemplos de código.

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

Os doc comments também podem ser usados com //! para documentar o item que os contém (como um módulo ou crate). Eles são colocados no início do arquivo ou dentro do módulo. Por exemplo:

//! # Meu Crate
//!
//! Esta é a documentação do crate.

É uma boa prática documentar todas as funções públicas, structs e módulos com doc comments, explicando o propósito, parâmetros, retorno e possíveis panics. O Rustacean (comunidade Rust) valoriza muito a documentação.

cargo doc

O comando cargo doc gera documentação HTML a partir dos doc comments do seu projeto. Ele usa o rustdoc internamente e produz uma página de documentação que pode ser visualizada no navegador. Para gerar e abrir a documentação, use:

cargo doc --open

Isso criará uma pasta target/doc com toda a documentação do seu crate e das dependências. A documentação inclui links para tipos, funções e módulos, além de exemplos testáveis. Você também pode gerar documentação apenas para o seu crate com cargo doc --no-deps.

A documentação gerada é estática e pode ser hospedada em serviços como docs.rs. O docs.rs é o principal repositório de documentação de crates Rust, e ele automaticamente gera e hospeda a documentação de cada versão de crate publicada no crates.io.

Algumas opções úteis do cargo doc:

  • --open: abre a documentação no navegador após gerar.
  • --no-deps: gera apenas a documentação do seu crate, ignorando dependências.
  • --document-private-items: inclui itens privados na documentação.

Exemplos testáveis

Os exemplos em doc comments podem ser testáveis, ou seja, o Rust pode compilar e executar o código dos exemplos para garantir que estejam corretos. Para criar um exemplo testável, basta escrever um bloco de código Markdown com três crases e a linguagem rust (ou omitir a linguagem, que o padrão é rust). O comando cargo test executará todos os exemplos de documentação (doctests) e reportará falhas.

/// Divide dois números.
///
/// # Exemplos
///
/// ```
/// let resultado = divide(10, 2);
/// assert_eq!(resultado, 5);
/// ```
///
/// ```rust,should_panic
/// // Este exemplo deve causar pânico
/// divide(1, 0);
/// ```
pub fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Divisão por zero");
    }
    a / b
}

Você pode usar atributos como should_panic, no_run, ignore e compile_fail para controlar o comportamento do teste. Por exemplo, ```rust,should_panic indica que o código deve causar pânico, e o teste passará se isso ocorrer. ```rust,no_run compila mas não executa, útil para exemplos que não podem ser executados (como código que depende de hardware).

Os exemplos testáveis são uma excelente forma de manter a documentação atualizada e garantir que os exemplos funcionem como esperado. Sempre que possível, inclua exemplos testáveis nas suas doc comments.

Boas práticas e observações finais

  • Use comentários de linha (//) para explicações breves e comentários de bloco (/* */) para desabilitar código ou comentários longos.
  • Documente todas as funções e tipos públicos com doc comments (///).
  • Inclua seções como # Exemplos, # Panics, # Errors e # Safety nos doc comments para clareza.
  • Execute cargo doc --open regularmente para visualizar a documentação gerada.
  • Use exemplos testáveis para garantir que a documentação esteja correta e funcional.

Referências

Exercícios

  1. Escreva uma função pública chamada saudacao que recebe um nome e retorna uma string de saudação. Adicione um doc comment completo com descrição e um exemplo testável.

    ✓ Resposta:
    /// Retorna uma saudação personalizada.
    ///
    /// # Exemplos
    ///
    /// ```
    /// let msg = saudacao("Mundo");
    /// assert_eq!(msg, "Olá, Mundo!");
    /// ```
    pub fn saudacao(nome: &str) -> String {
        format!("Olá, {}!", nome)
    }
  2. Adicione um comentário de linha e um comentário de bloco no código abaixo. O comentário de linha deve explicar a variável x, e o comentário de bloco deve desabilitar a linha println!("Isso não deve ser executado");.

    fn main() {
        let x = 10;
        println!("x = {}", x);
        println!("Isso não deve ser executado");
    }

    ✓ Resposta:
    fn main() {
        // x é uma variável inteira com valor 10
        let x = 10;
        println!("x = {}", x);
        /*
        println!("Isso não deve ser executado");
        */
    }
  3. Gere a documentação do seguinte código usando cargo doc. Explique o que o comando faz e qual arquivo é gerado.

    /// Retorna o quadrado de um número.
    pub fn quadrado(x: i32) -> i32 {
        x * x
    }

    ✓ Resposta:

    O comando cargo doc --open gera a documentação HTML a partir dos doc comments. A documentação é salva em target/doc/nome_do_crate/index.html e aberta no navegador. A página mostra a função quadrado com seu doc comment.

  4. Crie um exemplo testável que deve falhar (compile_fail) no doc comment de uma função que só aceita números positivos. A função deve usar um tipo que garanta isso (ex: u32).

    ✓ Resposta:
    /// Retorna o dobro de um número positivo.
    ///
    /// # Exemplos
    ///
    /// ```
    /// let resultado = dobro(5u32);
    /// assert_eq!(resultado, 10);
    /// ```
    ///
    /// ```rust,compile_fail
    /// // Isto não compila porque passamos um número negativo
    /// let resultado = dobro(-5u32);
    /// ```
    pub fn dobro(x: u32) -> u32 {
        x * 2
    }
  5. Escreva um doc comment para a função abaixo que inclua as seções # Panics e # Exemplos. A função deve entrar em pânico se o índice for maior que o tamanho do vetor.

    pub fn elemento(v: &[i32], indice: usize) -> i32 {
        if indice >= v.len() {
            panic!("Índice fora dos limites");
        }
        v[indice]
    }

    ✓ Resposta:
    /// Retorna o elemento de um vetor no índice especificado.
    ///
    /// # Panics
    ///
    /// A função entrará em pânico se o índice for maior ou igual ao comprimento do vetor.
    ///
    /// # Exemplos
    ///
    /// ```
    /// let v = vec![10, 20, 30];
    /// assert_eq!(elemento(&v, 1), 20);
    /// ```
    ///
    /// ```rust,should_panic
    /// let v = vec![10];
    /// elemento(&v, 5); // Isto causa pânico
    /// ```
    pub fn elemento(v: &[i32], indice: usize) -> i32 {
        if indice >= v.len() {
            panic!("Índice fora dos limites");
        }
        v[indice]
    }