Skip to content
Go言語とは、Googleが開発した新しいプログラミング言語です。
当サイトではこの新しい言語についての情報を集約していきます。

【サーバ停止のお知らせ】
 下記の日時で断続的にサーバを停止致します。
 ご迷惑をお掛けいたしますが、ご理解、ご協力をお願い申し上げます。

  • 2010/02/19 10:00-18:00
  • 2010/02/20 10:00-18:00

実践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を使ってif-else-if-elseチェーンを書くことができます。これは慣用的な書き方です。

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)
}

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


セミコロン

Go言語の文法上、ステートメントの終了にはC言語同様にセミコロンが使われますが、C言語とは異なるのは、それらセミコロンはソース上に現れないことです。その代わりに、lexer(字句解析プログラム)がソースを調べて、ある単純なルールに基づき自動的にセミコロンを付け加えるので、打ち込むコードにはセミコロンがほとんど不要です。

ルールはこうです。改行直前のトークンが識別子(intfloat64といった単語を含む)、または文字列や数値定数といった基本的なリテラル、または下のトークンのどれかであるときに、lexerはトークンの後ろにセミコロンを付け加えます。

break continue fallthrough return ++ -- ) }

このルールは次のようにまとめられます。「改行文字の直前に、ステートメントの終わりと成りえるトークンがあるときにセミコロンが挿入される。」

また、右波括弧”}”の直前にあるセミコロンも省略可能です。次のようなステートメントではセミコロンはいりません。

    go func() { for { dst <- <-src } }()

Go言語の慣用記法に沿ったプログラムにおいては、セミコロンが記述されるのはforループ節のイニシャライザ、条件、繰り返しの各要素を分割するようなときだけです。ただし一行を複数のステートメントに分割するにはセミコロンが必要なので、このようなコードを記述するときはセミコロンを挿入してください。

