O pacote fmt é um dos pacotes mais fundamentais da biblioteca padrão de Go. Ele fornece funções para entrada e saída formatada de dados, similar ao printf e scanf de C, mas com segurança de tipo e integração com o sistema de tipos de Go. Dominar o fmt é essencial para qualquer programador Go, pois ele é usado para depuração, logging, interação com o usuário e formatação de dados para saída em arquivos ou redes.

Nesta aula, vamos cobrir as principais funções de saída (Print, Printf, Sprintf), os verbos de formatação que controlam como os valores são exibidos, as funções de entrada (Scan, Scanf, Sscan) e boas práticas para evitar erros comuns. Ao final, você será capaz de usar o pacote fmt com confiança e escrever código mais claro e eficiente.

Print, Printf, Sprintf

As funções de saída do pacote fmt são usadas para escrever dados na saída padrão (stdout) ou em uma string. As três principais são Print, Printf e Sprintf. Print simplesmente imprime seus argumentos com espaços entre eles e uma nova linha no final. Printf permite formatação usando verbos, similar à função printf de C. Sprintf funciona como Printf, mas retorna a string formatada em vez de imprimi-la.

Exemplo básico:

package main

import "fmt"

func main() {
    nome := "João"
    idade := 30

    // Print: imprime valores separados por espaço
    fmt.Print("Nome:", nome, "Idade:", idade) // Saída: Nome:João Idade:30

    // Printf: formata com verbos
    fmt.Printf("Nome: %s, Idade: %d\n", nome, idade)

    // Sprintf: retorna string formatada
    msg := fmt.Sprintf("Nome: %s, Idade: %d", nome, idade)
    fmt.Println(msg)
}

Note que Print e Println adicionam espaços automaticamente entre argumentos não string, enquanto Printf exige que você especifique a formatação. Println também adiciona uma nova linha. Para saída de depuração rápida, Println é conveniente; para saída controlada, use Printf.

Verbos

Os verbos de formatação são placeholders que começam com % e controlam como um valor é exibido. Os mais comuns são:

  • %v: valor no formato padrão (útil para qualquer tipo)
  • %+v: para structs, inclui nomes dos campos
  • %#v: representação Go-syntax do valor
  • %T: tipo do valor
  • %d: inteiro decimal
  • %s: string
  • %f: ponto flutuante (float64)
  • %t: booleano
  • %p: ponteiro (endereço em hexadecimal)

Exemplo de uso:

package main

import "fmt"

func main() {
    p := struct {
        Nome string
        Idade int
    }{"Maria", 25}

    fmt.Printf("%%v: %v\n", p)   // {Maria 25}
    fmt.Printf("%%+v: %+v\n", p) // {Nome:Maria Idade:25}
    fmt.Printf("%%#v: %#v\n", p) // struct { Nome string; Idade int }{Nome:"Maria", Idade:25}
    fmt.Printf("%%T: %T\n", p)   // struct { Nome string; Idade int }
    fmt.Printf("%%d: %d\n", 42)  // 42
    fmt.Printf("%%s: %s\n", "Olá") // Olá
    fmt.Printf("%%f: %f\n", 3.14) // 3.140000
    fmt.Printf("%%t: %t\n", true) // true
    x := 10
    fmt.Printf("%%p: %p\n", &x) // 0xc0000b2000 (exemplo)
}

Você também pode usar flags como %+d para exibir sinal, % d para espaço em branco, %0d para preenchimento com zeros, e especificar largura e precisão, como %5.2f (largura 5, 2 casas decimais).

Scan

As funções de entrada Scan, Scanf e Sscan (e suas variantes Scanln, Sscanln) leem dados da entrada padrão (stdin) ou de uma string. Elas são úteis para interação com o usuário ou para parsing de texto simples.

Exemplo de Scanf:

package main

import "fmt"

func main() {
    var nome string
    var idade int

    fmt.Print("Digite seu nome e idade: ")
    n, err := fmt.Scanf("%s %d", &nome, &idade)
    if err != nil {
        fmt.Println("Erro:", err)
        return
    }
    fmt.Printf("Lidos %d itens: nome=%s, idade=%d\n", n, nome, idade)
}

Scanf usa verbos de formatação para interpretar a entrada. É importante verificar o erro retornado, pois a entrada pode não corresponder ao formato esperado. Scan lê tokens separados por espaços, enquanto Scanln lê até uma nova linha. Sscan e Sscanln leem de uma string.

