Modulo std::fmt em Rust: Formatacao Completa

Guia completo do modulo std::fmt em Rust: format!, write!, Display, Debug, Formatter, padding, alinhamento, precisao e formatacao customizada.

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

TraitPlaceholderDescricao
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

TipoDescricao
FormatterControla como a saida e escrita
ArgumentsArgumentos pre-compilados para formatacao
ErrorErro retornado por operacoes de formatacao
ResultAlias para Result<(), fmt::Error>

Macros Relacionadas

MacroDescricao
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
  • tipob, 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 askama ou tera
  • 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