From 45a3b3714ff78fe1c81a5b3680822859a9fa35ff Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Wed, 6 Mar 2013 15:47:49 -0800 Subject: [PATCH] doc/effective_go.html: unify and expand the discussion of Sprintf and String It's a common mistake to build a recursive String method; explain it well and show how to avoid it. R=golang-dev, bradfitz, adg CC=golang-dev https://golang.org/cl/7486049 --- doc/effective_go.html | 59 +++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/doc/effective_go.html b/doc/effective_go.html index 033d836768..0986e53849 100644 --- a/doc/effective_go.html +++ b/doc/effective_go.html @@ -1710,10 +1710,45 @@ the receiver for String must be of value type; this example used a that's more efficient and idiomatic for struct types. See the section below on pointers vs. value receivers for more information.)

+

Our String method is able to call Sprintf because the -print routines are fully reentrant and can be used recursively. -We can even go one step further and pass a print routine's arguments directly to another such routine. +print routines are fully reentrant and can be wrapped this way. +There is one important detail to understand about this approach, +however: don't construct a String method by calling +Sprintf in a way that will recur into your String +method indefinitely. This can happen if the Sprintf +call attempts to print the receiver directly as a string, which in +turn will invoke the method again. It's a common and easy mistake +to make, as this example shows. +

+ +
+type MyString string
+
+func (m MyString) String() string {
+    return fmt.Sprintf("MyString=%s", m) // Error: will recur forever.
+}
+
+ +

+It's also easy to fix: convert the argument to the basic string type, which does not have the +method. +

+ +
+type MyString string
+func (m MyString) String() string {
+    return fmt.Sprintf("MyString=%s", string(m)) // OK: note conversion.
+}
+
+ +

+In the initialization section we'll see another technique that avoids this recursion. +

+ +

+Another printing technique is to pass a print routine's arguments directly to another such routine. The signature of Printf uses the type ...interface{} for its final argument to specify that an arbitrary number of parameters (of arbitrary type) can appear after the format. @@ -1857,13 +1892,13 @@ while ByteSize(1e13) prints as 9.09TB.

-Note that it's fine to call Sprintf and friends in the -implementation of String methods, but beware of -recurring into the String method through the nested -Sprintf call using a string format -(%s, %q, %v, %x or %X). -The ByteSize implementation of String is safe -because it calls Sprintf with %f. +The use here of Sprintf +to implement ByteSize's String method is safe +(avoids recurring indefinitely) not because of a conversion but +because it calls Sprintf with %f, +which is not a string format: Sprintf will only call +the String method when it wants a string, and %f +wants a floating-point value.

Variables

@@ -2022,10 +2057,8 @@ func (s Sequence) String() string { }

-The conversion causes s to be treated as an ordinary slice -and therefore receive the default formatting. -Without the conversion, Sprint would find the -String method of Sequence and recur indefinitely. +This method is another example of the conversion technique for calling +Sprintf safely from a String method. Because the two types (Sequence and []int) are the same if we ignore the type name, it's legal to convert between them. The conversion doesn't create a new value, it just temporarily acts -- 2.48.1