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

Archive

Archive for 11月, 2009

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


catコマンドの実装例

前回作成したfileパッケージを基にして、Unixユーティリティのcat(1)の簡易バージョンを作成します。
このファイルはprogs/cat.goにあります。

05    package main

07    import (
08        "./file";
09        "flag";
10        "fmt";
11        "os";
12    )

14    func cat(f *file.File) {
15        const NBUF = 512;
16        var buf [NBUF]byte;
17        for {
18            switch nr, er := f.Read(&buf); true {
19            case nr < 0:
20                fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", f.String(), er.String());
21                os.Exit(1);
22            case nr == 0:  // EOF
23                return;
24            case nr > 0:
25                if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
26                    fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", f.String(), ew.String());
27                }
28            }
29        }
30    }

32    func main() {
33        flag.Parse();   // Scans the arg list and sets up flags
34        if flag.NArg() == 0 {
35            cat(file.Stdin);
36        }
37        for i := 0; i < flag.NArg(); i++ {
38            f, err := file.Open(flag.Arg(i), 0, 0);
39            if f == nil {
40                fmt.Fprintf(os.Stderr, "cat: can't open %s: error %s\n", flag.Arg(i), err);
41                os.Exit(1);
42            }
43            cat(f);
44            f.Close();
45        }
46    }

すでにこのコードをすぐに理解できるようになっていると思いますが、switch文には説明すべき箇所があります。
forループと同じく、ifやswitchには初期化文を含めることができます。18行目のswitchではf.Read()の戻り値をnrとer変数に、一度に代入しています。(25行目のifも同様です)

swtich文では通常、値と一致するまでcaseを先頭ひとつずつ順に評価していきます。caseに記述する式はswitchで指定した値と同じ型でありさえすれば定数や数値である必要はありません。

このswitchの値にはtrueが指定されています。このように値がtrueのときだけは値の記述は省略可能です。(for文と同じです)
値が記述されていないときはtrueとして扱われます。このようなswitch文は、if – else文を連ねたのと同じです。
またswtich文のcaseには暗黙的なbreakがあることをここに明記しておきます。

26行目では、入力バッファをスライスしてWrite()を呼び出しています。
Go言語ではI/Oバッファを取り扱うときは通常スライスを使用します。

これから入力をrot13変換するオプション機能をもったcatの亜種を作成してみましょう。rot13とはアルファベットを一文字毎に13文字後のアルファベットに置き換えてつくる簡単な暗号です。(参考文献:Wikipedia)
これはバイト処理で行えば簡単ですが、すこし回り道してGo言語のインタフェース(interface)を理解していきましょう。

cat()サブルーチンはf.Read()とString()の2つのメソッドしか使っていませんでした。
まずは、それらの2つのメソッドとまったく同じメソッド持つインタフェースを定義することから始めましょう。
このコードは、progs/cat_rot13.goファイルにあります。 

26    type reader interface {
27        Read(b []byte) (ret int, err os.Error);
28        String() string;
29    }

readerインタフェースの2つのメソッドを持つ型であればすべて(たとえ他のメソッドを有していても)このインタフェースを実装していることになります。すなわちfile.Fileはこの2つのメソッドをすでに実装しているので、readerインタフェースを実装していることになります。

catサブルーチンを少し変更し、引数を*file.Fileの代わりにreaderを受け取るようにするだけでもちゃんと動作しますが、せっかくなのでreaderを実装する別の型を記述してみましょう。

新しく作成する型では別のreaderをラップし、そのreaderから取り出したデータにrot13変換を行います。
型を定義しreaderインタフェースの2つのメソッドを記述すると、readerインタフェースの2個目の実装ができあがります。

31    type rotate13 struct {
32        source    reader;
33    }

35    func newRotate13(source reader) *rotate13 {
36        return &rotate13{source}
37    }

39    func (r13 *rotate13) Read(b []byte) (ret int, err os.Error) {
40        r, e := r13.source.Read(b);
41        for i := 0; i < r; i++ {
42            b[i] = rot13(b[i])
43        }
44        return r, e
45    }

47    func (r13 *rotate13) String() string {
48        return r13.source.String()
49    }
50    // end of rotate13 implementation

