ZetCode

Golang fmt.Formatter Interface

last modified May 8, 2025

This tutorial explains how to use the fmt.Formatter interface in Go. We'll cover interface basics with practical examples of custom formatting.

The fmt.Formatter interface allows types to control their formatting. It provides more flexibility than the Stringer interface by supporting format verbs and flags.

In Go, fmt.Formatter is used when you need custom formatting behavior beyond simple string conversion. It works with all fmt printing functions like Printf and Sprintf.

Basic fmt.Formatter interface definition

The fmt.Formatter interface requires implementing one method. This example shows the interface definition and basic implementation.
Note: The Format method gives you full control over formatting.

basic_formatter.go
package main

import "fmt"

type Point struct {
    X, Y int
}

func (p Point) Format(f fmt.State, verb rune) {
    switch verb {
    case 'v':
        if f.Flag('+') {
            fmt.Fprintf(f, "Point{X: %d, Y: %d}", p.X, p.Y)
        } else {
            fmt.Fprintf(f, "{%d %d}", p.X, p.Y)
        }
    case 's':
        fmt.Fprintf(f, "(%d,%d)", p.X, p.Y)
    default:
        fmt.Fprintf(f, "%%!%c(Point=%d,%d)", verb, p.X, p.Y)
    }
}

func main() {
    p := Point{3, 4}
    fmt.Printf("%v\n", p)       // {3 4}
    fmt.Printf("%+v\n", p)      // Point{X: 3, Y: 4}
    fmt.Printf("%s\n", p)       // (3,4)
    fmt.Printf("%d\n", p)       // %!d(Point=3,4)
}

The Point type implements Format to handle different format verbs. It supports 'v' with + flag and 's' verb with custom formatting.

Handling width and precision

The fmt.State parameter provides access to formatting flags. This example shows how to handle width and precision specifications.

width_precision.go
package main

import (
    "fmt"
    "strings"
)

type Banner string

func (b Banner) Format(f fmt.State, verb rune) {
    switch verb {
    case 's', 'v':
        width, hasWidth := f.Width()
        if hasWidth {
            padded := strings.Repeat(string(b), width/len(b)+1)
            fmt.Fprint(f, padded[:width])
        } else {
            fmt.Fprint(f, string(b))
        }
    default:
        fmt.Fprintf(f, "%%!%c(Banner=%s)", verb, b)
    }
}

func main() {
    b := Banner("Go")
    fmt.Printf("%s\n", b)       // Go
    fmt.Printf("%5s\n", b)      // GoGoG
    fmt.Printf("%.3s\n", b)     // Go
    fmt.Printf("%5.3s\n", b)    //   Go
}

The Banner type uses Width() to check for width specification. It repeats the banner text to fill the requested width when printing.

Custom flag handling

The fmt.State interface provides flag information. This example demonstrates custom flag handling in a formatter.

flag_handling.go
package main

import "fmt"

type Temperature float64

func (t Temperature) Format(f fmt.State, verb rune) {
    switch verb {
    case 'f', 'F':
        prec, hasPrec := f.Precision()
        if !hasPrec {
            prec = 1
        }
        if f.Flag('+') {
            fmt.Fprintf(f, "%+.*f°F", prec, float64(t))
        } else {
            fmt.Fprintf(f, "%.*f°C", prec, float64(t)*5/9)
        }
    default:
        fmt.Fprintf(f, "%%!%c(Temperature=%f)", verb, t)
    }
}

func main() {
    temp := Temperature(32.0)
    fmt.Printf("%f\n", temp)     // 0.0°C
    fmt.Printf("%+.2f\n", temp) // +32.00°F
    fmt.Printf("%5.1f\n", temp) //  0.0°C
}

The Temperature type checks for '+' flag to switch between Fahrenheit and Celsius. It also handles precision specifications for decimal places.

Combining with Stringer interface

A type can implement both fmt.Stringer and fmt.Formatter. This example shows how they work together.

stringer_combine.go
package main

import "fmt"

type Color struct {
    R, G, B uint8
}

func (c Color) String() string {
    return fmt.Sprintf("RGB(%d,%d,%d)", c.R, c.G, c.B)
}

func (c Color) Format(f fmt.State, verb rune) {
    switch verb {
    case 'v':
        if f.Flag('#') {
            fmt.Fprintf(f, "Color{R:0x%02x, G:0x%02x, B:0x%02x}", c.R, c.G, c.B)
        } else {
            fmt.Fprint(f, c.String())
        }
    case 's':
        fmt.Fprint(f, c.String())
    case 'x', 'X':
        fmt.Fprintf(f, "%02x%02x%02x", c.R, c.G, c.B)
    default:
        fmt.Fprintf(f, "%%!%c(Color=%s)", verb, c.String())
    }
}

