技術メモ

技術メモ

ラフなメモ

GoでポインタのString()をしたときの興味深い結果

はじめに

標準出力する際に fmt.Print を用いることがあると思います。ポインタを fmt.Println に渡すと興味深い結果が表示されることがあります。その例を紹介します。

まずは以下の実装例を見てください。単に fmt.Println で time.Now() の変数とそのポインタ型の変数を標準出力しているだけです。

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    printTime(&t)
}

func printTime(t *time.Time) {
    fmt.Println(t)
    fmt.Println(*t)
}
  • 出力結果

上記の実装の出力結果は以下のようになります。ポインタの値を表示される想定でしたが、実際に表示されたのは time.Time の値でした。なぜでしょう。

2009-11-10 23:00:00 +0000 UTC m=+0.000000001
2009-11-10 23:00:00 +0000 UTC m=+0.000000001

https://play.golang.org/p/8_kqljv7LVL

理由

fmtが表示するもの

まず fmt.Println が何を表示するのか確認します。Package fmt によると %T%p を用いる場合を除いて、指定の順番で表示するようになっています。 fmt.Println に効いてくるのは 5 番目の内容です。

  1. 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).

String() メソッドを実装している場合は、オブジェクトを文字列に変換するために String() が呼ばれます。time.Time は以下のように String() メソッドを実装していました。そのため fmt.Print() で標準出力するときは String() メソッドの返り値の文字列が表示されます。

func (t Time) String() string {
    s := t.Format("2006-01-02 15:04:05.999999999 -0700 MST")

    // Format monotonic clock reading as m=±ddd.nnnnnnnnn.
    if t.wall&hasMonotonic != 0 {
        m2 := uint64(t.ext)
        sign := byte('+')
        if t.ext < 0 {
            sign = '-'
            m2 = -m2
        }
        m1, m2 := m2/1e9, m2%1e9
        m0, m1 := m1/1e9, m1%1e9
        var buf []byte
        buf = append(buf, " m="...)
        buf = append(buf, sign)
        wid := 0
        if m0 != 0 {
            buf = appendInt(buf, int(m0), 0)
            wid = 9
        }
        buf = appendInt(buf, int(m1), wid)
        buf = append(buf, '.')
        buf = appendInt(buf, int(m2), 9)
        s += string(buf)
    }
    return s
}

ポインタのメソッドセット

time.Time は String() メソッドを保持しているので文字列を表示するのは分かりました。ではなぜ *time.Time が String() メソッドを保持しているのでしょうか?

Method sets から引用すると、以下が理由になります。

The method set of the corresponding pointer type T is the set of all methods declared with receiver T or T (that is, it also contains the method set of T).

つまり T 型のメソッドセットは T 型と T 型のメソッドセットを保持します。

よって今回の例では *time.Time 型の変数は time.Time 型で実装していた String() を保持しているので String() が返却する文字列を表示することができました。

参考