O que são Display e Debug?
Em Rust, os traits fmt::Display e fmt::Debug controlam como um tipo é convertido em texto. Eles são fundamentais para qualquer programa, pois determinam o que aparece quando você usa println!, format!, write! e outros macros de formatação.
Display({}) produz saída voltada para o usuário final — limpa, legível e amigável.Debug({:?}) produz saída voltada para o desenvolvedor — detalhada, com nomes de campos e estrutura interna.
Praticamente todo tipo que você cria em Rust deveria implementar pelo menos Debug. Já Display é implementado quando o tipo tem uma representação textual natural que faz sentido para o usuário.
Definição dos Traits
fmt::Debug
// Definido em std::fmt
pub trait Debug {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
}
fmt::Display
// Definido em std::fmt
pub trait Display {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
}
Ambos têm exatamente a mesma assinatura — a diferença está no propósito e no marcador de formatação que os invoca.
Como Implementar: derive vs impl manual
Debug com #[derive]
A forma mais comum de implementar Debug é usando a macro derive. O compilador gera automaticamente uma implementação que mostra o nome do tipo e todos os campos:
#[derive(Debug)]
struct Produto {
nome: String,
preco: f64,
em_estoque: bool,
}
fn main() {
let p = Produto {
nome: String::from("Teclado Mecânico"),
preco: 299.90,
em_estoque: true,
};
// Saída: Produto { nome: "Teclado Mecânico", preco: 299.9, em_estoque: true }
println!("{:?}", p);
// Formatação alternativa (pretty-print):
// Produto {
// nome: "Teclado Mecânico",
// preco: 299.9,
// em_estoque: true,
// }
println!("{:#?}", p);
}
Para usar #[derive(Debug)], todos os campos do tipo também devem implementar Debug. Todos os tipos da biblioteca padrão já o fazem.
Debug com implementação manual
Quando você quer controlar exatamente o que aparece na saída de depuração:
use std::fmt;
struct SenhaProtegida {
usuario: String,
senha: String,
}
impl fmt::Debug for SenhaProtegida {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SenhaProtegida")
.field("usuario", &self.usuario)
.field("senha", &"****") // Nunca exibir a senha real
.finish()
}
}
fn main() {
let cred = SenhaProtegida {
usuario: String::from("admin"),
senha: String::from("s3cr3t0"),
};
// Saída: SenhaProtegida { usuario: "admin", senha: "****" }
println!("{:?}", cred);
}
Display com implementação manual
Display não pode ser derivado automaticamente — você sempre precisa implementá-lo manualmente. Isso faz sentido, pois a representação amigável depende do contexto do seu domínio:
use std::fmt;
struct Moeda {
valor: f64,
simbolo: &'static str,
}
impl fmt::Display for Moeda {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {:.2}", self.simbolo, self.valor)
}
}
fn main() {
let preco = Moeda { valor: 49.9, simbolo: "R$" };
// Saída: R$ 49.90
println!("{}", preco);
// Display também é usado por .to_string()
let texto: String = preco.to_string();
assert_eq!(texto, "R$ 49.90");
}
Implementar Display automaticamente fornece .to_string() graças a uma implementação blanket na biblioteca padrão: impl<T: Display> ToString for T.
Exemplos Práticos
Exemplo 1: Enum com Display e Debug
use std::fmt;
#[derive(Debug)]
enum StatusPedido {
Pendente,
Processando,
Enviado { codigo_rastreio: String },
Entregue,
Cancelado(String), // motivo
}
impl fmt::Display for StatusPedido {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StatusPedido::Pendente => write!(f, "Pendente"),
StatusPedido::Processando => write!(f, "Em processamento"),
StatusPedido::Enviado { codigo_rastreio } => {
write!(f, "Enviado (rastreio: {})", codigo_rastreio)
}
StatusPedido::Entregue => write!(f, "Entregue"),
StatusPedido::Cancelado(motivo) => {
write!(f, "Cancelado: {}", motivo)
}
}
}
}
fn main() {
let status = StatusPedido::Enviado {
codigo_rastreio: String::from("BR123456789"),
};
// Display: Enviado (rastreio: BR123456789)
println!("Status: {}", status);
// Debug: Enviado { codigo_rastreio: "BR123456789" }
println!("Debug: {:?}", status);
}
Exemplo 2: Usando f.write_str e o Formatter
use std::fmt;
struct Coordenada {
lat: f64,
lon: f64,
}
impl fmt::Display for Coordenada {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// f.write_str é mais eficiente que write! para strings fixas
f.write_str("(")?;
write!(f, "{:.4}, {:.4}", self.lat, self.lon)?;
f.write_str(")")
}
}
impl fmt::Debug for Coordenada {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Verifica se o modo alternativo {:#?} foi solicitado
if f.alternate() {
write!(
f,
"Coordenada {{\n latitude: {},\n longitude: {},\n}}",
self.lat, self.lon
)
} else {
write!(f, "Coordenada({}, {})", self.lat, self.lon)
}
}
}
fn main() {
let local = Coordenada { lat: -23.5505, lon: -46.6333 };
println!("{}", local); // (-23.5505, -46.6333)
println!("{:?}", local); // Coordenada(-23.5505, -46.6333)
println!("{:#?}", local);
// Coordenada {
// latitude: -23.5505,
// longitude: -46.6333,
// }
}
Exemplo 3: Debug para coleções e tipos aninhados
#[derive(Debug)]
struct Aluno {
nome: String,
notas: Vec<f64>,
}
#[derive(Debug)]
struct Turma {
disciplina: String,
alunos: Vec<Aluno>,
}
fn main() {
let turma = Turma {
disciplina: String::from("Programação Rust"),
alunos: vec![
Aluno {
nome: String::from("Ana"),
notas: vec![9.5, 8.0, 10.0],
},
Aluno {
nome: String::from("Bruno"),
notas: vec![7.5, 8.5, 9.0],
},
],
};
// {:#?} formata a estrutura aninhada de forma legível
println!("{:#?}", turma);
}
Exemplo 4: format! macro e formatação condicional
use std::fmt;
#[derive(Debug)]
struct Temperatura {
celsius: f64,
}
impl fmt::Display for Temperatura {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Respeita a precisão solicitada pelo chamador
if let Some(precisao) = f.precision() {
write!(f, "{:.prec$}°C", self.celsius, prec = precisao)
} else {
write!(f, "{:.1}°C", self.celsius)
}
}
}
fn main() {
let temp = Temperatura { celsius: 23.456 };
// Usa a precisão padrão do Display (1 casa)
println!("{}", temp); // 23.5°C
// Solicita 3 casas decimais
println!("{:.3}", temp); // 23.456°C
// format! retorna uma String
let msg = format!("A temperatura é {:.0}", temp);
println!("{}", msg); // A temperatura é 23°C
}
Exemplo 5: Display para tipos wrapper (newtype)
use std::fmt;
struct Cpf([u8; 11]);
impl Cpf {
fn new(digitos: [u8; 11]) -> Self {
Cpf(digitos)
}
}
impl fmt::Display for Cpf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let d = &self.0;
write!(
f,
"{}{}{}.{}{}{}.{}{}{}-{}{}",
d[0], d[1], d[2], d[3], d[4], d[5],
d[6], d[7], d[8], d[9], d[10]
)
}
}
impl fmt::Debug for Cpf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Cpf(\"{}\")", self) // Reutiliza Display
}
}
fn main() {
let cpf = Cpf::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1]);
println!("{}", cpf); // 123.456.789-01
println!("{:?}", cpf); // Cpf("123.456.789-01")
}
Padrões e Boas Práticas
Sempre derive
Debug: Adicione#[derive(Debug)]a todos os seus tipos. Isso facilita depuração, logs e mensagens de erro.Implemente
Displayquando faz sentido: Nem todo tipo precisa deDisplay. Implemente-o quando seu tipo tem uma representação textual natural (como moeda, data, CPF, coordenadas).Oculte dados sensíveis: Em implementações manuais de
Debug, nunca exponha senhas, tokens ou dados pessoais. Use"****"ou"[REDACTED]".Reutilize
DisplayemDebug: Se seu tipo tem umDisplaybom, sua implementação deDebugpode referenciá-lo comwrite!(f, "NomeTipo(\"{}\")", self).Use
{:#?}para depuração: A formatação alternativa (#) produz saída indentada e multilinha, muito mais legível para estruturas complexas.Displayé necessário paraError: O traitstd::error::Errorexige que o tipo implementeDisplay. Veja Error Trait para mais detalhes.Prefira
write!a concatenação: Dentro defmt, usewrite!(f, ...)em vez de construir strings intermediárias. Isso evita alocações desnecessárias.
Veja Também
- From e Into Traits — conversão de tipos, complementar a
Display - Error Trait — requer
Displaypara mensagens de erro - Hash Trait — outro trait comumente derivado junto com
Debug - Documentação de Código Rust — como documentar seus tipos e traits