注意事項:制御構造(ifforswitchselect)の左波括弧”{“を、その次の行に絶対に置いてはいけません。もし置いてしまうと、括弧の前にセミコロンが挿入され、それによって予期しない影響が起きる可能性があります。よって次のように記述してください。

if i < f() {
    g()
}

下のように書いてはいけません。

if i < f()  // wrong!
{           // wrong!
    g()
}

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


名前

Go言語において、他の言語同様に名前は重要です。場合によっては、名前そのものが意味を持つことがあります。たとえば名前の頭文字が大文字かどうかで、パッケージの外からの可視性が決まります。Go言語プログラムにおける命名規則の解説に少し時間を割いてみましょう。

パッケージ名

パッケージがインポートされると、パッケージ名はそのパッケージへのアクセッサとなります。次に例を示します。

import "bytes"

このインポートによって、bytes.Bufferにアクセスすることができるようになります。

パッケージの内容を参照する際に、利用者すべてが同一の名前を使ってアクセスできるのであれば、それは有益なことです。それは即ち、パッケージ名が短く簡潔であり、すぐに連想できるような良い名前でなければならないということです。

慣例では、パッケージ名は小文字でひとつの単語です。アンダースコアや大文字が混ざって(mixedCaps)はいけません。

パッケージ使用者がその名前をタイプすることを考慮して、簡潔すぎるぐらいにしてください。名前が重複することを気にする必要はありません。パッケージ名は、単にインポート時にデフォルトとして使われる名前であり、ソースコード全体でユニークである必要はありません。万が一、パッケージのインポート時に重複が起きたときは、別なローカルな名前をつけることができます。いずれにせよ、インポートのファイル名によってどのパッケージが使われるかが決まるので、区別がつかなくなることはまずありません。

もう一つの慣例は、パッケージ名がそのソースディレクトリのベース名であるということです。たとえばsrc/pkg/container/vectorに置かれているパッケージは、"container/vector"としてインポートし、名前はvectorとなります。container_vectorcontainerVectorとはなりません。

パッケージを利用する側は、そのパッケージの内容へアクセスするためにパッケージ名を使用します。(import .表記を使うとアクセス時にパッケージ名が不要となりますが、これはテストや他の一般的でない状況に使うためのものなので、ここでは無視します。)パッケージ名を使うので、エクスポートされる名前は、同じ名称が繰り返されることを避けるためにパッケージ名を利用することがあります。たとえば、bufioパッケージ内の、バッファ付きリーダ型は、BufReaderではなくReaderと名づけられています。これはユーザ側からはbufio.Readerという明確かつ簡潔な名前で見えるためです。さらに、インポートされた実体は、常にそのパッケージ名を使って指定されるため、bufio.Readerio.Readerと名前がかち合うことはありません。おなじように、ring.Ring型の新しいインスタンスを作成する関数(Go言語におけるコンストラクタ)は通常、NewRingと名づけられますが、Ringがそのパッケージからエクスポートされている唯一の型で、かつパッケージ名がringであれば、単にNewとします。この関数はパッケージを利用する側からは、ring.Newとなります。このようにパッケージの構造を利用して良い名前を付けてください。

もう一つの短い例は、once.Doです。once.Do(setup)関数は、何をする関数か想像がつきますが、これをonce.DoOrWaitUntilDone(setup)と書き換えても、より分かりやすくはなりません。長い名前は、慣れたとしても読みやすくなることはありません。複雑もしくは微妙なニュアンスを持つものに名前をつけるときは、すべての情報を名前で表現しようとするより、通常は役立つドキュメントコメントを書いたほうがよいでしょう。

インタフェース名

慣例として、ひとつのメソッドだけを持つインタフェースには、そのメソッド名の後にサフィックス(-er)を加えた名前を付けます。ReaderWriterFormatterなどが該当します。こういった名前は他にもあり、インタフェース名とそこから得られる関数名が重要であるため、このようにして名前を作成します。

ReadWriteCloseFlushStringといったメソッドは、決められた役割とシグネチャを持ちます。混乱を避けるため、同じ役割およびシグネチャを持たないメソッドに対しこれらの名前を使わないでください。それとは反対に、型に実装しようとしているメソッドが、有名なメソッドと同じ役割を持つのであれば、それと同じ名前とシグネチャを使ってください。(注:文字列変換メソッドの名前は、ToStringではなくStringです)

MixedCaps

最後になりますが、Go言語の慣例では、複数の単語から成る名前をつけるときはアンダースコアを使わずに、MixedCapsまたはmixedCapsのように単語の先頭だけ大文字を用います。

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


コメント

Go言語では、C形式の/* */ブロックコメント、およびC++形式の//行コメントが使用できます。基本は行コメントですが、ブロックコメントはパッケージのコメントとして使われる他、コードをブロック単位で無効化するのに役立ちます。

godocプログラム(ウェブサーバ機能もある)は、Go言語のソースファイルから、そのパッケージ内容に関したドキュメントを抽出します。トップレベルの宣言の直前に、空行を挟まずに記述されているコメントは、その宣言に対する説明として宣言とともに抽出されます。これらコメントのありようがgodocによって生成されるドキュメントの品質を左右します。

すべてのパッケージにはパッケージコメントが必要です。これはパッケージ文の前に置かれたブロックコメントです。パッケージが複数のファイルに分かれているときは、どれかひとつにパッケージコメントを記述しておけば、それが使われます。パッケージコメントはパッケージの説明、およびパッケージ全体の情報を提供するものであるべきです。パッケージコメントはgodocページの頭に出力されるので、そのあとに続いて出力されるドキュメントの詳細部に対する導入部分としての役割もあります。

/*
    The regexp package implements a simple library for
    regular expressions.

    The syntax of the regular expressions accepted is:

    regexp:
        concatenation { '|' concatenation }
    concatenation:
        { closure }
    closure:
        term [ '*' | '+' | '?' ]
    term:
        '^'
        '$'
        '.'
        character
        '[' [ '^' ] character-ranges ']'
        '(' regexp ')'
*/
package regexp

シンプルなパッケージであれば、パッケージコメントも簡潔で構いません。

// The path package implements utility routines for
// manipulating slash-separated filename paths.

コメントに、アスタリスクを並べるような特殊な書式は不要です。生成された出力は、等幅フォントで表示されるかどうか分からないので、gofmtのようにスペースによる配置に頼らないよう気をつけてください。最後になりますが、コメントは何ら解釈されることはないプレーンテキストであるため、HTMLや、_this_のようなアノテーションはそのままの形で出力されるので用いないほうがよいでしょう。

パッケージ内の、トップレベルの宣言の直前にあるコメントはすべて、その宣言に対するドキュメントコメントとして扱われます。また、プログラム内でエクスポート(大文字で記述)されている識別子にはすべて、ドキュメントコメントが必要です。

ドキュメントコメントは様々な自動フォーマットが適用されることを考慮すると英語の文章が望ましいです。また先頭の一文は、宣言した識別子で始まる文章で、かつ概要を記述したものでなければなりません。

// Compile parses a regular expression and returns, if successful, a Regexp
// object that can be used to match against text.
func Compile(str string) (regexp *Regexp, error os.Error) {

Go言語では宣言文をグループ化することができます。このときは一連の定数または変数に対して、ひとつのドキュメントコメントで記述することができます。このようなコメントは宣言全体に対して行われるので、たいていは形式的なものとなってしまいます。

// Error codes returned by failures to parse an expression.
var (
    ErrInternal      = os.NewError("internal error")
    ErrUnmatchedLpar = os.NewError("unmatched '('")
    ErrUnmatchedRpar = os.NewError("unmatched ')'")
    ...
)

プライベートな識別子であってもグループ化することで、項目どうしに関連性があることを表すことができます。下の例では、一連の変数がmutexによって保護されていることを示しています。

var (
    countLock   sync.Mutex
    inputCount  uint32
    outputCount uint32
    errorCount uint32
)