チュートリアルの翻訳、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
    %