This question already has an answer here:
I tried to implement the Stringer
interface on my type as follow:
package main
import (
"fmt"
)
type IPAddr [4]byte
// TODO: Add a "String() string" method to IPAddr.
func (o IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", o[0], o[1], o[2], o[3])
}
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v
", name, ip)
fmt.Printf("%v
", ip.String())
}
}
In the code above, I used a value receiver to implement the String()
method. The Printf
recognised my implementation and called the correct String
function on my type.
Output:
googleDNS: 8.8.8.8
8.8.8.8
loopback: 127.0.0.1
127.0.0.1
Then I updated my code to use pointer receiver:
func (o *IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", o[0], o[1], o[2], o[3])
}
The output of the updated code:
loopback: [127 0 0 1]
127.0.0.1
googleDNS: [8 8 8 8]
8.8.8.8
The Printf
method didn't call my String
method anymore. The output told me that Printf
use the default String
method of the type. However, when I called ip.String()
, my method was used.
Can someone explain to me this behaviour, please? As far as I know, we can implement methods of interfaces by both value and pointer receivers.
Thank you.
</div>
The %v
conversion specifier will read any method satisfying the Stringer
interface. For this to happen, however, that method must exist in the method set of the value.
For a value of type T
, its method set contains any methods that receive a value of that type T
:
func (t T) Foo() // in the method set of T
func (t *T) Bar() // not in the method set of T
For a pointer of type *T
, its method set contains both methods that receive a value of type T
and a pointer of type *T
:
func (t T) Foo() // in the method set of *T
func (t *T) Bar() // in the method set of *T
In main
, you have a value identified as ip
with type IPAddr
, so the first set of commented code above applies.
Your first example will work because the method receiver of the String
method has type IPAddr
.
In the second example, the method receiver of the String
method has type *IPAddr
, which means it's not in the method set of ip
, which has type IPAddr
.
In summary:
| String() Method | fmt.Print, fmt.Printf, etc.
Input Type | Receiver | calls String() implicitly
========== | =============== | ===========================
*IPAddr | IPAddr | Yes
| *IPAddr | Yes
---------- + --------------- + ---------------------------
IPAddr | IPAddr | Yes
| *IPAddr | No
You might be wondering why this occurs. It turns out that some values might not be addressable, so a method receiver with type *IPAddr
can't receive a value that has no address. For example, try executing IPAddr{}.String()
with an *IPAddr
method receiver. It will fail to compile because a literal value has no address. If you instead used (&IPAddr{}).String()
, it would work because now you have a pointer *IPAddr
created using &IPAddr{}
, and if you used a non-pointer receiver IPAddr
, then it would work whether the IPAddr
was addressable or not.
The thing is that your map contains type IPAddr
which does not have a String()
function, only *IPAddr
does. This means that you do not pass a Stringer
interface to the print function, so it uses default printing.
One peculiarity in Go is that you can still do the following:
var ip IPAddr
ip.String()
because in this case Go is smart enough to know that it can call the String()
function on the address of the variable. Go can automagically take the address of a variable for calling a function on it.
On the other hand, you would not even be allowed to call String()
on an IPAddr
contained in a map because getting something out of a map
with []
returns a copy that is not addressable. Here is an example to illustrate these properties:
package main
import "fmt"
type IPAddr [4]byte
func (o *IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", o[0], o[1], o[2], o[3])
}
func main() {
var ip = IPAddr{1, 2, 3, 4}
// printing the value does not call String() because we pass type IPAddr,
// not type *IPAddr
fmt.Printf("%v
", ip)
// but we can call String() on the variable because Go knows how to get its
// address
fmt.Println(ip.String())
m := map[int]IPAddr{1: IPAddr{1, 2, 3, 4}}
fmt.Println(m[1])
// the following is a compile-time error because Go cannot take the address
// of things in the map, because the []-operator returns only a copy of the
// IPAddr
//fmt.Println(m[1].String())
}