Exemplo com Sscan:

package main

import "fmt"

func main() {
    entrada := "42 3.14 verdadeiro"
    var i int
    var f float64
    var b bool

    n, err := fmt.Sscan(entrada, &i, &f, &b)
    if err != nil {
        fmt.Println("Erro:", err)
        return
    }
    fmt.Printf("Lidos %d valores: %d, %f, %t\n", n, i, f, b)
}

Lembre-se de que as funções Scan esperam que os argumentos sejam ponteiros para as variáveis onde os valores serão armazenados.

Boas práticas

Ao usar o pacote fmt, siga estas boas práticas para evitar erros comuns:

  • Sempre verifique erros nas funções de entrada (Scan, Scanf, etc.). Ignorar erros pode levar a valores não inicializados.
  • Use verbos específicos em vez de %v quando possível, para maior clareza e controle.
  • Evite Printf sem necessidade de formatação; prefira Println para saída simples.
  • Cuidado com injeção de formato: nunca passe strings do usuário diretamente como formato em Printf. Use %s ou Print para evitar vulnerabilidades.
  • Prefira fmt.Fprintf para escrever em arquivos ou buffers, pois permite maior controle.
  • Use fmt.Sprintf para construir strings complexas em vez de concatenação.

Exemplo de injeção de formato (perigoso):

// Perigoso: se o usuário digitar "%s%s%s%s", pode causar pânico
fmt.Printf(entradaUsuario)

// Correto: tratar a entrada como valor
fmt.Printf("%s", entradaUsuario)

Referências

Exercícios

  1. Escreva um programa que leia o nome e a idade do usuário usando fmt.Scanf e imprima uma mensagem como "Olá, [nome]! Você tem [idade] anos." usando fmt.Printf.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        var nome string
        var idade int
        fmt.Print("Digite seu nome e idade: ")
        _, err := fmt.Scanf("%s %d", &nome, &idade)
        if err != nil {
            fmt.Println("Erro na leitura:", err)
            return
        }
        fmt.Printf("Olá, %s! Você tem %d anos.\n", nome, idade)
    }
  2. Use fmt.Sprintf para criar uma string formatada que exiba um número inteiro com largura 5 e preenchimento com zeros à esquerda. Imprima a string resultante.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        num := 42
        formatado := fmt.Sprintf("%05d", num)
        fmt.Println(formatado) // "00042"
    }
  3. Escreva um programa que leia uma linha de texto do usuário usando bufio.Scanner e depois use fmt.Sscanf para extrair um número inteiro e uma string separados por espaço.

    ✓ Resposta:
    package main
    
    import (
        "bufio"
        "fmt"
        "os"
    )
    
    func main() {
        scanner := bufio.NewScanner(os.Stdin)
        fmt.Print("Digite um número e uma palavra: ")
        scanner.Scan()
        linha := scanner.Text()
        var num int
        var palavra string
        _, err := fmt.Sscanf(linha, "%d %s", &num, &palavra)
        if err != nil {
            fmt.Println("Erro ao parsear:", err)
            return
        }
        fmt.Printf("Número: %d, Palavra: %s\n", num, palavra)
    }
  4. Crie um programa que use fmt.Fprintf para escrever uma string formatada em um bytes.Buffer e depois imprima o conteúdo do buffer.

    ✓ Resposta:
    package main
    
    import (
        "bytes"
        "fmt"
    )
    
    func main() {
        var buf bytes.Buffer
        nome := "Ana"
        idade := 28
        fmt.Fprintf(&buf, "Nome: %s, Idade: %d", nome, idade)
        fmt.Println(buf.String()) // "Nome: Ana, Idade: 28"
    }
  5. Explique por que o código abaixo é perigoso e como corrigi-lo: fmt.Printf(entradaUsuario).

    ✓ Resposta:

    O código fmt.Printf(entradaUsuario) é perigoso porque permite injeção de formato. Se o usuário digitar algo como %s%s%s%s, a função tentará interpretar a string como formato, podendo causar pânico ou comportamento inesperado. A correção é usar fmt.Printf("%s", entradaUsuario) ou fmt.Print(entradaUsuario) para tratar a entrada como um valor a ser impresso, não como formato.