(42行目で呼び出しているrot13関数は、取るに足らないので省略しています)

新しく追加した機能を利用するためにflagを定義します。

14    var rot13Flag = flag.Bool("rot13", false, "rot13 the input")

さらに、これを使うためにcat()関数を少しだけ変更します。 

52    func cat(r reader) {
53        const NBUF = 512;
54        var buf [NBUF]byte;

56        if *rot13Flag {
57            r = newRotate13(r)
58        }
59        for {
60            switch nr, er := r.Read(&buf); {
61            case nr < 0:
62                fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", r.String(), er.String());
63                os.Exit(1);
64            case nr == 0:  // EOF
65                return;
66            case nr > 0:
67                nw, ew := file.Stdout.Write(buf[0:nr]);
68                if nw != nr {
69                    fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", r.String(), ew.String());
70                }
71            }
72        }
73    }

56~58行目でrot13フラグがtrueのとき、引数で受け取ったreaderを、rotate13内にラップさせて入れ替えています。
このラッピングさせる処理をmain関数内で行えば、cat関数は引数の型以外は変更しなくてもよかったのですが、これも学習の一部と思ってください。

注意してもらいたいのは、インタフェース変数がポインタではなく値であることです。
渡された値は構造体のポインタではありますが、受け取る引数の型はreaderであり*readerではありません。

実行してみます。

    % echo abcdefghijklmnopqrstuvwxyz | ./cat
    abcdefghijklmnopqrstuvwxyz
    % echo abcdefghijklmnopqrstuvwxyz | ./cat --rot13
    nopqrstuvwxyzabcdefghijklm
    %

このようにインタフェースを使ってファイルディスクリプタの実装が入れ替えられるので、依存性注入(dependency injection)も簡単に行えます。

このインタフェースは他の言語にない、Go言語の特徴です。
インタフェース内に定義されている全てのメソッドを実装した型は、自動的にそのインタフェースを実装したことになります。
すなわち、型は複数の異なるインタフェースを実装可能ということです。

Go言語の型には階層はありませんが、その代わりインタフェースを使用することでrot13で行ったようにプログラムの部分的な変更が容易に行えます。
file.File型はreaderインタフェースを実装していますが、writerインタフェース、その他のインタフェースを必要に応じて実装可能です。

空インタフェースについて考えてみましょう。

    type Empty interface {}

すべての型は空インタフェース(interface {})を実装しています。
そのため、空インタフェースはコンテナの類を作るときに役立ちます。

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


I/O パッケージ

次は、ファイルI/Oアクセスを行うopen, close, read, write関数を持つ簡単なパッケージです。
ここからfile.goファイルが始まります。

05    package file

07    import (
08        "os";
09        "syscall";
10    )

12    type File struct {
13        fd      int;    // file descriptor number
14        name    string; // file name at Open time
15    }

最初の数行は、fileパッケージの宣言と2つのパッケージのインポートを行っています。
osパッケージはオペレーションシステムごとの差異を吸収します。そのosパッケージのエラーハンドリング機能を使ってファイルI/Oの基本を実装してみましょう。

もうひとつのsyscallパッケージは、オペレーティングシステムを呼び出す低レベルなシステムコールインタフェースを提供します。

次の行では新しく型を定義しています。型の定義はtypeキーワードで始まり、このケースではFileという名前の構造体(struct)を定義しています。少し趣向を凝らし、このFile構造体内にファイルディスクリプタ(fd)と、それの参照先ファイルのファイル名(name)のフィールドを定義しました。

Fileという型の名前が大文字から始まるため、この型はパッケージの外部(パッケージの利用者)から利用することができます。
Go言語の情報の可視性についてのルールは単純です。名前(トップレベルの型名、関数名、メソッド名、定数名、変数名、構造体のフィールドおよびメソッド名)の先頭一文字が大文字になっていれば、パッケージの利用者側から参照可能となります。すなわち大文字にしなければ、それが定義されているパッケージ内からしか参照できません。
このルールはコンパイラによって実施されるため、絶対的なルールとなっています。
この外部パッケージから可視状態であることをGo言語の用語で「エクスポートされた(exported)」と言います。

