Search code examples
stringpointersgomethodsstack-overflow

The difference between t and *t


package main

import "fmt"

type TT struct {
    a int
    b float32
    c string
}

func (t *TT) String() string {
    return fmt.Sprintf("%+v", *t)
}

func main() {
    tt := &TT{3, 4, "5"}
    fmt.Printf(tt.String())
}

The code can work well. But if I change the String method as in the following, it will cause dead loop. The difference is that the *t is replaced with t. Why?

func (t *TT) String() string {
    return fmt.Sprintf("%+v", t)
}

Solution

  • Because the fmt package checks if the value being printed has a String() string method (or in other words: if it implements the fmt.Stringer interface), and if so, it will be called to get the string representation of the value.

    This is documented in the fmt package doc:

    [...] If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).

    Here:

    return fmt.Sprintf("%+v", *t)
    

    You are passing a value *t of type TT to the fmt package. If the TT.String() method has a pointer receiver, then the method set of the type TT does not include the String() method, so the fmt package will not call it (only the method set of *TT includes it).

    If you change the receiver to non-pointer type, then the method set of the type TT will include the String() method, so the fmt package will call that, but this is the method we're currently in, so that's an endless "indirect recursion".

    Prevention / protection

    If for some reason you do need to use the same receiver type as the type of the value you pass to the fmt package, an easy and common way to avoid this / protect from it is to create a new type with the type keyword, and use type conversion on the value being passed:

    func (t TT) String() string {
        type TT2 TT
        return fmt.Sprintf("%+v", TT2(t))
    }
    

    Try this on the Go Playground.

    But why does this work? Because the type keyword creates a new type, and the new type will have zero methods (it does not "inherit" the methods of the underlying type).

    Does this incur some run-time overhead? No. Quoting from Spec: Type declarations:

    Specific rules apply to (non-constant) conversions between numeric types or to and from a string type. These conversions may change the representation of x and incur a run-time cost. All other conversions only change the type but not the representation of x.

    Read more about this here: Does convertion between alias types in Go create copies?