Skip to Content
Course content

109: Custom Stringer Interface

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're at a massive tech conference. You're surrounded by hundreds of people. Now, if I asked you to "describe" the person standing next to you, you wouldn't start by listing their blood type, their exact height in millimeters, and the date of their last dental cleaning. That would be a nightmare to listen to, and it's far too much raw data.

Instead, you'd probably look at their nametag. The nametag is a curated, simplified representation of that complex human being—usually just their name and their company. The person is the "object," but the nametag is the "Stringer" interface. It tells the world: "Whenever you need a text representation of me, just use this specific format."

In Go, when you print a struct using fmt.Println, the language does its best to show you the raw data inside. It's like reading a medical chart instead of a nametag. If you have a User struct, you get something like {John Doe john@example.com 12345}. It's functional, but it's ugly and not helpful for logs or user-facing output.

Telling Go how to describe your types

To fix this, we use the fmt.Stringer interface. It is one of the most common interfaces in the Go standard library, and it's incredibly simple. It only requires one method: String() string.

Let's say we're building a system to monitor servers. Without a custom Stringer, our server logs look like a mess of brackets. Here is how I'd handle that:

package main

import (
	"fmt"
)

type Server struct {
	Hostname string
	IP       string
	Status   int // 0: Down, 1: Up
}

// This is where the magic happens. By adding this method, 
// Server now implements the fmt.Stringer interface.
func (s Server) String() string {
	statusText := "Down"
	if s.Status == 1 {
		statusText = "Up"
	}
	return fmt.Sprintf("Server [%s] (%s) is currently %s", s.Hostname, s.IP, statusText)
}

func main() {
	webServer := Server{
		Hostname: "prod-web-01",
		IP:       "10.0.0.1",
		Status:   1,
	}

	// I'm not calling .String() explicitly. 
	// fmt.Println detects that Server implements Stringer and calls it for me.
	fmt.Println(webServer) 
}

Now, instead of {prod-web-01 10.0.0.1 1}, you get Server [prod-web-01] (10.0.0.1) is currently Up. It's clean, it's readable, and it's decoupled from the logic of your main program.

The infinite loop trap

I've seen a lot of developers trip over this one, so listen closely: never call a printing function that triggers the Stringer method inside the String() method itself.

If you do something like return fmt.Sprintf("%v", s) inside the String() method of s, you've created an infinite recursive loop. fmt.Sprintf sees that your type has a String() method and calls it... which calls fmt.Sprintf... which calls String()... until your program crashes with a stack overflow. If you need to print the underlying fields of the struct within the method, be explicit about the fields or cast the object to a different type.

When to actually use this

I don't think every single struct needs a String() method. If you're just passing data around internally, the default Go formatting is fine. But use the Stringer interface when:

  • You're creating a custom type (like an enum) where the integer value means nothing to a human.
  • You're writing a library and want to provide a clean way for users to log your objects.
  • You find yourself manually formatting the same struct into a string in five different places in your code.



📋 Practical Task

Formatting a Currency Display

You are building a financial application. Currently, the Money struct prints in a raw format that is confusing to users. Your task is to implement the fmt.Stringer interface to make the output professional.

Requirements:

  • Create a struct named Money with two fields: Amount (float64) and Currency (string).
  • Implement the String() method so that when a Money object is printed, it displays as: "[Currency] [Amount]" (e.g., "USD 100.50").
  • Ensure the amount is formatted to exactly two decimal places.
  • In your main function, create an instance of Money and print it using fmt.Println to verify that your custom stringer is being called automatically.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.