ZetCode

Golang fmt.Stringer Interface

last modified May 8, 2025

This tutorial explains how to use the fmt.Stringer interface in Go. We'll cover interface basics with practical examples of string representation.

The fmt.Stringer interface is used to customize how types are printed. It defines a single method String() string that returns a string representation of the value.

In Go, implementing Stringer allows types to control their output format when printed with functions like fmt.Println. This is useful for debugging and logging.

Basic Stringer implementation

The simplest implementation of Stringer provides a custom string representation for a type. This example demonstrates basic usage.
Note: The String() method must return a string value.

basic_stringer.go
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s (%d years)", p.Name, p.Age)
}

func main() {
    p := Person{"Alice", 25}
    fmt.Println(p) // Uses our String() method
}

The Person type implements Stringer by defining a String() method. When printed, our custom format is used instead of the default struct representation.

Stringer with pointer receivers

Stringer can be implemented with pointer receivers for mutable types. This example shows pointer receiver implementation.

pointer_stringer.go
package main

import "fmt"

type Counter struct {
    value int
}

func (c *Counter) Increment() {
    c.value++
}

func (c *Counter) String() string {
    return fmt.Sprintf("Counter: %d", c.value)
}

func main() {
    c := &Counter{}
    c.Increment()
    fmt.Println(c) // Prints "Counter: 1"
}

The Counter type uses a pointer receiver for String(). This allows the method to access and format the current value after mutations.

Stringer with complex types

Stringer can format complex types with nested structures. This example shows custom formatting for a composite type.

complex_stringer.go
package main

import (
    "fmt"
    "strings"
)

type Address struct {
    Street  string
    City    string
    Country string
}

func (a Address) String() string {
    return fmt.Sprintf("%s, %s, %s", a.Street, a.City, a.Country)
}

type Contact struct {
    Name    string
    Email   string
    Address Address
}

func (c Contact) String() string {
    return fmt.Sprintf("%s <%s>\n%s", c.Name, c.Email, c.Address)
}

func main() {
    addr := Address{"123 Main St", "Springfield", "USA"}
    contact := Contact{"Bob", "bob@example.com", addr}
    fmt.Println(contact)
}

Both Address and Contact implement Stringer. The Contact's String() method uses Address's String() method to build its output. This creates a clean, hierarchical format.

Stringer with collections

Stringer can format collection types like slices and maps. This example shows custom formatting for a slice type.

collection_stringer.go
package main

import (
    "fmt"
    "strconv"
)

type IntList []int

func (list IntList) String() string {
    var builder strings.Builder
    builder.WriteString("[")
    for i, v := range list {
        if i > 0 {
            builder.WriteString(", ")
        }
        builder.WriteString(strconv.Itoa(v))
    }
    builder.WriteString("]")
    return builder.String()
}

func main() {
    nums := IntList{1, 2, 3, 4, 5}
    fmt.Println(nums) // Prints "[1, 2, 3, 4, 5]"
}

The IntList type implements Stringer to format its elements. The strings.Builder is used for efficient string concatenation. This produces clean output for slices.

Stringer with enums

Stringer is commonly used with iota-based enums to provide readable names. This example demonstrates enum string representation.

enum_stringer.go
package main

import "fmt"

type Status int

const (
    Pending Status = iota
    Processing
    Completed
    Failed
)

func (s Status) String() string {
    switch s {
    case Pending:
        return "Pending"
    case Processing:
        return "Processing"
    case Completed:
        return "Completed"
    case Failed:
        return "Failed"
    default:
        return fmt.Sprintf("Status(%d)", s)
    }
}

func main() {
    status := Processing
    fmt.Println("Current status:", status)
}

The Status type implements Stringer to return descriptive names for enum values. This makes output more readable than raw integer values.

Stringer with embedded types

Stringer can be implemented for types that embed other types. This example shows how embedded types affect string representation.

embedded_stringer.go
package main

import "fmt"

type Point struct {
    X, Y int
}

func (p Point) String() string {
    return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}

type Circle struct {
    Point  // Embedded
    Radius int
}

func (c Circle) String() string {
    return fmt.Sprintf("Circle at %s with radius %d", c.Point, c.Radius)
}

func main() {
    c := Circle{Point{10, 20}, 5}
    fmt.Println(c) // Uses Circle's String() method
}

Circle embeds Point and both implement Stringer. Circle's String() method uses Point's String() method in its output. This creates a clean, hierarchical format.

Stringer with error handling

Stringer implementations can include error handling for invalid states. This example shows robust string formatting.

error_stringer.go
package main

import (
    "fmt"
    "time"
)

type Event struct {
    Name      string
    Timestamp time.Time
}

func (e Event) String() string {
    if e.Timestamp.IsZero() {
        return fmt.Sprintf("%s (no time set)", e.Name)
    }
    return fmt.Sprintf("%s at %s", e.Name, e.Timestamp.Format(time.RFC3339))
}

func main() {
    e1 := Event{"Meeting", time.Now()}
    e2 := Event{"Party", time.Time{}} // Zero time
    
    fmt.Println(e1)
    fmt.Println(e2)
}

The Event type's String() method checks for zero time values. It provides different output formats based on the object's state. This makes output more informative.

Source

Go fmt.Stringer documentation

This tutorial covered the fmt.Stringer interface in Go with practical examples of custom string representation for various 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.