func main() {
    c := Color{255, 0, 128}
    fmt.Println(c)              // RGB(255,0,128)
    fmt.Printf("%v\n", c)       // RGB(255,0,128)
    fmt.Printf("%#v\n", c)      // Color{R:0xff, G:0x00, B:0x80}
    fmt.Printf("%x\n", c)       // ff0080
}

The Color type uses String() for simple string conversion. The Format method provides additional formatting options including hex output and struct-style printing with the # flag.

Formatting complex numbers

The fmt.Formatter can be used to create custom complex number formatting. This example shows polar coordinate representation.

complex_format.go
package main

import (
    "fmt"
    "math"
)

type Polar complex128

func (p Polar) Format(f fmt.State, verb rune) {
    r := real(p)
    i := imag(p)
    switch verb {
    case 'v', 'f', 'g':
        mag := math.Hypot(r, i)
        ang := math.Atan2(i, r) * 180 / math.Pi
        prec, hasPrec := f.Precision()
        if !hasPrec {
            prec = 2
        }
        fmt.Fprintf(f, "%.*f∠%.*f°", prec, mag, prec, ang)
    default:
        fmt.Fprintf(f, "%%!%c(Polar=%f+%fi)", verb, r, i)
    }
}

func main() {
    p := Polar(1 + 1i)
    fmt.Printf("%v\n", p)       // 1.41∠45.00°
    fmt.Printf("%.3f\n", p)     // 1.414∠45.000°
    fmt.Printf("%.1g\n", p)     // 1.4∠45.0°
}

The Polar type formats complex numbers in polar notation. It calculates magnitude and angle, then formats them with the specified precision.

Custom date formatting

Implementing fmt.Formatter allows flexible date formatting. This example shows custom date output with different verbs.

date_format.go
package main

import (
    "fmt"
    "time"
)

type MyDate time.Time

func (d MyDate) Format(f fmt.State, verb rune) {
    t := time.Time(d)
    switch verb {
    case 'd':
        fmt.Fprintf(f, "%02d/%02d/%04d", t.Day(), t.Month(), t.Year())
    case 't':
        fmt.Fprintf(f, "%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
    case 'v':
        if f.Flag('+') {
            fmt.Fprintf(f, time.Time(d).String())
        } else {
            fmt.Fprintf(f, "%s", t.Format("2006-01-02"))
        }
    default:
        fmt.Fprintf(f, "%%!%c(MyDate=%s)", verb, t.Format(time.RFC3339))
    }
}

func main() {
    d := MyDate(time.Date(2023, 5, 15, 14, 30, 0, 0, time.UTC))
    fmt.Printf("%d\n", d)       // 15/05/2023
    fmt.Printf("%t\n", d)       // 14:30:00
    fmt.Printf("%v\n", d)       // 2023-05-15
    fmt.Printf("%+v\n", d)      // 2023-05-15 14:30:00 +0000 UTC
}

The MyDate type provides custom formatting for dates. It supports 'd' for date, 't' for time, and 'v' with + flag for detailed output.

Binary data formatting

The fmt.Formatter can format binary data in different representations. This example shows hex dump and binary output.

binary_format.go
package main

import (
    "fmt"
    "strings"
)

type Binary []byte

func (b Binary) Format(f fmt.State, verb rune) {
    switch verb {
    case 'x', 'X':
        for _, byteVal := range b {
            fmt.Fprintf(f, "%02x", byteVal)
        }
    case 'b':
        for _, byteVal := range b {
            fmt.Fprintf(f, "%08b ", byteVal)
        }
    case 'v':
        if f.Flag('#') {
            fmt.Fprintf(f, "Binary{0x%x}", b)
        } else {
            fmt.Fprintf(f, "%x", b)
        }
    default:
        fmt.Fprintf(f, "%%!%c(Binary=%x)", verb, b)
    }
}

func main() {
    data := Binary{0xDE, 0xAD, 0xBE, 0xEF}
    fmt.Printf("%x\n", data)    // deadbeef
    fmt.Printf("%X\n", data)    // DEADBEEF
    fmt.Printf("%b\n", data)    // 11011110 10101101 10111110 11101111 
    fmt.Printf("%#v\n", data)   // Binary{0xdeadbeef}
}

The Binary type formats byte slices as hex or binary. The '#' flag triggers Go-syntax representation when used with 'v' verb.

Source

Go fmt package documentation

This tutorial covered the fmt.Formatter interface in Go with practical examples of custom formatting for different types.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Golang tutorials.