Skip to content
Go言語とは、Googleが開発した新しいプログラミング言語です。
当サイトではこの新しい言語についての情報を集約していきます。
このサイトの更新が滞っており、情報が古くなっておりますのでご注意ください。

Archive

Archive for 3月, 2010

実践Go言語(Effective Go)の翻訳、10回目です。
前回までの訳は実践Go言語[日本語訳]にまとめてあります。


インタフェースとそれ以外の型

インタフェース

Go言語のインタフェースは、オブジェクトの振舞いを規定する手立てです。このセクションではインタフェースにより実現できることすべてを説明します。今まですでに2、3の簡単な例を見てきました。たとえばカスタム出力はStringメソッドを実装することで作られ、一方FprintfWriteメソッドによって出力先をどこへでも変更できます。Go言語のコードでは、通常インタフェースは1~2個のメソッドしか持たず、またWriteメソッドを持つio.Writerのようにたいていメソッド名と関連した名前が付けられます。

型には複数のインタフェースを実装することができます。たとえばあるコレクション型がLen()Less(i, j int) boolSwap(i, j int)メソッドを持つsort.Interfaceを実装していればsortパッケージのルーチンを使ってソートが可能になり、その上さらに独自の出力メソッドを実装することもできます。下の例のSequence型は、少々不自然ですがこれらをすべて実装しています。

type Sequence []int

// sort.Interfaceに必要な全メソッドを実装
func (s Sequence) Len() int {
    return len(s)
}
func (s Sequence) Less(i, j int) bool {
    return s[i] < s[j]
}
func (s Sequence) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

// 出力用メソッド - 出力前に要素を並び替え
func (s Sequence) String() string {
    sort.Sort(s)
    str := "["
    for i, elem := range s {
        if i > 0 {
            str += " "
        }
        str += fmt.Sprint(elem)
    }
    return str + "]"
}

変換

さきほどのSequenceStringメソッドを変更して、Sprintが本来持っているスライス出力機能を利用するようにしました。Sprintを呼び出す前にSequenceを純粋な[]intに変換することで、既存の処理を利用することが可能になります。

func (s Sequence) String() string {
    sort.Sort(s)
    return fmt.Sprint([]int(s))
}

このとき変換が行われ、sがただのスライスとみなされるため、スライスに規定されている書式で文字列が返されます。ここで変換を行われなければ、SprintSequenceStringメソッドを見つけ出し、呼び出しを無限に繰り返してしまいます。この2つの型(Sequence[]int)は名前を除けば同一であるため、これらの型の間における変換は問題なく行われます。この変換では新しい値が作られることはなく、既存の値が一時的に新しい型を持つかのような働きをします。(これと異なる変換もあります。たとえば整数を浮動小数点へ変換したときは新しく値が作成されます。)

Go言語のプログラムでは、異なるメソッド群を使用するために式の型を変換することがよく行われます。例として、既存の型sort.IntArrayを使ってサンプルプログラム全体を下のように軽量化することが可能です。

type Sequence []int

// 出力用メソッド - 出力する前に要素を並び替え
func (s Sequence) String() string {
    sort.IntArray(s).Sort()
    return fmt.Sprint([]int(s))
}

これで、Sequenceに複数のインタフェース(ソートと出力)を実装する代わりに、データを複数の型(Sequencesort.IntArray[]int)に変換できることを利用して各機能を実現できるようになりました。このような使い方をすることは実際にはほとんどありませんが効果的な使い方です。

概説

ある型がインタフェースをひとつだけ実装していて、そのインタフェースのメソッド以外にエクスポートされたメソッドを持たないならば、型自体のエクスポートは不要です。インタフェースだけをエクスポートすることで、次の点を明白にすることができます。ひとつは、重要なのは実装ではなく振舞いであること。もうひとつは、振舞いは同じでも異なる機能を持った別個の実装であることです。また、共通メソッドを実装している各箇所で、その都度ドキュメントを記述する手間が省けるという利点もあります。

