使用值或指针接收器实现Stringer接口[重复]

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())
}