このFile型の場合、フィールドがすべて小文字なのでパッケージの使用者側からは不可視ですが、後ほど大文字で始まるエクスポートされたメソッドをいくつか追加していきます。

最初はFile型オブジェクトを作成するファクトリ関数です。

17    func newFile(fd int, name string) *File {
18        if fd < 0 {
19            return nil
20        }
21        return &File{fd, name}
22    }

この関数の戻り値は、新しく作られたFile構造体のポインタで、内部フィールドにファイルディスクリプタとファイル名が格納されます。
このコードはマップや配列をつくるときに用いられる書き方に類似しています。これはGo言語の”複合リテラル(composite literal)”という書き方で、これを使ってオブジェクトに新しいヒープ領域を割り当てています。
次のような書き方もできます。

    n := new(File);
    n.fd = fd;
    n.name = name;
    return n

File型のような単純な構造体であれば、21行目のように複合リテラルを使ったほうが簡単です。

このファクトリ関数を使用して、エクスポートされた*File型の変数を作成します。この変数名はおなじみですね。
 
24    var (
25        Stdin  = newFile(0, "/dev/stdin");
26        Stdout = newFile(1, "/dev/stdout");
27        Stderr = newFile(2, "/dev/stderr");
28    )

newFileは内部関数なのでエクスポートされません。
エクスポートすべきは次のOpen関数です。

30    func Open(name string, mode int, perm int) (file *File, err os.Error) {
31        r, e := syscall.Open(name, mode, perm);
32        if e != 0 {
33            err = os.Errno(e);
34        }
35        return newFile(r, name), err
36    }

このたった数行ですが、いくつかの新しいことが現れました。
最初に目に付くのは、Open関数がfileとerrという複数の戻り値をもっていることです。(エラーについてはあとで説明します)
括弧でくくった宣言リストで複数の戻り値を宣言します。構文的には引数リストが2個あるように見えます。

31行目のsyscall.Open関数も複数の戻り値を返しすので、変数を複数宣言して戻り値を受け取っています
戻り値を受け取る変数rとeはともにint型です。(syscallパッケージを要確認)
最後に35行目で、Fileのポインタとエラーの2つの値を返しています。syscall.Openがエラーとなったときは、ファイルディスクリプタrの値はマイナス値となり、newFile関数はnilを返します。

エラーについて説明します。osパッケージライブラリは包括的なエラーの概念を含んでいます。
この例のように関数間におけるエラー情報の受け渡しに、この共通的なエラー機能を使用するのは、Go言語のコード全体で一貫したエラーハンドリングを行うよい方法です。
Openメソッドでは、Unixのerrno値をos.Errno型を使うことでos.Error型に変換しています。

File型オブジェクトが作成できるようになったので、つづいてFileにメソッドを追加していきます。
型にメソッドを宣言するには、定義する関数名の直前の括弧内に、レシーバを型を明示して記述します。
下に記述する*Fileの各メソッドでは、それぞれfileレシーバ変数を宣言しています。

38    func (file *File) Close() os.Error {
39        if file == nil {
40            return os.EINVAL
41        }
42        e := syscall.Close(file.fd);
43        file.fd = -1;  // so it can't be closed again
44        if e != 0 {
45            return os.Errno(e);
46        }
47        return nil
48    }

50    func (file *File) Read(b []byte) (ret int, err os.Error) {
51        if file == nil {
52            return -1, os.EINVAL
53        }
54        r, e := syscall.Read(file.fd, b);
55        if e != 0 {
56            err = os.Errno(e);
57        }
58        return int(r), err
59    }

61    func (file *File) Write(b []byte) (ret int, err os.Error) {
62        if file == nil {
63            return -1, os.EINVAL
64        }
65        r, e := syscall.Write(file.fd, b);
66        if e != 0 {
67            err = os.Errno(e);
68        }
69        return int(r), err
70    }

72    func (file *File) String() string {
73        return file.name
74    }