このような場合は、コンストラクタは実装している型ではなく、インタフェース値を返さなければなりません。一例として、ハッシュライブラリのcrc32.NewIEEE()adler32.New()では、双方ともhash.Hash32インタフェース型の値を返します。Go言語プログラムでこのハッシュアルゴリズムをAdler-32からCRC-32に変更するために必要なのは、コンストラクタの呼び出しを入れ替えるだけです。それ以外のコードは、ハッシュアルゴリズムの変更による影響を受けません。

同様のアプローチにより、crypto/blockパッケージのストリーム暗号アルゴリズムは、一連のブロック暗号から独立しています。これらは特定の実装を返すのではなく、bufioパッケージにならってCipherインタフェースをラップしたhash.Hashio.Readerio.Writerいずれかのインタフェース値を返します。

下はcrypto/block内のインタフェースです。

type Cipher interface {
    BlockSize() int
    Encrypt(src, dst []byte)
    Decrypt(src, dst []byte)
}

// NewECBDecrypterは、rからデータを読み込み、
// c内の電子コードブック(ECB)モードを使って復号化を行うReaderを返します。
func NewECBDecrypter(c Cipher, r io.Reader) io.Reader

// NewCBCDecrypterは、rからデータを読み込み、
// c内の暗号ブロックチェイニング(CBC)モードと初期化ベクタivを使って
// 復号化を行うReaderを返します。
func NewCBCDecrypter(c Cipher, iv []byte, r io.Reader) io.Reader

NewECBDecrypterおよびNewCBCReaderで扱うことができるのは、特定の暗号化アルゴリズムやデータソースではなく、どのCipherインタフェースの実装であっても、またどのio.Readerでも適用することが可能です。これらはio.Readerインタフェース値を返すため、ECB暗号化をCBC暗号化と入れ替えるときは部分的な変更だけですみます。コンストラクタを呼び出している箇所は変更が必要ですが、その周囲のコードではコンストラクタから返されるのはio.Readerだけとみなしていれば違いに気づくことさえないでしょう。

インタフェースとメソッド

ほぼすべての型に対してメソッドを付け加えることができるので、これはすなわち、どのインタフェースであっても、ほとんどの型に実装可能であると言えます。この実例のひとつが、Handlerインタフェースを定めているhttpパッケージにあります。Handlerを実装しているすべてのオブジェクトで、HTTPリクエストを処理することが可能です。

type Handler interface {
    ServeHTTP(*Conn, *Request)
}

ここでは簡略化のため、POSTは無視してHTTPリクエストが常にGETであると仮定します。 (この簡略化がハンドラの書き方に影響を及ぼすことはありません。) 下は、ページの訪問回数を単に数えるだけのハンドラの実装一式です。

// 単純なカウントサーバ
type Counter struct {
    n int
}

func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) {
    ctr.n++
    fmt.Fprintf(c, "counter = %d\n", ctr.n)
}

(今回のテーマを気にとめつつ、FprintfがどのようにしてHTTP接続を出力するか注意してみてください。)
参考までに、こういったサーバをURLパスに割り当てる手順です。

import "http"
...
ctr := new(Counter)
http.Handle("/counter", ctr)

ところで、なぜCounterを構造体としているのでしょうか。必要なのは整数ひとつだけのはずです。(ただし値が増えたことを呼び出し側にも伝わるように、レシーバはポインタである必要があります。)

// 単純なカウントサーバ
type Counter int

func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) {
    *ctr++
    fmt.Fprintf(c, "counter = %d\n", *ctr)
}

自作プログラムが内部ステータスを持っていて、そこにページが訪問されたことを通知しなければならないとしたらどうすればよいでしょうか。このようなときは、次のようにチャネルとウェブページとを関連付けてください。

// 訪問がある度に通知を送信するチャネル
// (たぶんバッファリングされている必要がある)
type Chan chan *http.Request

func (ch Chan) ServeHTTP(c *http.Conn, req *http.Request) {
    ch <- req
    fmt.Fprint(c, "notification sent")
}

最後に、パス/argsを訪問したときにサーバプログラムを起動した際に与えられた引数を出力するようにしてみましょう。引数の出力関数は下のように簡単に書けます。

func ArgServer() {
    for i, s := range os.Args {
        fmt.Println(s)
    }
}

