gostructmethods

Pointer receiver methods do not get called interface variables holding values


type User struct {
    name string
    age  int
}

func (u *User) String() string {
    return fmt.Sprintf("name = %s, age = %d", u.name, u.age)
}

func main() {

    u1 := User{
        name: "old",
        age:  12,
    }
    fmt.Println(u1)
}

In above code fmt.Println's receiver is of type: any (empty interface).

Assumption :

Pointer receiver methods can't be called on value holding interfaces, as any change made inside method won't be reflected in original struct but only on copy that interface holds.

But this code worked without any compile/run-time error with below result.

Result :

{old 12}

It seems that fmt is not calling String method at all. Is this expected behaviour ?


Solution

  • Your pointer String method is not being called.

    {old 12} is the default output for a struct. See fmt Printing...

    struct:             {field0 field1 ...}
    

    That is expected behavior, pointer methods will not be called on values. It's not because of interfaces, it's to avoid a common mistake. Effective Go explains...

    This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded. The language therefore disallows this mistake.

    String does not modify the receiver, so it should be a value method. func (u User) String() string.


    Perhaps confusingly, fmt.Println(u1.String()) will work. This is because Go has quietly changed that to fmt.Println((&u1).String()) for your convenience. From Effective Go: Methods, Pointers vs. Values...

    There is a handy exception, though. When the value is addressable, the language takes care of the common case of invoking a pointer method on a value by inserting the address operator automatically.


    But shouldn't fmt will try to call pointer method internally while printing?

    No. fmt.Println(u1) receives a copy of u1, here is a demonstration. If it tried to call the pointer method like (&u1_copy).String() it would point at the copy. Any changes made to the User inside String would be to the copy and quietly lost. This is why Go won't call pointer methods on values.