レシーバ変数は明示的に定義しなくてはならず、構造体のメンバにアクセスするためにはレシーバ変数を使用する必要があります。
メソッドは構造体内で宣言するのではありません。構造体の宣言内で定義するのはデータメンバだけです。
実はメソッドは構造体にだけではなく、整数または配列などほぼすべての型に作成することができます。
配列を使った例は後で記述します。

Stringメソッドは、後ほど解説する文字出力変換に使用されるメソッドです。

これらのメソッドはUnixのエラーコードEINVAL(これをos.Errorに変換した値)を返すためパブリック変数os.EINVALを使用しています。osパッケージライブラリにはこのような標準的なエラー値が定義されています。

今回は新しいパッケージを使います。

05    package main

07    import (
08        "./file";
09        "fmt";
10        "os";
11    )

13    func main() {
14        hello := []byte{'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n'};
15        file.Stdout.Write(hello);
16        file, err := file.Open("/does/not/exist",  0,  0);
17        if file == nil {
18            fmt.Printf("can't open file; err=%s\n",  err.String());
19            os.Exit(1);
20        }
21    }

インポートの”./file”の”./”の部分は、コンパイラに対しパッケージがインストールされているディレクトリからインポートするのではなく、このmainパッケージ自身のディレクトリからインポートするように指示しています。

最後にプログラムを実行してみましょう。

    % helloworld3
    hello, world
    can't open file; err=No such file or directory
    %

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


定数について

Go言語の整数(int)型には多くのサイズが用意されていますが、整数の定数は一種類だけです。0LLや0x0ULといった定数は使えません。
その代わり整数の定数は大きな桁数の精度を持っているとして扱われ、その値より小さい精度の変数へ代入しようとしたときだけオーバーフローを起こします。  

    const hardEight = (1 << 100) >> 97  // これは正しいです

言語仕様(language specification)を参照してもらうのが一番良いのですが、実例はここにも少しあります。

    var a uint64 = 0  // aはuint64型で値は0
    a := uint64(0)    // 上と等価、変換(conversion)を使用
    i := 0x1234       // iはデフォルトのint型
    var j int = 1e6   // 正しい(1000000 はintで表現可能)
    x := 1.5          // float型
    i3div2 := 3/2     // 整数の割り算。値は1
    f3div2 := 3./2.   // 浮動小数点の割り算。値は1.5

変換(conversion)は整数または浮動小数点に対し、符号やサイズを変換するような単純なケースにだけ使用できます。
数値を変数に代入するとき、具体的なサイズや型を持つ場合を除いては何ら自動変換は行われません。

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


メモリの割り当てについて

Go言語のほとんどの型は値であり、int型、構造体、配列の内容は参照ではなく実体です。
新しく変数を割り当てるにはnew()を使用します。new()は割り当てたメモリへのポインタを返します。 

    type T struct { a, b int }
    var t *T = new(T);

より慣用的な書き方をすると 

    t := new(T);

マップ、スライス、チャネル(後で説明)といった型は、他のオブジェクトへの参照情報を持っています。
スライスやマップの内容を変更すると、その参照先のデータをおなじく参照している別の変数にも影響します。
この3つの型を使うときは、ビルトイン関数のmake()を使います。

    m := make(map[string]int);

上のステートメントは新しいマップを初期化して要素が格納できる状態にします。
マップの宣言だけを行うには次のようにします。  

    var m map[string]int;

上は何も要素を持てないnil参照を作成します。
マップを使用するには、make()を使用して最初に初期化するか、またはすでに作成済みのマップを代入しなければなりません。

new(T)は*Tを返しますが、make(T)はTを返すので注意してください。
new()でオブジェクトの参照を(誤って)割り当ててしまうとnil参照へのポインタが返ります。これは初期化していない変数を作り、そのアドレスを取得したことと同じです。

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


Goの型について

Go言語も、他の言語と同じようにintやfloat型といったサイズがシステムによって異なる型を持ってます。
サイズを明示したint8やfloat64型もあり、加えてuint, uint32といった符号なし整数型もあります。これらはすべて異なる型であり、たとえint型が32ビットのシステム上であっても、intとint32は同じ型ではありません。
一方、byte型はuint8型の同義語です。(byte型はstring型の要素)