これをHTTPサーバへ変更していきます。さきほどのArgServer関数を適当な型(値は何でも良い)のメソッドとすることもできますが、それより良い方法があります。メソッドはポインタとインタフェース以外すべての型に定義することができるため、関数に対してメソッドを書くことも可能です。httpパッケージには、下のコードが含まれています。

// HandlerFunc型は、通常の関数をHTTPハンドラとして
// 使用可能にするためのアダプタです。
// fが適切なシグネチャを持つ関数であれば、HandlerFunc(f)は
// fを呼び出すハンドラオブジェクトとなります。
type HandlerFunc func(*Conn, *Request)

// ServeHTTPはf(c, req)を呼び出す
func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {
    f(c, req)
}

HandlerFuncServeHTTPメソッドを持つ型であるため、この型の値はHTTPリクエストを処理できます。メソッドの実装を見てください。このメソッドのレシーバは関数fであり、メソッド内でfを呼び出しています。これは少々風変わりではありますが、前出のチャネルをレシーバとしてそのチャネルへ送信するメソッドと大差ありません。

ArgServerをHTTPサーバにするため、まずは正しいシグネチャを持つように修正します。

// 引数サーバ
func ArgServer(c *http.Conn, req *http.Request) {
    for i, s := range os.Args {
        fmt.Fprintln(c, s)
    }
}

これでArgServerHandlerFuncと同じシグネチャを持つようになったので、以前SequenceIntArray.Sortを使用するためIntArrayに変換したときと同様に、ArgServerHandlerFunc内のメソッドを使用するためにHandlerFunc型に変換可能になりました。このセットアップを行うコードは次のように簡潔に書けます。

http.Handle("/args", http.HandlerFunc(ArgServer))

誰かが/argsページを訪問したときに呼び出されるハンドラは、値はArgServerで型はHandlerFuncとなりました。まず、HTTPサーバによってArgServerをレシーバとしてHandlerFunc型のServeHTTPメソッドが実行され、続いてHandlerFunc.ServeHTTP内のf(c, req)を通してArgServerが呼び出されます。そのあと引数の表示が行われます。

このセクションで、構造体、整数、チャネル、関数を使ってHTTPサーバを作成したのは、インタフェースがまさにメソッド群であり、(ほとんど)すべての型に対して定義できることを示すためです。

実践Go言語(Effective Go)の翻訳、9回目です。
前回までの訳は実践Go言語[日本語訳]にまとめてあります。


メソッド

ポインタ vs. 値

メソッドは、名前がつけられていればポインタとインタフェースを除くすべての型に定義することができます。(レシーバは構造体である必要はありません。)

以前、スライスの説明のところで書いたAppend関数を今度はスライスのメソッドとして定義してみます。これにはまず、メソッドと結びつけるために新たに名前付きの型を宣言し、メソッドにこの型のレシーバを定義します。

type ByteSlice []byte

func (slice ByteSlice) Append(data []byte) []byte {
    // 本体部分は前と全く同じ
}

このままでは、更新したスライスをメソッドから返す必要がまだ残っています。レシーバとしてByteSliceへのポインタを受け取るようメソッドを定義しなおすことによって、その問題を回避できます。こうすれば呼び出し元のスライスに対しメソッド内から上書き可能になります。

func (p *ByteSlice) Append(data []byte) {
    slice := *p
    // 本体部分は上とおなじだが、returnは除外
    *p = slice
}

実のところ、もう少し改善可能です。たとえば、この関数を標準的なWriteメソッドに合わせるように変更すると下のようになります。

func (p *ByteSlice) Write(data []byte) (n int, err os.Error) {
    slice := *p
    // これも上と同じ
    *p = slice
    return len(data), nil
}

このようにすることで*ByteSlice型は、利便性の高い標準インタフェースio.Writerを満たすようになり、下の例のようにスライスに対し出力することが可能となります。

    var b ByteSlice
    fmt.Fprintf(&b, "This hour has %d days\n", 7)

上でByteSliceのアドレスを渡しているのは、*ByteSliceでなければio.Writerインタフェースを満たさないためです。レシーバとしてポインタまたは値のどちらを受け取るかによって、次の違いがあります。レシーバとして値を受け取るメソッドでは、ポインタまたは値で呼び出すことができますが、ポインタを受け取るメソッドでは、ポインタでしか呼び出すことができません。これは後者ではメソッド内でレシーバを変更可能であるためです。(複製された値に加えた変更は破棄されてしまうため。)

