O pacote fmt é um dos mais importantes da biblioteca padrão de Go, fornecendo funções para formatação e leitura de entrada e saída. Ele implementa formatação inspirada na linguagem C, mas com tipagem segura e extensibilidade. Dominar esse pacote é essencial para qualquer desenvolvedor Go, pois ele é usado tanto para depuração rápida quanto para produção de mensagens formatadas para o usuário.

Nesta aula, exploraremos as funções mais comuns de saída (Print, Printf, Sprintf), os verbos de formatação que controlam a aparência dos dados, as funções de entrada (Scan e variantes) e boas práticas para uso eficiente e correto.

Print, Printf, Sprintf

As funções Print, Printf e Sprintf são as principais para gerar saída formatada. Print simplesmente escreve os argumentos separados por espaços e adiciona uma nova linha ao final. Printf permite especificar um formato com verbos de formatação, sem adicionar nova linha automaticamente. Sprintf retorna a string formatada em vez de escrevê-la na saída padrão.

Exemplo prático:

package main

import "fmt"

func main() {
    nome := "Maria"
    idade := 30
    altura := 1.75

    // Print: espaços entre argumentos, nova linha no final
    fmt.Print("Nome:", nome, "Idade:", idade)

    // Printf: controle total com verbos
    fmt.Printf("\nNome: %s, Idade: %d, Altura: %.2f\n", nome, idade, altura)

    // Sprintf: retorna string formatada
    mensagem := fmt.Sprintf("%s tem %d anos e %.2f m de altura.", nome, idade, altura)
    fmt.Println(mensagem)
}

A saída será:

Nome:Maria Idade:30
Nome: Maria, Idade: 30, Altura: 1.75
Maria tem 30 anos e 1.75 m de altura.

Note que Print não insere espaços automaticamente entre a string e o valor, mas insere entre cada argumento. Já Printf requer que você especifique a formatação exata.

Verbos

Os verbos de formatação são códigos iniciados com % que controlam como os valores são convertidos para string. Os mais comuns são:

  • %v: valor padrão (default format)
  • %+v: para structs, exibe nomes dos campos
  • %#v: representação Go-syntax do valor
  • %T: tipo do valor
  • %d: inteiro decimal
  • %b: binário
  • %o: octal
  • %x: hexadecimal (letras minúsculas)
  • %X: hexadecimal (letras maiúsculas)
  • %f: ponto flutuante decimal
  • %e: notação científica (e minúsculo)
  • %E: notação científica (E maiúsculo)
  • %s: string
  • %q: string entre aspas duplas, escapando caracteres especiais
  • %p: ponteiro (endereço hexadecimal)

Exemplo:

package main

import "fmt"

func main() {
    x := 255
    fmt.Printf("Decimal: %d\n", x)
    fmt.Printf("Binário: %b\n", x)
    fmt.Printf("Octal: %o\n", x)
    fmt.Printf("Hexadecimal: %x\n", x)

    pi := 3.1415926535
    fmt.Printf("Pi: %.2f\n", pi)   // duas casas decimais
    fmt.Printf("Pi: %e\n", pi)

    str := "Olá, mundo!"
    fmt.Printf("String: %s\n", str)
    fmt.Printf("Quoted: %q\n", str)
}

Saída:

Decimal: 255
Binário: 11111111
Octal: 377
Hexadecimal: ff
Pi: 3.14
Pi: 3.141593e+00
String: Olá, mundo!
Quoted: "Olá, mundo!"

Além disso, é possível usar flags como + (sempre exibir sinal), - (alinhar à esquerda), 0 (preencher com zeros), e largura/precisão numérica.

Scan

O pacote fmt também oferece funções para leitura de entrada formatada: Scan, Scanf e Sscan (e suas variantes com ln). Scan lê valores separados por espaços da entrada padrão. Scanf lê de acordo com um formato especificado. Sscan lê de uma string.

Exemplo:

package main

