Visao Geral do Modulo std::fmt
O modulo std::fmt e o coracao do sistema de formatacao do Rust. Ele define as traits e tipos que tornam possivel transformar qualquer valor em texto. Cada vez que voce usa println!, format!, write! ou eprintln!, o modulo std::fmt esta trabalhando por tras.
Os dois pilares do sistema sao as traits Display e Debug:
Display— formatacao voltada ao usuario final (ativada por{})Debug— formatacao voltada ao desenvolvedor (ativada por{:?})
Alem dessas, o modulo oferece controle fino sobre alinhamento, preenchimento, precisao, base numerica e muito mais. Entender std::fmt permite criar saidas formatadas elegantes sem depender de crates externas.
Traits e Tipos Principais
Traits de Formatacao
| Trait | Placeholder | Descricao |
|---|---|---|
Display | {} | Formatacao para usuario final |
Debug | {:?} | Formatacao para debug |
Binary | {:b} | Formato binario |
Octal | {:o} | Formato octal |
LowerHex | {:x} | Hexadecimal minusculo |
UpperHex | {:X} | Hexadecimal maiusculo |
LowerExp | {:e} | Notacao cientifica minuscula |
UpperExp | {:E} | Notacao cientifica maiuscula |
Pointer | {:p} | Endereco de memoria |
Tipos do Modulo
| Tipo | Descricao |
|---|---|
Formatter | Controla como a saida e escrita |
Arguments | Argumentos pre-compilados para formatacao |
Error | Erro retornado por operacoes de formatacao |
Result | Alias para Result<(), fmt::Error> |
Macros Relacionadas
| Macro | Descricao |
|---|---|
format!() | Retorna uma String formatada |
print!()/println!() | Escreve em stdout |
eprint!()/eprintln!() | Escreve em stderr |
write!()/writeln!() | Escreve em qualquer Write |
Exemplos Praticos
1. Sintaxe de Formatacao Completa
fn main() {
let nome = "Rust";
let versao = 1.75;
let contagem = 42;
// Formatacao posicional
println!("{0} tem versao {1}, e {0} e incrivel!", nome, versao);
// Formatacao nomeada
println!("{linguagem} v{ver}", linguagem = nome, ver = versao);
// Captura de variavel (Rust 1.58+)
println!("{nome} v{versao}");
// Padding e alinhamento
println!("[{:>20}]", nome); // alinhado a direita: [ Rust]
println!("[{:<20}]", nome); // alinhado a esquerda: [Rust ]
println!("[{:^20}]", nome); // centralizado: [ Rust ]
println!("[{:*^20}]", nome); // com preenchimento: [********Rust********]
// Largura minima e precisao
println!("{:.2}", 3.14159); // 3.14
println!("{:10.2}", 3.14159); // " 3.14"
println!("{:010.2}", 3.14159); // "0000003.14"
// Bases numericas
println!("Decimal: {contagem}");
println!("Binario: {contagem:b}");
println!("Octal: {contagem:o}");
println!("Hex minusculo: {contagem:x}");
println!("Hex maiusculo: {contagem:X}");
println!("Com prefixo: {contagem:#b}, {contagem:#o}, {contagem:#x}");
// Sinal explicito
println!("{:+}", 42); // +42
println!("{:+}", -42); // -42
// Notacao cientifica
println!("{:e}", 1000.0); // 1e3
println!("{:E}", 0.001); // 1E-3
}
2. Implementando Display e Debug
use std::fmt;
#[derive(Debug)] // Debug pode ser derivado automaticamente
struct Cor {
r: u8,
g: u8,
b: u8,
}
// Display precisa ser implementado manualmente
impl fmt::Display for Cor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
}
}
struct Matriz {
linhas: Vec<Vec<f64>>,
}
impl fmt::Display for Matriz {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, linha) in self.linhas.iter().enumerate() {
if i > 0 {
writeln!(f)?;
}
write!(f, "| ")?;
for (j, val) in linha.iter().enumerate() {
if j > 0 {
write!(f, " ")?;
}
write!(f, "{val:8.2}")?;
}
write!(f, " |")?;
}
Ok(())
}
}
fn main() {
let vermelho = Cor { r: 255, g: 0, b: 0 };
println!("Display: {vermelho}"); // #FF0000
println!("Debug: {vermelho:?}"); // Cor { r: 255, g: 0, b: 0 }
let m = Matriz {
linhas: vec![
vec![1.0, 2.5, 3.0],
vec![4.0, 5.123, 6.0],
],
};
println!("{m}");
// | 1.00 2.50 3.00 |
// | 4.00 5.12 6.00 |
}
3. Formatacao Customizada com Formatter
use std::fmt;
struct Bytes(u64);
impl fmt::Display for Bytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let valor = self.0;
let (num, unidade) = if valor >= 1_073_741_824 {
(valor as f64 / 1_073_741_824.0, "GiB")
} else if valor >= 1_048_576 {
(valor as f64 / 1_048_576.0, "MiB")
} else if valor >= 1024 {
(valor as f64 / 1024.0, "KiB")
} else {
return write!(f, "{valor} B");
};
// Respeita a precisao definida pelo usuario
if let Some(precisao) = f.precision() {
write!(f, "{num:.precisao$} {unidade}")
} else {
write!(f, "{num:.2} {unidade}")
}
}
}
fn main() {
let tamanho = Bytes(1_500_000);
println!("{tamanho}"); // 1.43 MiB
println!("{tamanho:.4}"); // 1.4305 MiB
println!("{}", Bytes(500)); // 500 B
println!("{}", Bytes(2_147_483_648)); // 2.00 GiB
}
4. Usando write! com Buffers
use std::fmt::Write;
fn gerar_tabela(dados: &[(&str, f64)]) -> String {
let mut saida = String::new();
writeln!(saida, "+{:-<20}+{:-<12}+", "", "").unwrap();
writeln!(saida, "| {:18} | {:>10} |", "Item", "Preco (R$)").unwrap();
writeln!(saida, "+{:-<20}+{:-<12}+", "", "").unwrap();
let mut total = 0.0;
for (item, preco) in dados {
writeln!(saida, "| {:18} | {:>10.2} |", item, preco).unwrap();
total += preco;
}
writeln!(saida, "+{:-<20}+{:-<12}+", "", "").unwrap();
writeln!(saida, "| {:18} | {:>10.2} |", "TOTAL", total).unwrap();
writeln!(saida, "+{:-<20}+{:-<12}+", "", "").unwrap();
saida
}
fn main() {
let itens = vec![
("Teclado mecanico", 450.00),
("Mouse gamer", 200.50),
("Monitor 27\"", 1899.99),
];
print!("{}", gerar_tabela(&itens));
}
5. Debug Customizado e Pretty-Print
use std::fmt;
struct Config {
host: String,
porta: u16,
senha: String,
max_conexoes: usize,
}
impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Config")
.field("host", &self.host)
.field("porta", &self.porta)
.field("senha", &"***") // Oculta a senha
.field("max_conexoes", &self.max_conexoes)
.finish()
}
}
impl fmt::Display for Config {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{} (max: {} conexoes)", self.host, self.porta, self.max_conexoes)
}
}
fn main() {
let cfg = Config {
host: "localhost".into(),
porta: 5432,
senha: "super_secreta_123".into(),
max_conexoes: 100,
};
// Display: para o usuario
println!("{cfg}");
// localhost:5432 (max: 100 conexoes)
// Debug compacto
println!("{cfg:?}");
// Config { host: "localhost", porta: 5432, senha: "***", max_conexoes: 100 }
// Debug pretty-print
println!("{cfg:#?}");
// Config {
// host: "localhost",
// porta: 5432,
// senha: "***",
// max_conexoes: 100,
// }
}
Padroes Comuns
Especificadores de Formato — Referencia Rapida
A sintaxe completa de um placeholder e:
{[argumento][:[preenchimento][alinhamento][sinal][#][0][largura][.precisao][tipo]]}
Onde:
- preenchimento — qualquer caractere (padrao: espaco)
- alinhamento —
<(esquerda),>(direita),^(centro) - sinal —
+para sempre mostrar o sinal - # — forma alternativa (
#b=0b,#x=0x,#?= pretty-print) - 0 — preenche com zeros
- largura — largura minima do campo
- precisao — casas decimais ou tamanho maximo de string
- tipo —
b,o,x,X,e,E,?,p
Largura e Precisao Dinamicas
Voce pode usar argumentos para definir largura e precisao em tempo de execucao:
fn main() {
let largura = 20;
let precisao = 3;
println!("{:>largura$.precisao$}", 3.14159);
// " 3.142"
// Usando posicao
println!("{0:>1$.2$}", 3.14159, 20, 3);
}
Implementando Display para Enums
use std::fmt;
enum Status {
Ativo,
Inativo,
Pendente(String),
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Status::Ativo => write!(f, "Ativo"),
Status::Inativo => write!(f, "Inativo"),
Status::Pendente(motivo) => write!(f, "Pendente: {motivo}"),
}
}
}
Quando Usar (e Quando Nao Usar)
Use std::fmt quando:
- Precisar de formatacao customizada para seus tipos
- Quiser controlar a representacao textual de structs e enums
- Precisar gerar saida tabulada ou alinhada
- Quiser ocultar campos sensiveis no
Debug
Nao use std::fmt quando:
- Precisar de serializacao completa (JSON, TOML) — use
serde - Precisar de templates HTML — use crates como
askamaoutera - Precisar de formatacao de datas/horas complexa — use
chrono
Dica: Sempre implemente Display para tipos que serao exibidos ao usuario. Derive Debug para todos os tipos durante o desenvolvimento. Use #[derive(Debug)] liberalmente — o custo e zero quando nao usado.
Veja Tambem
- Formatar Strings em Rust — receitas praticas de formatacao
- Display e Debug — aprofundamento nas traits de exibicao
- String e &str — tipos de texto que implementam
Display - O Prelude do Rust — traits de formatacao importadas automaticamente
- Documentacao oficial — std::fmt