ちなみに、Writeをバイトのスライスで使うというアイデアはbytes.Bufferで実装済です。

実践Go言語(Effective Go)の翻訳、8回目です。
前回までの訳は実践Go言語[日本語訳]にまとめてあります。


初期化

初期化において、Go言語とCやC++言語では見かけ上それほど差がないように見えますが、Go言語の初期化はより強力です。複合構造体は初期化を行いながら構築することが可能です。また異なるパッケージ間においてもオブジェクトの初期化順序は正しく取り扱われます。

定数

Go言語における定数は、その名の通り「定数」です。定数はコンパイル時に作成されます。これは定数が関数内でローカルに定義されているときも同様です。ただし定数となり得るのは数値、文字列、論理値だけです。定数はコンパイル時に作成されるという制約上、コンパイラによって評価可能な定数式でなければなりません。たとえば1<<3は定数式ですが、math.Sin(math.Pi/4)は定数式ではありません。これは後者を評価するためにはmath.Sinの呼び出しが必要となるためです。

Go言語での定数の列挙にはiota列挙子を使います。iotaは式の一部となって、かつその式は暗黙的に繰り返すことができるので値の複雑なセットも簡単に作成することができます。

type ByteSize float64
const (
    _ = iota  // 一番目の値はブランク識別子に代入して無視
    KB ByteSize = 1<<(10*iota)
    MB
    GB
    TB
    PB
    EB
    ZB
    YB
)

Stringのようなメソッドを型と結びつけることができるので、型の一部として上のような値を出力用に自前で自動フォーマットすることが可能になります。

func (b ByteSize) String() string {
    switch {
    case b >= YB:
        return fmt.Sprintf("%.2fYB", b/YB)
    case b >= ZB:
        return fmt.Sprintf("%.2fZB", b/ZB)
    case b >= EB:
        return fmt.Sprintf("%.2fEB", b/EB)
    case b >= PB:
        return fmt.Sprintf("%.2fPB", b/PB)
    case b >= TB:
        return fmt.Sprintf("%.2fTB", b/TB)
    case b >= GB:
        return fmt.Sprintf("%.2fGB", b/GB)
    case b >= MB:
        return fmt.Sprintf("%.2fMB", b/MB)
    case b >= KB:
        return fmt.Sprintf("%.2fKB", b/KB)
    }
    return fmt.Sprintf("%.2fB", b)
}

YBの出力は1.00YBになり、ByteSize(1e13)の出力は9.09TBとなります。

変数

変数は、定数と同じように初期化することができますが、変数のイニシャライザは通常の式であり、実行時に評価されます。

var (
    HOME = os.Getenv("HOME")
    USER = os.Getenv("USER")
    GOROOT = os.Getenv("GOROOT")
)

init関数

最後になりますが、各ソースファイルにはそれぞれ必要に応じて、セットアップのためにinit()関数を定義することができます。ひとつだけ制約があり、初期化中にゴルーチンを起動することはできますが、初期化が完了するまで実行は開始しません。すなわち、初期化処理中に実行されるスレッドは常にひとつだけです。

init()関数は、パッケージでインポートしている他のパッケージが初期化されたあと、パッケージ内で宣言されているすべての変数のイニシャライザが評価されたあとに呼び出されます。init()関数の一般的な使い方は、宣言としては記述できないような初期化処理を行うほか、処理の実行開始直前に、プログラムのステートの妥当性チェックおよびステートの修正を行います。

func init() {
    if USER == "" {
        log.Exit("$USER not set")
    }
    if HOME == "" {
        HOME = "/usr/" + USER
    }
    if GOROOT == "" {
        GOROOT = HOME + "/go"
    }
    // GOROOTはコマンドラインから--gorrotフラグを指定することで上書き可能
    flag.StringVar(&GOROOT, "goroot", GOROOT, "Go root directory")
}

実践Go言語(Effective Go)の翻訳、7回目です。
前回までの訳は実践Go言語[日本語訳]にまとめてあります。


