O que é Fmt em Go?

O pacote fmt (abreviação de “format”) é um dos pacotes mais utilizados em Go. Ele implementa formatação de entrada e saída (I/O) com funções análogas a printf e scanf da linguagem C, mas com capacidades estendidas e segurança de tipos. Praticamente todo programa Go usa fmt — desde um simples “Hello, World!” até sistemas complexos de produção.

O pacote fmt é parte da biblioteca padrão de Go e trabalha em conjunto com outros pacotes fundamentais como io para escrita em streams, string para manipulação de texto, e error para criação de erros formatados.

Primeiro exemplo

package main

import "fmt"

func main() {
    nome := "Go"
    versao := 1.22
    fmt.Printf("Bem-vindo ao %s versão %.2f!\n", nome, versao)
    // Bem-vindo ao Go versão 1.22!
}

Funções de Impressão

O pacote fmt oferece três famílias de funções de impressão, cada uma com um destino diferente:

fmt.Print("Sem newline")        // imprime sem newline
fmt.Println("Com newline")       // adiciona \n no final
fmt.Printf("Formatado: %d\n", 42) // formatação com verbos

Fprint, Fprintln, Fprintf — escrevem para io.Writer

Essas funções aceitam um io.Writer como primeiro argumento, permitindo escrever para qualquer destino — arquivos, buffers, conexões de rede, ou respostas HTTP:

// Escrevendo para arquivo
arquivo, _ := os.Create("saida.txt")
defer arquivo.Close()
fmt.Fprintln(arquivo, "Escrito no arquivo")

// Escrevendo para buffer
var buf bytes.Buffer
fmt.Fprintf(&buf, "Buffer contém: %d bytes", 42)

// Escrevendo em resposta HTTP
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Olá, %s!", r.URL.Query().Get("nome"))
}

Sprint, Sprintln, Sprintf — retornam string

Em vez de imprimir, retornam o resultado como uma string. São extremamente úteis para construir mensagens dinâmicas:

nome := "Maria"
idade := 30
mensagem := fmt.Sprintf("%s tem %d anos", nome, idade)
// mensagem = "Maria tem 30 anos"

erro := fmt.Sprintf("falha ao processar item %d de %d", atual, total)

Verbos de Formatação

Os verbos são o coração do pacote fmt. Cada verbo começa com % e indica como formatar o argumento correspondente:

Verbos gerais

type Usuario struct {
    Nome  string
    Idade int
}

u := Usuario{"Ana", 25}

fmt.Printf("%v\n", u)   // {Ana 25}           — valor padrão
fmt.Printf("%+v\n", u)  // {Nome:Ana Idade:25} — com nomes dos campos
fmt.Printf("%#v\n", u)  // main.Usuario{Nome:"Ana", Idade:25} — sintaxe Go
fmt.Printf("%T\n", u)   // main.Usuario        — tipo
fmt.Printf("%%\n")      // %                   — literal

Verbos para inteiros

n := 42
fmt.Printf("%d\n", n)   // 42      — decimal
fmt.Printf("%b\n", n)   // 101010  — binário
fmt.Printf("%o\n", n)   // 52      — octal
fmt.Printf("%x\n", n)   // 2a      — hexadecimal minúsculo
fmt.Printf("%X\n", n)   // 2A      — hexadecimal maiúsculo
fmt.Printf("%c\n", 65)  // A       — caractere Unicode
fmt.Printf("%U\n", 'A') // U+0041  — formato Unicode
fmt.Printf("%05d\n", n) // 00042   — padding com zeros

Verbos para floats

pi := 3.14159265
fmt.Printf("%f\n", pi)    // 3.141593       — decimal
fmt.Printf("%.2f\n", pi)  // 3.14           — 2 casas decimais
fmt.Printf("%e\n", pi)    // 3.141593e+00   — notação científica
fmt.Printf("%g\n", pi)    // 3.14159265     — mais compacto
fmt.Printf("%10.2f\n", pi) //       3.14    — largura total 10

Verbos para strings e bytes

