Skip to content
Go言語とは、Googleが開発した新しいプログラミング言語です。
当サイトではこの新しい言語についての情報を集約していきます。
このサイトの主要コンテンツは、公式サイトの日本語訳です。右側のメニューから選んでください。

Archive

Archive for 2月, 2010

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


関数

複数の戻り値

Go言語の目新しい特徴のひとつは、関数およびメソッドが複数の値を返せることです。これは、C言語のプログラムで扱いにくかった、受信エラー(EOFを表す-1など)や引数の値の変更などを改善します。

C言語で書き込みエラーが起きたときは、マイナス値を返すことにより通知され、共用の変数にエラーコードが格納されます。Go言語ではWriteは書き込みデータ数とエラーを別々に返すことができます。つまり次のような情報を得ることができます。「何バイトか書き込めましたが、デバイスが一杯になったので一部書き込めませんでした。」

osパッケージの*File.Writeのシグネチャは次のように定義されています。

func (file *File) Write(b []byte) (n int, err Error)

シグネチャが示すとおりに、このメソッドは書き込んだバイト数を返すとともに、n != len(b)のとき非nilのErrorを返します。これは一般的な書き方です。その他の例は、エラーハンドリングに関するセクションを参照ください。

同様のアプローチにより、戻り値に参照パラメータを模してポインタを返す必要がなくなります。次の関数は、バイト配列の指定位置から数値を取り出し、その値と次の位置を返す単純な関数です。

func nextInt(b []byte, i int) (int, int) {
    for ; i < len(b) && !isDigit(b[i]); i++ {
    }
    x := 0
    for ; i < len(b) && isDigit(b[i]); i++ {
        x = x*10 + int(b[i])-'0'
    }
    return x, i
}

この関数は次のようにして、入力配列から数値を取り出すために利用できます。

    for i := 0; i < len(a); {
        x, i = nextInt(a, i)
        fmt.Println(x)
    }

名前付き結果パラメータ

Go言語の、戻り/結果「パラメータ」には、名前をつけることができ、引数パラメータのように通常の変数として扱うことができます。名前が付けられていると、関数が呼び出されたときにその型のゼロ値で初期化されます。引数を持たないreturnステートメントを実行したときは、その時点で結果パラメータに格納されている値が、戻り値として使われます。

名前は必ずしも必要ではありませんが、名前をつけることでコードをより簡潔にすることができます。これは資料としても役立ちます。前出のnextInt関数の結果パラメータに名前を付けると、返されたint値がそれぞれ何を示しているか明確になります。

func nextInt(b []byte, pos int) (value, nextPos int) {

名前付きの結果パラメータは、初期化が行われた上で、パラメータなしのreturnと結びつけられるので、明確になるだけでなくシンプルになります。下は、この仕組みをうまく使ったio.ReadFullの例です。

func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
    for len(buf) > 0 && err == nil {
        var nr int
        nr, err = r.Read(buf)
        n += nr
        buf = buf[nr:len(buf)]
    }
    return
}

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


制御構造

Go言語の制御構造は、C言語の制御構造と似通っていますが、大きく異なる点があります。ループにはdowhileはなく、若干改良されたforループだけです。switchはより柔軟になっています。ifswitchはオプションとしてforのように初期化ステートメントを受け入れます。また、新しい制御構造として、型switch、および多重通信を取り扱えるselectがあります。文法も若干異なっており、丸括弧()は不要ですが、本体部は波括弧で区切られていなければなりません。

if

下はGo言語における単純なifステートメントです。

if x > 0 {
    return y
}

波括弧{}を必須にしたことにより、複数行に渡るifステートメントの記述が見やすくなりました。これは特に、returnbreakのような制御ステートメントを含むときには優れた書き方です。

ifswitchには初期化ステートメントを記述できるため、そこでローカル変数の準備を行うのが一般的です。

if err := file.Chmod(0664); err != nil {
    log.Stderr(err)
    return err
}

Go言語のライブラリ内で良く見られる書き方ですが、ifステートメントから次のステートメントへ制御が移らないとき(すなわち、breakcontinuegotoreturnのいずれかでifの本体から抜けるとき)は、不要なelseは省略します。