データ

new()による割り当て

Go言語の基本的なメモリ割り当てには、new()make()の2つがあります。これら2つはそれぞれ異なる働きをし、適用先の型も別となります。混乱させてしまうかもしれませんがルールは単純です。

まずnew()について説明します。これは組み込み関数であり、他の言語におけるnew()と基本的に同じです。new(T)は、型Tの新しいアイテム用にゼロ化した領域を割り当て、そのアドレスである*T型の値を返します。Go言語風に言い換えると、new(T)が返す値は、新しく割り当てられた型Tのゼロ値のポインタです。

new()から返されるメモリはゼロ化されています。ゼロ化済みオブジェクトは、さらなる初期化を行わなくても使用できるため、こういったオブジェクトの準備にnew()は便利です。すなわち、データ構造体の利用者がnew()でそれを作成すると、すぐに使える状態となります。たとえば、bytes.Bufferのドキュメントには、「Bufferのゼロ値は、利用準備が整った空のバッファである。」と記述されています。同様にsync.Mutexには明示的なコンストラクタやInitメソッドは用意されていませんが、その代わりにsync.Mutexのゼロ値は、非ロック状態のミューテックスであることが定められています。

この便利なゼロ値は連鎖的に働きます。下の型宣言をみてください。

type SyncedBuffer struct {
    lock    sync.Mutex
    buffer  bytes.Buffer
}

このSyncedBuffer型の値もまた、割り当てや宣言を行うと同時に使用準備が整います。下のコードの変数pvは、このままで正しく機能します。

p := new(SyncedBuffer)  // type *SyncedBuffer
var v SyncedBuffer      // type  SyncedBuffer

コンストラクタと複合リテラル

下の例はosパッケージからの抜粋です。この例のようにゼロ値では充分でなく、コンストラクタによる初期化が必要となることがあります。

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := new(File)
    f.fd = fd
    f.name = name
    f.dirinfo = nil
    f.nepipe = 0
    return f
}

上のコードには冗長な部分が多く見られます。複合リテラルとは、実行する度に新しいインスタンスを生成する式であり、これを使うことで下のコードのように単純化することができます。

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := File{fd, name, nil, 0}
    return &f
}

このようにローカル変数のアドレスを返しても問題ありません。関数から戻ったあとも、変数に割り当てたメモリは生き残ります。複合リテラルのアドレスを取得したときは、実行する度に新しいインスタンスが割り当てられる仕様なので、最後の2行を次のようにまとめることができます。

    return &File{fd, name, nil, 0}

複合リテラルでは、すべてのフィールドを順番通りに記述しなければなりません。ただし明示的に「フィールド:値」の組み合わせで要素にラベルをつけたときは、イニシャライザは順序通りである必要はなく、また指定しなかった要素には、その型のゼロ値がセットされます。すなわち次のように書き換えられます。

    return &File{fd: fd, name: name}

特殊なケースとして、複合リテラルがひとつもフィールドを含まないときは、その型のゼロ値が作られます。すなわち、式new(File)&File{}は等価です。

また複合リテラルでは、フィールドのラベルをインデックスまたはマップのキーとみなして、配列、スライス、マップを作成することもできます。次の例において、EnoneEioEinvalがそれぞれ個別の変数でありさえすれば、その値に関わらず初期化は成功します。

