Go String(): Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
No edit summary
 
(17 intermediate revisions by the same user not shown)
Line 1: Line 1:
=External=
* https://pkg.go.dev/fmt#Stringer
=Internal=
=Internal=
* [[Go_Language#Control_Default_Format_for_Type_Values_with_String()|Go Language]]
* [[Go_Language#Control_Default_Format_for_Type_Values_with_String()|Go Language]]
Line 5: Line 8:


=Overview=
=Overview=
Java developers are used to declare a <code>public String toString() {...}</code> method in their class to get instances of that class rendered as custom Strings. Go has a similar facility, the <code>String() string</code> method.


The default format of values of a custom type can be implemented by defining a method:
The default format of values of a custom type can be controlled by defining a method:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
func String() string
func String() string
</syntaxhighlight>
</syntaxhighlight>
and associate it with the type, by declaring the type as the method receiver.
and associating it with the type, by declaring the type as the method's receiver. It is said that the method defines the "native" format for that type.


The <code>String()</code> method's intention is to read the receiver's state and render it, never to modify it. In consequence, the <code>String()</code> should declare a [[Go_Methods#Deciding_between_Value_or_Pointer_Receiver|value receiver]].
<syntaxhighlight lang='go'>
func (t SomeType) String() string {
  return fmt.Stringf("...", "...")
}
</syntaxhighlight>


Thanks to compiler's facilities, <code>String()</code> can be invoked with both values and pointers:


<syntaxhighlight lang='go'>
type SomeType struct {
s string
}


func (s SomeType) String() string {
return "<" + s.s + ">"
}




------
...
t := SomeType{s: "A"}


<syntaxhighlight lang='go'>
fmt.Printf("%v\n", t)  // prints <A>
type Stringer interface {
fmt.Printf("%v\n", &t) // prints <A>
  String() string
fmt.Printf("%s\n", t) // prints <A>
}
fmt.Printf("%s\n", &t) // prints <A>
</syntaxhighlight>
</syntaxhighlight>
<code>Stringer</code> is implemented by any type that has a <code>String()</code> method, which defines the "native" format for that value. The <code>String()</code> method is used to print values passed as an operand to any format that accepts a string or to an unformatted printer such as <code>Print</code>.


Java developers are used to declare a <code>public String toString() {...}</code> method in their class to get instances of that class rendered as custom Strings.
Since we use a value receiver, the implementation does not have to protect against the receiver being <code>nil</code>.


Go has a similar facility. It consists in making the type that needs this behavior implement the <code>[[Go_Package_fmt#fmt.Stringer|fmt.Stringer]]</code> interface, which has only one <code>String()</code> method that renders the "native" string representation for the instances of that type. The <code>print()</code> or <code>fmt.*Print*</code> library code will use the method if found on the instance to be rendered as string.
<code>fmt</code> package printing functions automatically invoke the <code>String()</code> method, if defined, on instances of the type when the "[[Go_Package_fmt#%s|%s]]" and "[[Go_Package_fmt#%v|%v]]" conversion characters are used.


Note that is important whether we declare a [[Go_Language_Object_Oriented_Programming#Value_Receiver_Type|value]] or a [[Go_Language_Object_Oriented_Programming#Pointer_Receiver_Type|pointer receiver type]]. Depending on how the receiver type is declared, we will have to pass to the rendering code either a value, or a pointer to the instance of the type to achieve the desired behavior.
=The <tt>fmt.Stringer</tt> Interface=
{{External|https://pkg.go.dev/fmt#Stringer}}
 
The <code>fmt.Stringer</code> interface declares <code>String() string</code> as its only method:


=Example=
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
type SomeType struct {
type Stringer interface {
ID string
String() string
}
}
</syntaxhighlight>


// String makes SomeType implement the fmt.Stringer interface. It is important whether a value or a pointer receiver type is declared.
<code>fmt.Stringer</code> is implemented by any type that has a <code>String()</code> method, The <code>String()</code> method is used to print values passed as an operand to any format that accepts a string or to an unformatted printer such as <code>Print</code>.
func (e SomeType) String() string {
=Recurrence Warning=
return fmt.Sprintf("SomeType[%s]", e.ID)
Do not write a <code>String()</code> method that calls <code>Sprintf()</code> in way that will recur into your own <code>String()</code> method indefinitely. This can happen if the <code>Sprintf()</code> call attempts to print the receiver directly as a string with "%v" or "%s", which in turn will invoke the <code>String()</code> method again:
 
<syntaxhighlight lang='go'>
// Don't do this:
func (s SomeType) String() string {
    return fmt.Sprintf("SomeType<%v>", s)
}
}
</syntaxhighlight>
IDEs may detect this anti-pattern via static checking and warn that "Placeholder argument causes a recursive call to the 'String' method (%v)".


...
A solution is to convert the argument to the basic string type with <code>string(s)</code>, which does not have this method.
 
t := SomeType{ID: "001"}
fmt.Println(t)
</syntaxhighlight>
will render:
<font size=-2>
SomeType[001]
</font>

Latest revision as of 01:35, 31 August 2024

External

Internal

Overview

Java developers are used to declare a public String toString() {...} method in their class to get instances of that class rendered as custom Strings. Go has a similar facility, the String() string method.

The default format of values of a custom type can be controlled by defining a method:

func String() string

and associating it with the type, by declaring the type as the method's receiver. It is said that the method defines the "native" format for that type.

The String() method's intention is to read the receiver's state and render it, never to modify it. In consequence, the String() should declare a value receiver.

func (t SomeType) String() string {
   return fmt.Stringf("...", "...")
}

Thanks to compiler's facilities, String() can be invoked with both values and pointers:

type SomeType struct {
	s string
}

func (s SomeType) String() string {
	return "<" + s.s + ">"
}


...
t := SomeType{s: "A"}

fmt.Printf("%v\n", t)  // prints <A>
fmt.Printf("%v\n", &t) // prints <A>
fmt.Printf("%s\n", t)  // prints <A>
fmt.Printf("%s\n", &t) // prints <A>

Since we use a value receiver, the implementation does not have to protect against the receiver being nil.

fmt package printing functions automatically invoke the String() method, if defined, on instances of the type when the "%s" and "%v" conversion characters are used.

The fmt.Stringer Interface

https://pkg.go.dev/fmt#Stringer

The fmt.Stringer interface declares String() string as its only method:

type Stringer interface {
	String() string
}

fmt.Stringer is implemented by any type that has a String() method, The String() method is used to print values passed as an operand to any format that accepts a string or to an unformatted printer such as Print.

Recurrence Warning

Do not write a String() method that calls Sprintf() in way that will recur into your own String() method indefinitely. This can happen if the Sprintf() call attempts to print the receiver directly as a string with "%v" or "%s", which in turn will invoke the String() method again:

// Don't do this:
func (s SomeType) String() string {
     return fmt.Sprintf("SomeType<%v>", s)
}

IDEs may detect this anti-pattern via static checking and warn that "Placeholder argument causes a recursive call to the 'String' method (%v)".

A solution is to convert the argument to the basic string type with string(s), which does not have this method.