import "fmt"

func main() {
    var nome string
    var idade int

    fmt.Print("Digite seu nome e idade: ")
    _, err := fmt.Scan(&nome, &idade)
    if err != nil {
        fmt.Println("Erro na leitura:", err)
        return
    }
    fmt.Printf("Nome: %s, Idade: %d\n", nome, idade)
}

Para leitura com formato específico:

package main

import "fmt"

func main() {
    var nome string
    var idade int
    fmt.Print("Digite no formato 'Nome Idade': ")
    _, err := fmt.Scanf("%s %d", &nome, &idade)
    if err != nil {
        fmt.Println("Erro:", err)
        return
    }
    fmt.Printf("Lido: %s, %d\n", nome, idade)
}

As funções Scanln e Scanf param na nova linha. Sscanf permite analisar uma string já existente.

Boas práticas

Ao usar o pacote fmt, algumas práticas ajudam a evitar erros e melhorar a legibilidade:

  • Evite usar Print para debugging em produção: prefira o pacote log ou bibliotecas de logging estruturado.
  • Use fmt.Errorf para criar erros formatados: em vez de fmt.Sprintf + errors.New, use fmt.Errorf que já retorna um erro.
  • Prefira fmt.Fprint* para escrever em io.Writer: funções como fmt.Fprintf permitem escrever em qualquer writer (arquivo, buffer, etc.), tornando o código mais flexível.
  • Cuidado com a ordem dos argumentos: em Printf, a ordem dos verbos deve corresponder à ordem dos argumentos. Erros comuns são trocar a ordem ou esquecer argumentos.
  • Use %v para valores genéricos: quando não precisar de formatação específica, %v é suficiente e evita erros de tipo.
  • Verifique sempre os erros retornados por Scan: a leitura pode falhar, e ignorar o erro pode levar a dados inconsistentes.

Exemplo de boas práticas:

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var buf bytes.Buffer
    fmt.Fprintf(&buf, "Erro ao processar %s: %v", "arquivo.txt", fmt.Errorf("permissão negada"))
    fmt.Println(buf.String())
}

Isso demonstra o uso de Fprintf com um buffer, e fmt.Errorf para criar erros formatados.

Referências

Exercícios

  1. Escreva um programa que leia dois números inteiros do usuário usando fmt.Scan e imprima a soma com fmt.Printf, formatando o resultado como "Soma: X".

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        var a, b int
        fmt.Print("Digite dois números: ")
        _, err := fmt.Scan(&a, &b)
        if err != nil {
            fmt.Println("Erro:", err)
            return
        }
        fmt.Printf("Soma: %d\n", a+b)
    }
  2. Crie uma struct Pessoa com campos Nome e Idade. Use fmt.Printf com o verbo %+v para imprimir uma instância.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    type Pessoa struct {
        Nome  string
        Idade int
    }
    
    func main() {
        p := Pessoa{"João", 25}
        fmt.Printf("%+v\n", p)
    }
  3. Use fmt.Sprintf para criar uma mensagem formatada a partir de variáveis e depois imprima-a com fmt.Println.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        nome := "Ana"
        idade := 28
        msg := fmt.Sprintf("%s tem %d anos.", nome, idade)
        fmt.Println(msg)
    }
  4. Leia uma linha inteira do usuário usando fmt.Scanln e imprima o que foi lido com fmt.Printf usando o verbo %q.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        var linha string
        fmt.Print("Digite algo: ")
        _, err := fmt.Scanln(&linha)
        if err != nil {
            fmt.Println("Erro:", err)
            return
        }
        fmt.Printf("Você digitou: %q\n", linha)
    }
  5. Escreva um programa que use fmt.Errorf para criar um erro formatado com a mensagem "Erro ao abrir arquivo: " e imprima o erro.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        nome := "dados.txt"
        err := fmt.Errorf("Erro ao abrir arquivo: %s", nome)
        fmt.Println(err)
    }