f, err := os.Open(name, os.O_RDONLY, 0)
if err != nil {
    return err
}
codeUsing(f)

下の例は、一連のエラー判定を必要とするよく出会う状況です。処理が成功と判断されたときは、エラー処理はスキップし、処理が下方へと流れていくので読みやすいコードとなっています。エラーのときはreturnステートメントで抜けてしまうため、elseステートメントは必要ありません。

f, err := os.Open(name, os.O_RDONLY, 0)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    return err
}
codeUsing(f, d)

for

Go言語のforループは、C言語のforと似てはいますが、同じではありません。Go言語のforループは、C言語のforwhileループを兼ねていますが、do-whileループに相当するものはありません。forループには3つの形式がありますが、セミコロンを使うのはそのうちひとつだけです。

// Cのforに相当する形式
for init; condition; post { }

// Cのwhileに相当する形式
for condition { }

// Cのfor(;;)に相当する形式
for { }

省略形式による変数の宣言(:=)を使うと、インデックス変数の宣言が容易です。

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

配列、スライス、文字列、マップの内容、もしくはチャネルから読み込んだデータをループさせるときは、range節でループを制御することができます。

var m map[string]int
sum := 0
for _, value := range m {  // キーは使われません
    sum += value
}

文字列を扱うときのrangeはより高機能で、UTF-8エンコードでユニコードの各文字を取り出します。このとき不正なエンコーディングあると、1バイトスキップした上で置換ルーン(Unicode replacement character U+FFFD)として扱います。下はループの例です。

for pos, char := range "日本語" {
    fmt.Printf("character %c starts at byte position %d\n", char, pos)
}

次が出力されます。

character 日 starts at byte position 0
character 本 starts at byte position 3
character 語 starts at byte position 6

最後になりますが、Go言語にはカンマ演算子がなく、また++--は式ではなくステートメントです。forで複数の変数を回したいときは、下のように同時代入を使わなければなりません。

// aを逆に並び替える
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}

switch

Go言語のswitchは、C言語より多機能です。switchの式は、定数や整数である必要はありません。一致するものが見つかるまで、caseを上から下まで評価していきます。switchが式を伴わないときは、式の値がtrueとなるcaseにマッチします。これを利用してswitchを使ってifelseifelseチェーンを書くことができます。これは慣用的な書き方です。

func unhex(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
        return c - '0'
    case 'a' <= c && c <= 'f':
        return c - 'a' + 10
    case 'A' <= c && c <= 'F':
        return c - 'A' + 10
    }
    return 0
}

caseから直下のcaseへと処理が自動的に移ることはありませんが、caseにはカンマで区切ったリストを指定できます。

func shouldEscape(c byte) bool {
    switch c {
    case ' ', '?', '&', '=', '#', '+', '%':
        return true
    }
    return false
}

下は、2つのswitchステートメントを使ったバイト配列の比較ルーチンです。

// Compare は2つのバイト配列を辞書的に比較して整数を返します。
// 結果は、a == bのとき0、a < bのとき-1、a > bのとき+1
func Compare(a, b []byte) int {
    for i := 0; i < len(a) && i < len(b); i++ {
        switch {
        case a[i] > b[i]:
            return 1
        case a[i] < b[i]:
            return -1
        }
    }
    switch {
    case len(a) < len(b):
        return -1
    case len(a) > len(b):
        return 1
    }
    return 0
}

switchは、インタフェース変数の動的な型を見つけるために用いることもできます。その型switchには、型アサーションの構文を使って丸括弧()の中にキーワード"type"と書きます。switchの式で変数を宣言したとき、その変数は各case節において適切な型となります。

switch t := interfaceValue.(type) {
default:
    fmt.Printf("unexpected type %T", t)  // %T は型を出力する
case bool:
    fmt.Printf("boolean %t\n", t)
case int:
    fmt.Printf("integer %d\n", t)
case *bool:
    fmt.Printf("pointer to boolean %t\n", *t)
case *int:
    fmt.Printf("pointer to integer %d\n", *t)
}