s := "Golang"
fmt.Printf("%s\n", s)   // Golang       — string pura
fmt.Printf("%q\n", s)   // "Golang"     — com aspas
fmt.Printf("%x\n", s)   // 476f6c616e67 — hex dos bytes
fmt.Printf("%-10s|\n", s) // Golang    | — alinhado à esquerda
fmt.Printf("%10s|\n", s)  //     Golang| — alinhado à direita

Verbos para ponteiros e booleanos

x := 42
fmt.Printf("%p\n", &x)    // 0xc0000b4008 — endereço do ponteiro
fmt.Printf("%t\n", true)  // true          — booleano

Interface Stringer

A interface fmt.Stringer permite que seus tipos personalizem como são impressos pelo pacote fmt. Qualquer type que implemente o method String() string será formatado automaticamente quando usado com %v ou %s:

type Moeda struct {
    Valor   float64
    Simbolo string
}

func (m Moeda) String() string {
    return fmt.Sprintf("%s %.2f", m.Simbolo, m.Valor)
}

func main() {
    preco := Moeda{Valor: 199.90, Simbolo: "R$"}
    fmt.Println(preco)          // R$ 199.90
    fmt.Printf("Preço: %v\n", preco) // Preço: R$ 199.90
    fmt.Printf("Preço: %s\n", preco) // Preço: R$ 199.90
}

Interface GoStringer

Para customizar a saída do verbo %#v (representação Go), implemente GoString() string:

func (m Moeda) GoString() string {
    return fmt.Sprintf("Moeda{Valor: %.2f, Simbolo: %q}", m.Valor, m.Simbolo)
}

func main() {
    preco := Moeda{Valor: 199.90, Simbolo: "R$"}
    fmt.Printf("%#v\n", preco) // Moeda{Valor: 199.90, Simbolo: "R$"}
}

Fmt.Errorf e Wrapping de Erros

fmt.Errorf é a forma mais comum de criar errors formatados em Go. Com o verbo %w, introduzido no Go 1.13, você pode “embrulhar” (wrap) um erro existente, preservando a cadeia de erros para inspeção posterior:

func buscarUsuario(id int) (*Usuario, error) {
    dados, err := bancoDeDados.Query("SELECT * FROM usuarios WHERE id = ?", id)
    if err != nil {
        return nil, fmt.Errorf("falha ao buscar usuário %d: %w", id, err)
    }

    usuario, err := decodificar(dados)
    if err != nil {
        return nil, fmt.Errorf("falha ao decodificar usuário %d: %w", id, err)
    }

    return usuario, nil
}

// O caller pode inspecionar a cadeia de erros
func main() {
    u, err := buscarUsuario(123)
    if err != nil {
        // Verifica se o erro original é um erro de conexão
        var connErr *net.OpError
        if errors.As(err, &connErr) {
            log.Println("Problema de conexão:", connErr)
        }

        // Verifica se é um erro específico
        if errors.Is(err, sql.ErrNoRows) {
            log.Println("Usuário não encontrado")
        }
    }
}

A diferença entre %v e %w:

  • %v formata o erro como texto, sem manter a referência ao erro original
  • %w mantém a referência, permitindo errors.Is() e errors.As() na cadeia

Funções de Leitura (Scan)

O pacote fmt também oferece funções para ler entrada formatada, geralmente de os.Stdin:

func lerDados() {
    var nome string
    var idade int

    // Scan — lê valores separados por espaço
    fmt.Print("Nome e idade: ")
    fmt.Scan(&nome, &idade)

    // Scanf — lê com formato específico
    var hora, minuto int
    fmt.Print("Horário (HH:MM): ")
    fmt.Scanf("%d:%d", &hora, &minuto)

    // Scanln — lê até newline
    var linha string
    fmt.Print("Mensagem: ")
    fmt.Scanln(&linha)

    // Sscanf — lê de string
    entrada := "42 3.14 true"
    var n int
    var f float64
    var b bool
    fmt.Sscanf(entrada, "%d %f %t", &n, &f, &b)
    fmt.Printf("n=%d, f=%.2f, b=%t\n", n, f, b)
}

