The package fmt defines GoStringer interface, and I think it doesn’t have recognition it deserves. According to the documentation:

GoStringer is implemented by any value that has a GoString method, which defines the Go syntax for that value. The GoString method is used to print values passed as an operand to a %#v format.

That means that you can implement GoString() string method on any of your types, and it will be called when the object of this type is formatted using %#v. And the purpose of this is to return a representation of the object in Go-syntax.

Example

The errors.New returns an error. But since error is just an interface, it, in fact, returns a private struct that satisfies the interface. And indeed, if we print the result with the %#v flag, we’ll see this struct, including all private fields:

func main() {
    e := errors.New("oh no")
    fmt.Printf("%#v", e)
    // Output: &errors.errorString{s:"oh no"}
}

And this is the relevant source code of the package:

func New(text string) error {
    return &errorString{text}
}

type errorString struct{ s string }

func (e *errorString) Error() string {
    return e.s
}

We can make it better. Let’s copy-paste this source code and add to the struct one more method:

func (e *errorString) GoString() string {
    return fmt.Sprintf(`errors.New(%#v)`, e.s)
}

And if we print it now, we’ll see a nice and clean output of our new method:

func main() {
    e := New("oh no")
    fmt.Printf("%#v", e)
    // Output: errors.New("oh no")
}

Why

The idea isn’t new. For instance, Python has a repr function the output of which can be customized by adding __repr__ method to a class. The only difference is that Python stdlib actively uses this method to make the output friendly. For example:

import datetime
d = datetime.date(1977, 12, 15)
print(repr(d))
# Output: datetime.date(1977, 12, 15)

It gives several benefits:

  • It hides internal implementation from the user.
  • It looks cleaner.
  • It allows the user to copy-paste the output and (assuming all required imports are in place) get the object created. It can be helpful, for instance, to hardcode values for tests.

Go built-ins

Built-in types give good enough output:

m := map[string]int{"hello": 42}
fmt.Printf("%#v", m)
// map[string]int{"hello":42}

b := []byte("hello")
fmt.Printf("%#v", b)
// []byte{0x68, 0x65, 0x6c, 0x6c, 0x6f}

And if you want a bit nicer output, have a look at dd package which is designed exactly for printing structs and built-in types in a nice Go syntax:

fmt.Println(dd.Dump(m))
// map[string]int{
//   "hello": 42,
// }

Go stdlib

The stdlib does use it in a few places but most of the time it doesn’t. We’ve already seen the output of errors.New. Let’s see some more examples.

time.Date:

v := time.Unix(0, 0)
fmt.Printf("%#v", v)
// time.Date(1970, time.January, 1, 1, 0, 0, 0, time.Local)

fmt.Errorf:

e := errors.New("damn")
v := fmt.Errorf("oh no: %w", e)
fmt.Printf("%#v", v)
// &fmt.wrapError{msg:"oh no: damn", err:(*errors.errorString)(0xc000010250)}

sync.WaitGroup:

v := sync.WaitGroup{}
fmt.Printf("%#v", v)
// sync.WaitGroup{noCopy:sync.noCopy{}, state1:0x0, state2:0x0}

list.New:

v := list.New()
fmt.Printf("%#v", v)
// &list.List{root:list.Element{
// next:(*list.Element)(0xc00007e150),
// prev:(*list.Element)(0xc00007e150),
// list:(*list.List)(nil),
// Value:interface {}(nil)},
// len:0}

PSA

I want you to know that if stdlib doesn’t do something, it doesn’t mean you shouldn’t. GoString doesn’t worth bothering in your internal projects, but if you develop an open-source package, please, spend a few seconds of your life and make this representation of each of your types a little bit more useful.