チュートリアルの翻訳、9回目です。
前回までの訳はチュートリアル[日本語訳]にまとめてありますのでごらんください。


文字出力

これまでは文字列のフォーマット出力は、あまり使ってきませんでした。
このセクションではGo言語の文字列フォーマットI/Oがどのように動いているか解説します。

いままでのサンプルコードの中では簡単な使い方しかしていなかったfmtパッケージですが、このパッケージにはPrintf, Fprintfなどの関数が実装されています。fmtパッケージ内ではPrintfが次のシグネチャーで宣言されています。

    Printf(format string, v ...) (n int, errno os.Error)

この…は可変引数リストです。C言語ではstdarg.hのマクロを使ってハンドリングしますが、Go言語では空インタフェース(interface {})変数として引き渡され、それをリフレクションライブラリを使って処理します。
話がそれますが、Printfはリフレクションを使用してパラメータの型を動的に判断しています。リフレクションを使ってみるとGo言語のPrintfの素晴らしさがお分かりいただけると思います。

たとえば、C言語ではフォーマットと受け取ったパラメータとの型が一致している必要がありますが、Go言語ではほとんどの場合もっと簡単です。
フォーマット%lludの代わりに%dとだけ記述すれば、Printfは整数のサイズ・符号を判断して一番適したフォーマットで出力します。

下は簡単な例です。 

10        var u64 uint64 = 1<<64-1;
11        fmt.Printf("%d %d\n", u64, int64(u64));

その出力です。

    18446744073709551615 -1

もしフォーマットについて考えるのが面倒なら%vを使ってください。どんな値(配列や構造体でさえ)であってもそれに最適な書式で出力します。次はその例です。

14        type T struct { a int; b string };
15        t := T{77, "Sunset Strip"};
16        a := []int{1, 2, 3, 4};
17        fmt.Printf("%v %v %v\n", u64, t, a);

その出力です。

    18446744073709551615 {77 Sunset Strip} [1 2 3 4]

もしPrintfの代わりにPrintまたはPrintlnを使用するのであれば、フォーマットの指定は全く不要です。これらのルーチンは完全自動でフォーマットを行います。
Print関数はパラメータで指定された値を%vフォーマットを使ったときと同じように出力します。一方Println関数はパラメータの間にスペースを、そして最後に改行コードを挿入します。
下の2行の出力内容は、前回のPrintfの呼び出しと同じ結果になります。

18        fmt.Print(u64, " ", t, " ", a, "\n");
19        fmt.Println(u64, t, a);

自分で作った型をPrintfまたはPrintでフォーマットしたいときは、文字列を返すString()メソッドを実装してください。
printルーチンは、出力しようとする値がString()メソッドを実装しているか調べ、もし実装しているのであればそれを使います。

ここに分かりやすい例があります。

09    type testType struct { a int; b string }

11    func (t *testType) String() string {
12        return fmt.Sprint(t.a) + " " + t.b
13    }

15    func main() {
16        t := &testType{77, "Sunset Strip"};
17        fmt.Println(t)
18    }

*testTypeはString()メソッドを持っているので、この型のデフォルトのフォーマット処理としてString()メソッドが出力時に使われます。

    77 Sunset Strip

このString()メソッド内でフォーマットのためにSprint関数を呼び出しているように、自作のフォーマット処理から他のフォーマット処理を呼び出すこともできます。

Printfのもう一つの特徴として、フォーマット%Tは値そのものではなく型情報を文字列に変換します。これはポリモーフィックなコードをデバッグするときに役立ちます。

フラグや精度情報などを持った完全自作のフォーマット処理を記述することは可能ですが、このチュートリアルの範囲からはずれているためここでは説明しません。

ある型がString()メソッドを実装しているかどうかをPrintfがどのように判断しているかというと、値が『String()メソッドを実装したインタフェース』に変換可能かを調べることで判定しています。

仮に変数vがあったとして、次のように判断しています。

    type Stringer interface {
        String() string
    }

 

    s, ok := v.(Stringer);  // vが"String()"を実装しているか調べる
    if ok {
        result = s.String()
    } else {
        result = defaultOutput(v)
    }

このコードでは、型アサーション(type assertion)を使用し、v変数の値がStringerインタフェースを実装しているか調べています。v.(Stringer)の箇所が型アサーションです。型アサーションに成功すると、指定したインタフェース型に変換されたインスタンスがs変数に設定され、ok変数にはtrueが設定されます。その後、変換されたs変数を使用してメソッドにアクセスします。
(「カンマ+ok」という書き方は、型変換、マップ更新、通信処理において、処理が成功したかどうかを確認するときによく使われるGo言語のイディオムです。ですがチュートリアル内で使用しているのはここだけです)

値がインタフェースを実装していないときは、ok変数にはfalseが設定されます。

このStringerのコードのように、インタフェースに名前をつける際、メソッド名にer( またはr)をつけるというのがGo言語の習わしです。

最後の話題です。Printf系、Sprintf系がでてきましたが、当然Fprintf系もあります。C言語と異なりFprintfの第1パラメータはファイルではなく、ioパッケージで定義されているio.Writerインタフェースです。

    type Writer interface {
        Write(p []byte) (n int, err os.Error);
    }

このインタフェースは前出の習わしに従った名前です。WriteメソッドからWriterインタフェースと名づけられています。
io.Reader、io.ReadWriterの名前も同じルールで名づけられています。

このように、ファイルだけでなく、ネットワークチャネル、バッファなど標準Writeメソッドを実装していればどんな型を使っても、Fprintfを呼び出すとができます。