Subtle change to printing semantics

10 views
Skip to first unread message

Rob 'Commander' Pike

unread,
Jun 14, 2010, 8:33:15 PM6/14/10
to golang-nuts Nuts
I have just submitted CL 1613045. It's not part of the release yet but it will be in the next release. There is a semantic change to Printf and friends caused by this CL that I want to announce proactively.

The CL is a reimplementation of Printf that is cleaner, shorter, and maybe even a little faster in some cases. A consequence of the CL is a change to the handling of the fmt.Stringer interface,

type Stringer interface {
String() string
}

which is used throughout Go code to make types know how to print themselves.

Very few programs will be affected by the change - I found only a couple of tests that were - but the change means that some old working code might now fail by recurring forever at runtime. Please read the rest of this message carefully if you have implementations of the Stringer interface.

In the old code, String() was only used when printing with %s, %q, or %v, or through the non-format-driven routines Print[ln], Sprint[ln], and Fprint[ln]. In the new code, any value that implements String() will always have String() called for any printing operation. The result of that operation will be a string, of course, and that will be printed with the format provided (if any).

This matters for cases such as this, where String calls a version of Sprint:

type X int
func (x X) String() string { return Sprintf("%d", x) }

In the old implementation, String() would not be invoked again in the call to Sprintf because the format is %d, not %s. But in the new code it will be, and this will cause an infinite recursion. Fortunately, the fix is easy: convert x to a type that does not implement String(), such as by converting it to int:

func (x X) String() string { return Sprintf("%d", int(x)) }

Most code is totally unaffected by this because almost any String() method with a *pointer* receiver is immune. That is, only value receivers are likely to recur forever (and of course only if they call Sprint, etc.). This is because the implementation of String with a pointer receiver will likely indirect or unpack the value, causing a type change:

type Y float
func (y *Y) String() string { return Sprintf("%g", *y) } // NOTE: argument to Sprintf is *y not y.

I recommend that you fix your code now and be ready for the next release. Look for implementations of String with value receivers that invoke Sprint, Sprintf, or Sprintln.

-rob

Reply all
Reply to author
Forward
0 new messages