Para leitura de entrada mais robusta, considere usar bufio.Scanner, que lida melhor com linhas longas e diferentes delimitadores.

Performance e Boas Práticas

Evite fmt em hot paths

As funções do pacote fmt usam reflection internamente, o que tem custo de performance. Em caminhos críticos de performance, considere alternativas:

// LENTO — usa reflection
msg := fmt.Sprintf("usuário-%d", id)

// RÁPIDO — concatenação direta
msg := "usuário-" + strconv.Itoa(id)

// RÁPIDO — strings.Builder para múltiplas concatenações
var sb strings.Builder
sb.WriteString("usuário-")
sb.WriteString(strconv.Itoa(id))
sb.WriteString("-ativo")
msg := sb.String()

Resultados de benchmark típico:

BenchmarkSprintf    5000000    250 ns/op    48 B/op    2 allocs/op
BenchmarkConcat    20000000     80 ns/op    16 B/op    1 allocs/op
BenchmarkBuilder   30000000     50 ns/op    16 B/op    1 allocs/op

Evite fmt.Sprintf para erros simples

// Menos eficiente
return fmt.Errorf("valor inválido")

// Mais eficiente para erros sem formatação
return errors.New("valor inválido")

// Use Errorf apenas quando precisar de formatação ou %w
return fmt.Errorf("valor %d inválido para campo %s: %w", val, campo, err)

Tabela de referência rápida

VerboUsoExemploSaída
%vValor padrãofmt.Sprintf("%v", 42)42
%+vStruct com camposfmt.Sprintf("%+v", s){Nome:Go}
%#vSintaxe Gofmt.Sprintf("%#v", s)main.S{Nome:"Go"}
%TTipofmt.Sprintf("%T", 42)int
%dDecimalfmt.Sprintf("%d", 42)42
%sStringfmt.Sprintf("%s", "Go")Go
%qString entre aspasfmt.Sprintf("%q", "Go")"Go"
%wWrap errorfmt.Errorf(": %w", err)erro encadeado

Próximos Passos

Para continuar aprendendo sobre formatação, saída e I/O em Go:

  • IO — interfaces Reader e Writer usadas por Fprint
  • String — manipulação de strings em Go
  • Error — sistema de erros e uso de Errorf com %w
  • Log — logging com formatação similar a fmt
  • Rune — caracteres Unicode e o verbo %c
  • Type — tipos em Go e o verbo %T
  • Interface — Stringer e GoStringer
  • Go para Iniciantes — primeiros passos com Go

Perguntas Frequentes

Qual a diferença entre Print, Println e Printf?

fmt.Print imprime os argumentos sem newline e sem formatação. fmt.Println adiciona espaço entre argumentos e newline no final. fmt.Printf aceita um format string com verbos (como %d, %s) seguido dos argumentos correspondentes. Use Printf quando precisar de formatação precisa e Println para debug rápido.

Quando usar Sprintf vs concatenação de strings?

Use fmt.Sprintf para legibilidade quando a performance não é crítica — a formatação fica clara e fácil de manter. Use concatenação (+) ou strings.Builder em hot paths onde a performance importa, pois Sprintf usa reflection internamente. Em benchmark típicos, concatenação é 3-5x mais rápida que Sprintf.

Como funciona o verbo %w em fmt.Errorf?

O verbo %w “embrulha” (wraps) um error existente dentro de um novo error formatado. O erro original fica acessível via errors.Unwrap(), errors.Is() e errors.As(). Diferente de %v que apenas converte o erro para texto, %w preserva a referência ao erro original, permitindo inspeção da cadeia de erros completa.

O pacote fmt é seguro para uso concorrente?

As funções do pacote fmt que escrevem para io.Writer são seguras para uso concorrente apenas se o Writer subjacente for thread-safe. As funções Sprintf, Sprint e Sprintln são sempre seguras pois não compartilham estado. Para escrita concorrente em stdout/stderr, considere usar o pacote log que inclui sincronização interna via mutex.