a := [...]string   {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
s := []string      {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}

make()による割り当て

割り当てに話を戻します。組み込み関数make(T, args)は、new(T)とは使用目的が異なります。makeで割り当てできるのはスライス、マップ、チャネルだけであり、初期化された、すなわちゼロ値でないT型(*Tでない)の値を返します。makenewを使い分ける理由は、これらの3つの型が隠蔽されたデータ構造への参照であり、このデータ構造が使用前に初期化されている必要があるためです。スライスを例にとると、スライスはデータ(配列内)へのポインタ、長さ、キャパシティという3つの情報から構成されており、それらの情報が初期化されるまではスライスの値はnilです。makeはスライス、マップ、チャネルの内部データ構造を初期化し、使用可能となるよう値を準備します。下は、makeの例です。

make([]int, 10, 100)

この例では、100個のintを持つ配列を割り当てたあと、その配列の先頭から10個目までの要素を示す、長さが10でキャパシティ100のスライス構造を作成します。(スライス作成時、キャパシティは省略可能です。詳細はスライスに関するセクションを参照ください。)これに対して、new([]int)は新しくメモリを割り当て、ゼロ化したスライス構造のポインタを返します。つまりこれはnilスライス値へのポインタです。

下は、new()make()の違いを例示したものです。

var p *[]int = new([]int)       // スライス構造の割り当て(*p == nil)。あまり使わない。
var v  []int = make([]int, 100) // vは100個のintを持つ配列への参照

// 必要以上に複雑な書き方
var p *[]int = new([]int)
*p = make([]int, 100, 100)

// 一般的な書き方
v := make([]int, 100)

覚えておいていただきたいことは、make()が適用可能なのはマップ、スライス、チャネルだけであり、返されるのはポインタではないことです。ポインタが必要であればnew()で割り当ててください。

配列

配列はメモリ配置を厳密に指定したいときや、ときにはメモリ割り当てを回避したいときに役立ちますが、配列の主な役割は、次セクションの主題であるスライスから参照されることです。そのスライスについて説明する前に基礎知識として、配列について2、3説明しておきます。

Go言語とC言語では配列の動作に大きな違いがあります。Go言語では次のように振舞います。

  • 配列は値です。ある配列を他へ代入するとすべての要素がコピーされます。
  • すなわち関数に配列を渡すと、関数側ではポインタではなく、その配列のコピーを受け取ります。
  • 配列のサイズは型の一部です。[10]int[20]intは異なる型です。

値として扱うと有用なこともありますが、高コストでもあるため、C言語のような動作と効率が必要であれば、配列へのポインタを関数に渡すことも可能です。

func Sum(a *[3]float) (sum float) {
    for _, v := range *a {
        sum += v
    }
    return
}

array := [...]float{7.0, 8.5, 9.1}
x := Sum(&array)  // 注:明示的にアドレス演算子を使用

しかし、これはGo言語では一般的ではなく、通常はスライスを使用します。

スライス

スライスは配列をラップして、データ列に対しより普遍的かつ効果的で使い易いインタフェースを提供します。変換行列のような明らかに多次元のデータ構造を扱う場合を除いて、配列を扱うGo言語のプログラムでは、配列そのままではなくスライスが多用されます。

スライスは参照型です。すなわちスライスを別のスライスに代入したときは、双方が同じ配列を参照します。たとえば関数がスライスを引数として取るとき、関数内でスライスの要素に変更を加えると、ポインタ渡しのように関数の呼び元からも変更内容が参照できます。ゆえにRead関数では、引数としてポインタと個数を受け取る代わりに、スライスを受け取ることが可能となります。このときスライスが持つ長さ情報は、読み込むべきデータの上限となります。下は、osパッケージのFile型のReadメソッドのシグネチャです。

func (file *File) Read(buf []byte) (n int, err os.Error)

このメソッドは読み込んだバイト数と、エラーがあればエラー値を返します。ある大きなバッファbufから先頭32バイトを読み込むためには、次のようにバッファをスライスしてください。※この「スライス」という言葉は名詞ではなく動詞です。

    n, err := f.Read(buf[0:32])

こういったスライスの使い方は一般的かつ効率的です。実際のところ、あまり効率を考慮に入れなければ、下のコードでも同様にバッファの先頭32バイトを読み込めます。

    var n int
    var err os.Error
    for i := 0; i < 32; i++ {
        nbytes, e := f.Read(buf[i:i+1])  // 1バイト読み込み
        if nbytes == 0 || e != nil {
            err = e
            break
        }
        n += nbytes
    }

スライスが持つ長さ情報は、その参照先配列の範囲内であれば変更することができます。(試しにスライスを同じスライスに代入し直してみてください。) スライスのキャパシティは組み込み関数のcapで参照できます。キャパシティとはスライスが扱える長さの上限値です。
下はスライスにデータを追加する関数です。この関数ではデータの長さがキャパシティを超えるときは、スライスは再割り当てされ、結果得られたスライスが関数から返されます。nilスライスが関数に与えられたときには、仕様上nilスライスがlencapともに0を返すことを利用しています。

func Append(slice, data[]byte) []byte {
    l := len(slice)
    if l + len(data) > cap(slice) {  // reallocate
        // 再利用を考慮し、必要なサイズの倍、割り付ける
        newSlice := make([]byte, (l+len(data))*2)
        // Copy data (could use bytes.Copy()).
        for i, c := range slice {
            newSlice[i] = c
        }
        slice = newSlice
    }
    slice = slice[0:l+len(data)]
    for i, c := range data {
        slice[l+i] = c
    }
    return slice
}

Appendではsliceの要素を変更していますが、スライス自体(ランタイムデータ構造がポインタ、長さ、キャパシティを保持している)は値渡しされているため、処理を終えたあと関数からスライスを返す必要があります。

マップ

マップは、異なる型の値を結びつける便利で強力な組み込みデータ構造です。マップのキーには、イコール演算子が定められていればどんな型(例えば整数、浮動小数、文字列、ポインタ、動的な型がイコールをサポートするのであればインタフェースさえ)でも使えます。ですが構造体、配列、スライスにはイコールが定義されていないため、マップのキーとして使用することはできません。スライスと同じくマップもまた参照型であるため、マップを関数に渡し、その関数内でマップの内容に変更を加えると、変更内容は呼び出し元からも参照可能です。

複合リテラル構文を使い、キーと値をコロン区切りで指定することでマップを作成できるので、作成と同時に初期化が簡単に行えます。

var timeZone = map[string] int {
    "UTC":  0*60*60,
    "EST": -5*60*60,
    "CST": -6*60*60,
    "MST": -7*60*60,
    "PST": -8*60*60,
}

マップへの値の設定と取得は、配列の操作と構文的に似通っていますが、インデックスが整数である必要がない点で異なります。マップ内に存在しないキーを使って、マップから値を取得しようとするとプログラムがクラッシュする要因と成り得るので、これを回避するため下のように複数代入式を使ってください。

var seconds int
var ok bool
seconds, ok = timeZone[tz]

これは見たままですが、「カンマok」慣用句と呼ばれています。この例では、tzが存在すれば、secondsに適切な値がセットされ、okの値は真となります。存在しなければ、secondsにはゼロがセットされ、okの値は偽となります。下はこれを利用した関数です。

func offset(tz string) int {
    if seconds, ok := timeZone[tz]; ok {
        return seconds
    }
    log.Stderr("unknown time zone", tz)
    return 0
}

マップ内に値が存在するか調べたいとき、値自体の取得が不要であればブランク識別子(ただのアンダーライン(_))を使うことができます。ブランク識別子を使うとどんな型の値でも代入または宣言することができ、値を他に影響およぼすことなく破棄することができます。マップ内の存在チェックをするときは、次のように本来は値が返される変数の代わりにブランク識別子を指定してください。

_, present := timeZone[tz]

マップから登録を削除するには、代入方向を変えて複数代入式の右側に論理値を指定してください。この論理値の値が偽のとき登録が削除されます。このときマップ内にキーが存在しなくても問題ありません。

timeZone["PDT"] = 0, false  // 標準時に

出力

Go言語におけるフォーマット出力は、C言語のprintf群のスタイルと類似していますが、より高機能・多機能です。これら関数はfmtパッケージ内で定義されています。先頭一文字は大文字となっており、fmt.Printffmt.Fprintffmt.Sprintfなどが用意されています。文字列関数(Sprintfなど)は、指定したバッファに文字列を格納するのではなく、新しい文字列を返します。

フォーマット文字列の指定は必要ではありません。各PrintfFprintfSprintfにはそれぞれ他にペアとなる関数、例えばPrintPrintlnが用意されています。これらの関数はフォーマット文字列をとらず、代わりに引数ごとにデフォルトフォーマットを生成します。ln版では、引数の間に両引数とも文字列でないときだけ空白を挿入し、出力の後ろに改行を付加します。[訳注:実際試したところ、非ln版のときは引数がともに文字列でないときだけ引数間に空白が挿入されるのに対し、ln版のときは常に空白が挿入されるようです] 下の例では、各行はいずれも同じ内容を出力します。

fmt.Printf("Hello %d\n", 23)
fmt.Fprint(os.Stdout, "Hello ", 23, "\n")
fmt.Println(fmt.Sprint("Hello ", 23))

チュートリアルでも解説したように、fmt.Fprintとそれに関連する関数は、一番目の引数にio.Writerインタフェースを実装していればどんなオブジェクトでも指定可能です。 よく使われるのは、お馴染みの変数os.Stdouos.Stderrです。

ここからC言語との違いが現れます。まず、%dのような数値フォーマットでは、フラグによる符号やサイズの指定は行いません。その代わりに出力ルーチンは、引数の型を参照して値の属性を決定します。

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

これの出力結果です。

18446744073709551615 ffffffffffffffff; -1 -1

例えば整数値を10進表記で出力するようなデフォルトの変換でよければ、どんなケースにも対応可能なフォーマット%vを使うことができます。このとき出力される内容は、PrintPrintlnの出力結果と全く同じです。さらにこのフォーマットは、配列、構造体、マップなどどんな値でも出力することができます。下は、前のセクションで定義したタイムゾーンマップを出力するステートメントです。

fmt.Printf("%v\n", timeZone)  // fmt.Println(timeZone)としても同じ

これの出力結果です。

map[CST:-21600 PST:-28800 EST:-18000 UTC:0 MST:-25200]

マップの出力では、キーの出力は当然ながら順不同となります。
構造体を出力するとき、限定フォーマット%+vを使うと構造体の各フィールドに対してフィールド名も出力されます。また、代替フォーマット%#vを使うとどんな値でも完全なGo言語の構文形式で出力されます。

type T struct {
    a int
    b float
    c string
}
t := &T{ 7, -2.35, "abc\tdef" }
fmt.Printf("%v\n", t)
fmt.Printf("%+v\n", t)
fmt.Printf("%#v\n", t)
fmt.Printf("%#v\n", timeZone)

これらの出力結果です。(アンパサンドに注意)

&{7 -2.35 abc   def}
&{a:7 b:-2.35 c:abc     def}
&main.T{a:7, b:-2.35, c:"abc\tdef"}
map[string] int{"CST":-21600, "PST":-28800, "EST":-18000, "UTC":0, "MST":-25200}

引用符付き文字列の出力も、stringまたは[]byte型の値に対して%qを使えば可能です。(代替フォーマット%#qでは、可能であればバッククォートを使います。) また%xを文字列またはバイト配列に対して適用したときは、整数に適用したときと同様に、長い16進数文字列を出力します。このときフォーマットにスペースを入れると(% x)、バイト間にスペースが出力されます。

もう一つの便利なフォーマットは%Tです。これは値の型を出力します。

fmt.Printf("%T\n", timeZone)

これの出力結果です。

map[string] int

自作の型でデフォルトのフォーマット処理を制御したければ、必要なことはその型にメソッドString() stringを定義するだけです。下は先ほどの型Tに実装してみた例です。

func (t *T) String() string {
    return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c)
}
fmt.Printf("%v\n", t)

これのフォーマット出力結果です。

7/-2.35/"abc\tdef"

自作のString()メソッドはSprintfからも呼び出されます。これは出力ルーチンが完全にリエントラント(再入可能)かつ再帰的に呼び出されるためです。もう少し手を加えて、出力ルーチンが受け取った引数を、同様の別ルーチンにそのまま渡すことも可能です。Printfのシグネチャの最後の引数には…型が使われているため、フォーマット指定以降には、いくつでもパラメータを指定できます。

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

Printf関数内の変数vは、別の出力ルーチンに渡すことも可能です。下は、以前使用した関数log.Stderrの実装です。ここでは、実際にフォーマット処理を行うために、fmt.Sprintlnに引数をそのまま渡しています。

// Stderrは標準出力にログを簡単に出力するヘルパー関数です。Fprint(os.Stderr)と似ています。
func Stderr(v ...) {
    stderr.Output(2, fmt.Sprintln(v))  // Outputはパラメータ (int, string)を取る
}

ここで説明した範囲は出力機能のほんの一部です。より詳しい説明はパッケージfmtgodocドキュメントを参照ください。