Rust — Artigo #38
Documentação e Doc Tests — Escrevendo Documentação que Não Fica Desatualizada
Por Prof. Dr. Marcelo Fontes | Série: Dominando Rust em 1 Ano
Documentação desatualizada é pior que documentação ausente. Um comentário que mente sobre o comportamento do código induz erros com mais eficiência que a ausência de qualquer orientação. Rust resolve esse problema com uma abordagem elegante: a documentação é código, e o código é testado.
Doc tests — exemplos de código dentro da documentação — são compilados e executados pelo cargo test. Se o comportamento da função mudar e o exemplo quebrar, o teste falha. A documentação não pode mentir sobre código que está sendo testado.
A ferramenta: rustdoc
rustdoc gera documentação HTML a partir de comentários especiais. Está integrado ao Cargo:
# Gera documentação do projeto atual
cargo doc
# Gera e abre no navegador
cargo doc --open
# Inclui documentação das dependências
cargo doc --document-private-items
# Executa apenas os doc tests
cargo test --doc
Comentários de documentação
// Comentário normal — não aparece na documentação
/// Comentário de documentação — aparece na documentação do ITEM SEGUINTE
/// Suporta Markdown completo.
//! Comentário de documentação do MÓDULO OU CRATE atual
//! Usado no topo de lib.rs ou mod.rs
/// # Seções Markdown
///
/// ## Exemplo
///
/// ```rust
/// let x = 42;
/// assert_eq!(x, 42);
/// ```
///
/// ## Panics
/// Descreve quando a função entra em pânico.
///
/// ## Errors
/// Descreve os erros que podem ser retornados.
///
/// ## Safety
/// Para funções `unsafe` — explica os invariantes.
fn exemplo() {}
Documentando uma biblioteca completa
//! # Calculadora de Intervalos
//!
//! Esta biblioteca fornece tipos para trabalhar com intervalos
//! numéricos de forma segura e expressiva.
//!
//! ## Uso Rápido
//!
//! ```rust
//! use calc_intervalos::Intervalo;
//!
//! let intervalo = Intervalo::novo(1.0, 10.0).unwrap();
//! assert!(intervalo.contem(5.0));
//! assert!(!intervalo.contem(15.0));
//! println!("Comprimento: {}", intervalo.comprimento());
//! ```
//!
//! ## Feature Flags
//!
//! - `serde` — Serialização/deserialização com Serde
/// Representa um intervalo fechado [início, fim].
///
/// Um intervalo fechado inclui seus dois extremos.
/// Garante em tempo de construção que `início <= fim`.
///
/// # Exemplos
///
/// ```rust
/// use calc_intervalos::Intervalo;
///
/// let i = Intervalo::novo(0.0, 1.0).unwrap();
/// assert_eq!(i.inicio(), 0.0);
/// assert_eq!(i.fim(), 1.0);
/// ```
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Intervalo {
inicio: f64,
fim: f64,
}
impl Intervalo {
/// Cria um novo intervalo [início, fim].
///
/// # Argumentos
///
/// * `inicio` — Extremo inferior do intervalo (inclusivo)
/// * `fim` — Extremo superior do intervalo (inclusivo)
///
/// # Retorna
///
/// `Some(Intervalo)` se `inicio <= fim`, `None` caso contrário.
///
/// # Exemplos
///
/// ```rust
/// use calc_intervalos::Intervalo;
///
/// // Intervalo válido
/// let i = Intervalo::novo(1.0, 5.0);
/// assert!(i.is_some());
///
/// // Intervalo degenerado (ponto único) — válido
/// let ponto = Intervalo::novo(3.0, 3.0);
/// assert!(ponto.is_some());
///
/// // Intervalo inválido
/// let invalido = Intervalo::novo(5.0, 1.0);
/// assert!(invalido.is_none());
/// ```
pub fn novo(inicio: f64, fim: f64) -> Option<Self> {
if inicio <= fim {
Some(Intervalo { inicio, fim })
} else {
None
}
}
/// Retorna o extremo inferior do intervalo.
///
/// # Exemplos
///
/// ```rust
/// # use calc_intervalos::Intervalo;
/// let i = Intervalo::novo(2.0, 8.0).unwrap();
/// assert_eq!(i.inicio(), 2.0);
/// ```
pub fn inicio(&self) -> f64 { self.inicio }
/// Retorna o extremo superior do intervalo.
///
/// # Exemplos
///
/// ```rust
/// # use calc_intervalos::Intervalo;
/// let i = Intervalo::novo(2.0, 8.0).unwrap();
/// assert_eq!(i.fim(), 8.0);
/// ```
pub fn fim(&self) -> f64 { self.fim }
/// Retorna o comprimento (medida) do intervalo.
///
/// O comprimento é definido como `fim - início`.
///
/// # Exemplos
///
/// ```rust
/// # use calc_intervalos::Intervalo;
/// let i = Intervalo::novo(2.0, 7.0).unwrap();
/// assert_eq!(i.comprimento(), 5.0);
///
/// // Intervalo degenerado tem comprimento zero
/// let ponto = Intervalo::novo(3.0, 3.0).unwrap();
/// assert_eq!(ponto.comprimento(), 0.0);
/// ```
pub fn comprimento(&self) -> f64 {
self.fim - self.inicio
}
/// Verifica se um valor está contido no intervalo.
///
/// Um valor `x` está contido se `inicio <= x <= fim`.
///
/// # Exemplos
///
/// ```rust
/// # use calc_intervalos::Intervalo;
/// let i = Intervalo::novo(1.0, 10.0).unwrap();
///
/// // Interior
/// assert!(i.contem(5.0));
///
/// // Extremos são incluídos (intervalo fechado)
/// assert!(i.contem(1.0));
/// assert!(i.contem(10.0));
///
/// // Exterior
/// assert!(!i.contem(0.0));
/// assert!(!i.contem(11.0));
/// ```
pub fn contem(&self, valor: f64) -> bool {
valor >= self.inicio && valor <= self.fim
}
/// Calcula a interseção com outro intervalo.
///
/// Retorna `Some(intervalo)` se os intervalos se sobrepõem,
/// `None` se são disjuntos.
///
/// # Exemplos
///
/// ```rust
/// # use calc_intervalos::Intervalo;
/// let a = Intervalo::novo(1.0, 5.0).unwrap();
/// let b = Intervalo::novo(3.0, 8.0).unwrap();
///
/// let intersecao = a.intersectar(&b).unwrap();
/// assert_eq!(intersecao.inicio(), 3.0);
/// assert_eq!(intersecao.fim(), 5.0);
///
/// // Sem sobreposição
/// let c = Intervalo::novo(6.0, 9.0).unwrap();
/// assert!(a.intersectar(&c).is_none());
/// ```
pub fn intersectar(&self, outro: &Intervalo) -> Option<Intervalo> {
let inicio = self.inicio.max(outro.inicio);
let fim = self.fim.min(outro.fim);
Intervalo::novo(inicio, fim)
}
/// Retorna o menor intervalo que contém ambos os intervalos.
///
/// Também conhecido como "hull" ou "union convexa".
///
/// # Exemplos
///
/// ```rust
/// # use calc_intervalos::Intervalo;
/// let a = Intervalo::novo(1.0, 3.0).unwrap();
/// let b = Intervalo::novo(5.0, 8.0).unwrap();
///
/// let envoltoria = a.envoltoria(&b);
/// assert_eq!(envoltoria.inicio(), 1.0);
/// assert_eq!(envoltoria.fim(), 8.0);
/// ```
pub fn envoltoria(&self, outro: &Intervalo) -> Intervalo {
Intervalo {
inicio: self.inicio.min(outro.inicio),
fim: self.fim.max(outro.fim),
}
}
/// Escala o intervalo por um fator em torno do seu centro.
///
/// # Panics
///
/// Entra em pânico se `fator` for negativo.
///
/// # Exemplos
///
/// ```rust
/// # use calc_intervalos::Intervalo;
/// let i = Intervalo::novo(0.0, 10.0).unwrap();
///
/// // Dobra o tamanho (mantém o centro em 5.0)
/// let dobrado = i.escalar(2.0);
/// assert_eq!(dobrado.inicio(), -5.0);
/// assert_eq!(dobrado.fim(), 15.0);
/// ```
///
/// ```rust,should_panic
/// # use calc_intervalos::Intervalo;
/// let i = Intervalo::novo(0.0, 10.0).unwrap();
/// i.escalar(-1.0); // Panic!
/// ```
pub fn escalar(&self, fator: f64) -> Intervalo {
assert!(fator >= 0.0, "Fator de escala não pode ser negativo");
let centro = (self.inicio + self.fim) / 2.0;
let metade = self.comprimento() / 2.0 * fator;
Intervalo {
inicio: centro - metade,
fim: centro + metade,
}
}
}
impl std::fmt::Display for Intervalo {
/// Formata o intervalo como `[início, fim]`.
///
/// # Exemplos
///
/// ```rust
/// # use calc_intervalos::Intervalo;
/// let i = Intervalo::novo(1.0, 5.0).unwrap();
/// assert_eq!(format!("{}", i), "[1, 5]");
/// ```
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "[{}, {}]", self.inicio, self.fim)
}
}
Anatomia de um doc test
/// Demonstra as diferentes formas de escrever doc tests.
///
/// ## Código oculto com `#`
///
/// Linhas prefixadas com `#` são compiladas mas não aparecem
/// na documentação renderizada — útil para setup:
///
/// ```rust
/// # use minha_crate::MinhaStruct; // oculto na doc, necessário para compilar
/// # let config = MinhaStruct::nova(); // oculto
/// let resultado = config.processar("dados"); // visível
/// assert_eq!(resultado, "DADOS"); // visível
/// ```
///
/// ## Testes que devem falhar em pânico
///
/// ```rust,should_panic
/// # use minha_crate::Intervalo;
/// let i = Intervalo::novo(0.0, 1.0).unwrap();
/// i.escalar(-2.0); // deve causar panic
/// ```
///
/// ## Código que não deve compilar
///
/// ```rust,compile_fail
/// let x: u32 = "não sou um número"; // erro de tipo
/// ```
///
/// ## Código sem execução (apenas exibição)
///
/// ```rust,no_run
/// // Este código compilaria mas não queremos executar em teste
/// // (ex: requer hardware, servidor externo, etc.)
/// std::thread::sleep(std::time::Duration::from_secs(3600));
/// ```
///
/// ## Outros idiomas (não testados)
///
/// ```bash
/// cargo run --release
/// ```
///
/// ```toml
/// [dependencies]
/// minha_crate = "1"
/// ```
///
/// ```text
/// Saída esperada:
/// resultado = 42
/// ```
pub fn documentar_exemplos() {}
Testando erros — documentando com ?
Doc tests em funções que retornam Result precisam de um wrapper:
/// Parseia uma data no formato `YYYY-MM-DD`.
///
/// # Erros
///
/// Retorna `Err` se o formato for inválido ou a data não existir.
///
/// # Exemplos
///
/// ```rust
/// # use minha_crate::parsear_data;
/// // O tipo de retorno fn() -> Result<_, _> permite usar ?
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let data = parsear_data("2024-03-15")?;
/// assert_eq!(data.ano, 2024);
/// assert_eq!(data.mes, 3);
/// assert_eq!(data.dia, 15);
/// # Ok(())
/// # }
/// ```
pub fn parsear_data(s: &str) -> Result<Data, ErroData> {
let partes: Vec<&str> = s.split('-').collect();
if partes.len() != 3 {
return Err(ErroData::FormatoInvalido);
}
Ok(Data {
ano: partes[0].parse().map_err(|_| ErroData::FormatoInvalido)?,
mes: partes[1].parse().map_err(|_| ErroData::FormatoInvalido)?,
dia: partes[2].parse().map_err(|_| ErroData::FormatoInvalido)?,
})
}
#[derive(Debug, PartialEq)]
pub struct Data { pub ano: u32, pub mes: u32, pub dia: u32 }
#[derive(Debug)]
pub enum ErroData { FormatoInvalido }
impl std::fmt::Display for ErroData {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Formato de data inválido")
}
}
impl std::error::Error for ErroData {}
Documentação de módulos e organização
//! # Módulo de Autenticação
//!
//! Fornece primitivas para autenticação de usuários.
//!
//! ## Fluxo típico
//!
//! ```rust,no_run
//! # use minha_crate::auth::{Autenticador, Credenciais};
//! let auth = Autenticador::novo("minha_chave_secreta");
//!
//! // Login
//! let token = auth.login(Credenciais {
//! email: "user@exemplo.com".to_string(),
//! senha: "senha_secreta".to_string(),
//! }).unwrap();
//!
//! // Verificação em rotas protegidas
//! let usuario = auth.verificar(&token).unwrap();
//! println!("Usuário: {}", usuario.nome);
//! ```
pub mod auth {
/// Gerencia autenticação e geração de tokens.
///
/// Veja o [guia de autenticação](crate::auth) para exemplos completos.
pub struct Autenticador {
chave: String,
}
impl Autenticador {
/// Cria um autenticador com a chave secreta fornecida.
///
/// # Segurança
///
/// A chave deve ter pelo menos 32 bytes de entropia.
/// Use `openssl rand -hex 32` para gerar uma chave adequada.
///
/// # Exemplos
///
/// ```rust
/// # use minha_crate::auth::Autenticador;
/// let auth = Autenticador::novo("chave_de_pelo_menos_32_caracteres_aqui");
/// ```
pub fn novo(chave: &str) -> Self {
Autenticador { chave: chave.to_string() }
}
}
}
Links em documentação
/// Processa um [`Pedido`] e retorna um [`Resultado`].
///
/// Veja também:
/// - [`Pedido::novo`] para criar pedidos
/// - [`Resultado::sucesso`] para interpretar o retorno
/// - [`crate::erros::ErroPedido`] para tratamento de erros
/// - [documentação do protocolo](https://protocolo.exemplo.com)
///
/// # Exemplos
///
/// Para uso avançado, veja [`ProcessadorAvancado`].
pub fn processar(pedido: Pedido) -> Resultado {
todo!()
}
/// Um pedido de processamento.
///
/// Relacionado: [`processar`]
pub struct Pedido { pub id: u64 }
impl Pedido {
/// Cria um novo pedido com o ID fornecido.
pub fn novo(id: u64) -> Self { Pedido { id } }
}
pub struct Resultado { pub sucesso: bool }
impl Resultado {
/// Retorna `true` se o processamento foi bem-sucedido.
pub fn sucesso(&self) -> bool { self.sucesso }
}
pub struct ProcessadorAvancado;
Atributos de documentação úteis
/// Item depreciado — use [`nova_funcao`] no lugar.
///
/// # Exemplos
///
/// ```rust,no_run
/// # use minha_crate::funcao_antiga;
/// funcao_antiga(); // gera aviso de depreciação
/// ```
#[deprecated(since = "2.0.0", note = "Use nova_funcao() no lugar")]
pub fn funcao_antiga() {}
/// Substituto moderno de [`funcao_antiga`].
pub fn nova_funcao() {}
// Oculta item da documentação pública mas mantém acessível
#[doc(hidden)]
pub fn detalhe_interno() {}
// Inclui conteúdo de arquivo markdown externo
#[doc = include_str!("../CHANGELOG.md")]
pub struct ChangelogDummy;
// Alias para uso em links
/// Veja [`TipoAliasado`](crate::TipoOriginal).
pub type TipoAliasado = crate::TipoOriginal;
pub struct TipoOriginal;
Um projeto completo: biblioteca bem documentada
Vamos construir uma pequena biblioteca de estatísticas com documentação exemplar:
//! # estatistica-rs
//!
//! Funções estatísticas simples para análise de dados numéricos.
//!
//! ## Exemplo Completo
//!
//! ```rust
//! use estatistica_rs::{Amostra, resumo};
//!
//! let dados = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
//! let amostra = Amostra::nova(dados).unwrap();
//!
//! println!("Média: {}", amostra.media());
//! println!("Mediana: {}", amostra.mediana());
//! println!("DP: {:.4}", amostra.desvio_padrao());
//!
//! // Saída:
//! // Média: 5
//! // Mediana: 4.5
//! // DP: 1.8708
//! ```
/// Erros possíveis ao trabalhar com amostras.
#[derive(Debug, PartialEq)]
pub enum ErroAmostra {
/// A amostra não contém nenhum dado.
Vazia,
/// O percentil solicitado está fora do intervalo `[0.0, 100.0]`.
PercentilInvalido(f64),
}
impl std::fmt::Display for ErroAmostra {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ErroAmostra::Vazia =>
write!(f, "Amostra vazia"),
ErroAmostra::PercentilInvalido(p) =>
write!(f, "Percentil inválido: {p} (deve estar em [0, 100])"),
}
}
}
impl std::error::Error for ErroAmostra {}
/// Uma amostra de dados numéricos.
///
/// Mantém os dados ordenados internamente para cálculos eficientes
/// de mediana e percentis.
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// let a = Amostra::nova(vec![3.0, 1.0, 4.0, 1.0, 5.0]).unwrap();
/// assert_eq!(a.n(), 5);
/// assert_eq!(a.media(), 2.8);
/// ```
#[derive(Debug, Clone)]
pub struct Amostra {
dados: Vec<f64>, // ordenados
soma: f64,
}
impl Amostra {
/// Cria uma nova amostra a partir de um vetor de valores.
///
/// Os dados são ordenados internamente.
///
/// # Erros
///
/// Retorna [`ErroAmostra::Vazia`] se o vetor estiver vazio.
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::{Amostra, ErroAmostra};
/// assert!(Amostra::nova(vec![1.0, 2.0, 3.0]).is_ok());
/// assert_eq!(Amostra::nova(vec![]).unwrap_err(), ErroAmostra::Vazia);
/// ```
pub fn nova(mut dados: Vec<f64>) -> Result<Self, ErroAmostra> {
if dados.is_empty() {
return Err(ErroAmostra::Vazia);
}
dados.sort_by(|a, b| a.partial_cmp(b).unwrap());
let soma = dados.iter().sum();
Ok(Amostra { dados, soma })
}
/// Retorna o número de observações.
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// let a = Amostra::nova(vec![1.0, 2.0, 3.0, 4.0]).unwrap();
/// assert_eq!(a.n(), 4);
/// ```
pub fn n(&self) -> usize { self.dados.len() }
/// Calcula a média aritmética.
///
/// `média = Σxᵢ / n`
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// let a = Amostra::nova(vec![1.0, 2.0, 3.0, 4.0, 5.0]).unwrap();
/// assert_eq!(a.media(), 3.0);
/// ```
pub fn media(&self) -> f64 {
self.soma / self.n() as f64
}
/// Calcula a mediana (valor central da distribuição).
///
/// Para `n` ímpar: elemento central.
/// Para `n` par: média dos dois elementos centrais.
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// // n ímpar
/// let a = Amostra::nova(vec![1.0, 3.0, 5.0]).unwrap();
/// assert_eq!(a.mediana(), 3.0);
///
/// // n par
/// let b = Amostra::nova(vec![1.0, 3.0, 5.0, 7.0]).unwrap();
/// assert_eq!(b.mediana(), 4.0);
/// ```
pub fn mediana(&self) -> f64 {
let n = self.n();
if n % 2 == 1 {
self.dados[n / 2]
} else {
(self.dados[n/2 - 1] + self.dados[n/2]) / 2.0
}
}
/// Calcula a variância populacional.
///
/// `σ² = Σ(xᵢ - μ)² / n`
///
/// Para variância amostral (divisão por n-1), use [`variancia_amostral`](Self::variancia_amostral).
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// let a = Amostra::nova(vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]).unwrap();
/// assert!((a.variancia() - 3.5).abs() < 1e-10);
/// ```
pub fn variancia(&self) -> f64 {
let media = self.media();
self.dados.iter()
.map(|&x| (x - media).powi(2))
.sum::<f64>() / self.n() as f64
}
/// Calcula a variância amostral (divisão por n-1).
///
/// Use quando os dados representam uma amostra de uma
/// população maior (correção de Bessel).
///
/// # Panics
///
/// Entra em pânico se `n == 1` (divisão por zero).
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// let a = Amostra::nova(vec![2.0, 4.0, 6.0]).unwrap();
/// assert!((a.variancia_amostral() - 4.0).abs() < 1e-10);
/// ```
pub fn variancia_amostral(&self) -> f64 {
assert!(self.n() > 1, "Variância amostral requer n > 1");
let media = self.media();
self.dados.iter()
.map(|&x| (x - media).powi(2))
.sum::<f64>() / (self.n() - 1) as f64
}
/// Calcula o desvio padrão populacional (raiz da variância).
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// let a = Amostra::nova(vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]).unwrap();
/// let dp = a.desvio_padrao();
/// assert!((dp - 1.8708286933869707).abs() < 1e-10);
/// ```
pub fn desvio_padrao(&self) -> f64 {
self.variancia().sqrt()
}
/// Calcula um percentil da amostra.
///
/// Usa interpolação linear (método recomendado pelo NIST).
///
/// # Argumentos
///
/// * `p` — Percentil em `[0.0, 100.0]`
///
/// # Erros
///
/// Retorna [`ErroAmostra::PercentilInvalido`] se `p < 0` ou `p > 100`.
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let a = Amostra::nova(vec![1.0, 2.0, 3.0, 4.0, 5.0]).unwrap();
///
/// // Mediana = percentil 50
/// assert_eq!(a.percentil(50.0)?, a.mediana());
///
/// // Mínimo e máximo
/// assert_eq!(a.percentil(0.0)?, 1.0);
/// assert_eq!(a.percentil(100.0)?, 5.0);
/// # Ok(())
/// # }
/// ```
pub fn percentil(&self, p: f64) -> Result<f64, ErroAmostra> {
if p < 0.0 || p > 100.0 {
return Err(ErroAmostra::PercentilInvalido(p));
}
let n = self.n();
if n == 1 { return Ok(self.dados[0]); }
let indice = p / 100.0 * (n - 1) as f64;
let i = indice.floor() as usize;
let fracao = indice - i as f64;
if i + 1 >= n {
Ok(self.dados[n - 1])
} else {
Ok(self.dados[i] * (1.0 - fracao) + self.dados[i + 1] * fracao)
}
}
/// Retorna o valor mínimo da amostra.
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// let a = Amostra::nova(vec![5.0, 2.0, 8.0, 1.0]).unwrap();
/// assert_eq!(a.minimo(), 1.0);
/// ```
pub fn minimo(&self) -> f64 { self.dados[0] }
/// Retorna o valor máximo da amostra.
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// let a = Amostra::nova(vec![5.0, 2.0, 8.0, 1.0]).unwrap();
/// assert_eq!(a.maximo(), 8.0);
/// ```
pub fn maximo(&self) -> f64 { *self.dados.last().unwrap() }
/// Retorna os cinco números do resumo estatístico.
///
/// O resumo consiste em: (mínimo, Q1, mediana, Q3, máximo).
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::Amostra;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let a = Amostra::nova(vec![1.0,2.0,3.0,4.0,5.0,6.0,7.0]).unwrap();
/// let (min, q1, med, q3, max) = a.cinco_numeros()?;
///
/// assert_eq!(min, 1.0);
/// assert_eq!(med, 4.0);
/// assert_eq!(max, 7.0);
/// assert!(q1 < med && med < q3);
/// # Ok(())
/// # }
/// ```
pub fn cinco_numeros(&self) -> Result<(f64,f64,f64,f64,f64), ErroAmostra> {
Ok((
self.minimo(),
self.percentil(25.0)?,
self.mediana(),
self.percentil(75.0)?,
self.maximo(),
))
}
}
/// Imprime um resumo estatístico formatado para uma amostra.
///
/// # Exemplos
///
/// ```rust
/// # use estatistica_rs::{Amostra, resumo};
/// let a = Amostra::nova(vec![1.0,2.0,3.0,4.0,5.0]).unwrap();
/// resumo(&a); // imprime tabela de resumo
/// ```
pub fn resumo(amostra: &Amostra) {
println!("── Resumo Estatístico ──────────────");
println!(" n : {}", amostra.n());
println!(" Média : {:.4}", amostra.media());
println!(" Mediana : {:.4}", amostra.mediana());
println!(" Desvio P : {:.4}", amostra.desvio_padrao());
println!(" Mínimo : {:.4}", amostra.minimo());
println!(" Máximo : {:.4}", amostra.maximo());
if let Ok((_, q1, _, q3, _)) = amostra.cinco_numeros() {
println!(" Q1 / Q3 : {:.4} / {:.4}", q1, q3);
println!(" IQR : {:.4}", q3 - q1);
}
println!("────────────────────────────────────");
}
fn main() {
let dados = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let amostra = Amostra::nova(dados).unwrap();
resumo(&amostra);
}
Execute os doc tests:
cargo test --doc
# running 14 tests
# test src/lib.rs - Amostra::cinco_numeros ... ok
# test src/lib.rs - Amostra::desvio_padrao ... ok
# test src/lib.rs - Amostra::media ... ok
# ...
# test result: ok. 14 passed; 0 failed
Boas práticas de documentação
Documente o "porquê", não apenas o "o quê". O nome da função já diz o que ela faz. A documentação deve explicar quando usá-la, quais são as alternativas, e quais são as armadilhas.
Todo item público merece documentação. Em crates publicados no crates.io, itens sem doc geram aviso do #![warn(missing_docs)]. Ative esse lint em bibliotecas públicas.
Exemplos devem ser auto-contidos e realistas. Use # para ocultar boilerplate, mas garanta que o exemplo compilado reflita uso real.
Documente os casos de erro com a mesma atenção que os casos felizes. A seção # Errors é frequentemente a mais valiosa para quem usa a biblioteca.
Use cargo doc --open durante o desenvolvimento. Ver a documentação renderizada revela problemas que não são visíveis no código-fonte.
Fontes e leituras recomendadas
- "rustdoc book" — guia oficial completo — https://doc.rust-lang.org/rustdoc/
- "Rust API Guidelines — Documentation" — convenções para documentação pública — https://rust-lang.github.io/api-guidelines/documentation.html
#![warn(missing_docs)]— lint que força documentação em itens públicos- "The Importance of Comments" — Jon Gjengset — filosofia de documentação — https://www.youtube.com/watch?v=8j-9wAV0tGw
- Exemplo excelente: documentação do
std::vec::Vec— https://doc.rust-lang.org/std/vec/struct.Vec.html
Artigo #38 de 52 | Série: Dominando Rust em 1 Ano Próximo → Artigo #39: Gerenciamento de Dependências e Publicação de Crates — Do projeto local ao crates.io