string型もまた言語に組み込まれた型です。これについて説明します。
string型は単純なbyte型の配列ではなく不変、すなわち一度string型の値を作成すると値は変更できません。あたりまえですが代入によりstring型の値を入れ替えることは可能です。strings.goプログラムから抜き出した下のコードは正しく動作します。

11        s := "hello";
12        if s[1] != 'e' { os.Exit(1) }
13        s = "good bye";
14        var p *string = &s;
15        *p = "ciao";

しかし下のコードはstring型の値を変更しようとしているので不正なコードです。 

    s[0] = 'x';
    (*p)[1] = 'y';

Go言語のstring型はC++言語の文字列定数(const string)のようなもので、string型のポインタは文字列定数の参照とほぼ同じです。

はい、ここでポインタという言葉が出てきました。
Go言語ではポインタの扱いが若干シンプルとなっています。では続きをどうぞ。

配列はこのように宣言します。 

    var arrayOfInt [10]int;

配列はstring型と似ていますが、要素の値は変更可能です。
C言語では、上の例のarrayOfInt変数をint型のポインタとしても扱えますがGo言語では扱えません。
配列のポインタはとても重要(かつ有益)であるため追って説明します。

配列はそれ自身がサイズ情報を持っています。そして配列からスライス変数をつくれます。スライスとは同型の配列のポインタを割り当てることができる変数です。
より一般的に説明すると、a[low : heigh]と記述すると配列aのlow~high-1までを参照しているスライスができます。
スライスは配列とそっくりですが、サイズを明示的に持たず([] と[10]の違い)、配列内の一部分だけを参照します。
配列はデータを共有はできませんが、複数のスライスで同じひとつの配列を参照していればデータが共有されます。

Go言語のプログラムにおいて、スライスは通常の配列より柔軟であり、他オブジェクトへの参照情報をもっていて、かつ効率的であるため多用されます。
その一方、スライスでは通常の配列のようには細かいメモリ割り当ての制御はできないため、たとえば100要素の配列を持たせるような場合は通常の配列を使ってください。

関数に配列を受け渡すときは、たいていパラメータをスライスとして宣言します。関数を呼び出す側では、スライス式で配列の必要な部分のみの参照を作成して(効率的に)関数をよびだしてください。

関数でスライスを使用した例です。(sum.goから) 

09    func sum(a []int) int {   // returns an int
10        s := 0;
11        for i := 0; i < len(a); i++ {
12            s += a[i]
13        }
14        return s
15    }

このsum関数を呼び出すにはこうします。 

19        s := sum(&[3]int{1,2,3});  // a slice of the array is passed to sum

sum関数の戻り値の型はintです。戻り値の型は関数のパラメータリストの直後に記述します。

[3]int{1,2,3}"の箇所で、角括弧[]につづいて型を記述して配列を作成しています。この例だとint型3要素を持つ配列です。&記号を前に書くことでこの配列のインスタンスのアドレスを得られます。
配列を暗黙的にスライスにしてsum()関数に配列のポインタを渡しています。

配列作成時、要素数を指定する代わりにコンパイラ側にカウントしてもらうには、配列のサイズに…を指定します。 

    s := sum(&[...]int{1,2,3});

実際のところ、配列データのメモリ割り当てについて気にしない限りは、角括弧[]を空にして&をつけずにスライスを作成するだけで事足ります。 

    s := sum([]int{1,2,3});

マップも同様に、次のように初期化できます。 

    m := map[string]int{"one":1 , "two":2}

sum関数内で初めて出てきた関数len()は組み込み関数で、要素数を返します。
このlen関数は文字列、配列、スライス、マップ、チャネルで利用可能です。 

話は変わりますが、同様に文字列、配列、スライス、マップ、チャネルに対して機能するrangeについて説明します。これはforループ節で使用します。説明するより見てもらったほうが早いので、

    for i := 0; i < len(a); i++ { ... }

これはスライス(またはマップなど)の要素数ループを繰り返します。これを下のように書き換えることが出来ます。 

    for i, v := range a { ... }

この例ではi変数にループのインデックス、v変数にrangeで指定したaの要素が順番に代入されます。
rangeの使用例は Effective Go をご覧ください。