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

Archive

Archive for 3月, 2010

このドキュメントはHow to Write Go Code(http://golang.org/doc/code.html)の翻訳です。
今回は文章が短かったので、分割せず一回で翻訳しました。


はじめに

このドキュメントでは新しいパッケージの作成方法、およびテスト方法について説明します。インストール手順に従いGo言語のインストールが済んでいることが前提となっています。

既存のパッケージに変更を加えるか、もしくは新しいパッケージの作成に取り掛かる前には必ず、あなたが何をしようとしているのかを他の人達に知らせるためにメーリングリスト宛にメールを送るようにしてください。そうすることで重複による無駄な労力が減らせるとともに、コードを書く前に仕様に関する議論をすることができます。

コミュニティ リソース

リアルタイムサポートは、Freenode IRCサーバの#go-nutsチャネルにて。

Go言語の公式ディスカッション用メーリングリストは Go Nutsにて。

バグの報告はGo issue trackerまで。

開発の状況を知りたい方は、別のメーリングリストgolang-checkinsにてGoリポジトリの個別のチェックイン情報のサマリーが受け取れます。

新しいパッケージの作成

インポートの際に使用されるパスが x/yとなるパッケージのソースコードは、慣例によってディレクトリ$GOROOT/src/pkg/x/yに格納されます。

Makefile

ソースファイルをチェックしてどれをどの順番でビルドすべきか判断するようなGo言語の仕様と合ったツールがあればよいのですが、現時点でGo言語ではGNU makeを使っています。新しいパッケージディレクトリに最初に作成するファイルは、通常は上で述べたMakefileになります。Goソースツリーで使われている標準的なMakefileとしてsrc/pkg/container/vector/Makefileを参考にしてください。

include ../../../Make.$(GOARCH)

TARG=container/vector
GOFILES=\
	intvector.go\
	stringvector.go\
	vector.go\

include ../../../Make.pkg

Goソースツリーの外(私的なパッケージ)での標準的なMakefileは次のようになります。

include $(GOROOT)/src/Make.$(GOARCH)

TARG=mypackage
GOFILES=\
	my1.go\
	my2.go\

include $(GOROOT)/src/Make.pkg

最初と最後の行では、標準的な定義とルールをインクルードしています。各パッケージでは$(GOROOT)/srcとする代わりに相対パスを使って標準Goツリーにアクセスしているので、$(GOROOT)にスペースが含まれていても正しく動作します。これはGo言語を初めて触るプログラマに役立ちます。

TARG はこのパッケージのターゲットとなるインストールパスであり、クライアントがこのパッケージをインポートするときに使う文字列です。Goツリー内では、この文字列はMakefile が置かれているディレクトリ名と同じでなければなりませんが文字列の前に$GOROOT/src/pkg/は不要です。Goツリー外では、標準Goパッケージの名前と重ならなければ好きなTARG が使用できます。一般的には、自作パッケージのグルーピングに使用しているトップレベルの名前を使います(myname/treemyname/filterなど)。自作パッケージをGoツリー外で管理するときも同様に、make installを実行すると標準インストール先である$GOROOT/pkgにパッケージのバイナリがインストールされるようにしてください。このようにすると、あとで探しやすくなります。

GOFILES はパッケージを作成するためにコンパイルするソースファイルのリストです。行末の文字\はリストを複数行に分割して、並び替えやすくするためのものです。

Goツリー内に新しくパッケージ用ディレクトリを作成するときは、標準ビルドに組み入れるために、そのディレクトリを$GOROOT/src/pkg/Makefile 内のリストに加えてください。それでは、実行してみます。

cd $GOROOT/src/pkg
./deps.bash

これを実行するのは依存関係ファイルMake.depsを更新するためです。(make all またはmake buildを実行したときには自動的に行われます。)

既存パッケージのインポートを変更したときは、$GOROOT/src/pkg/Makefileの変更は不要ですが、上のdeps.bashの実行が必要です。

Go言語のソースファイル

Makefile 内にリストされている各ソースファイルの先頭ステートメントはパッケージ名でなければなりません。またその名前はパッケージがインポートされるときに使われるデフォルトの名前であり、同一パッケージ内のすべてのファイルで同じ名前を使わなければなりません。Go言語では慣習的に、パッケージ名はインポートパスの最後の要素であり、"crypto/rot13"としてインポートされるパッケージはrot13という名前になります。いまのところGo言語のツールの制約として、リンクされてひとつのバイナリとなる各パッケージの名前はユニークでなければなりませんが、いずれこの制約はなくなります。

Go言語では、ひとつのパッケージのソースファイルを一度にまとめてコンパイルするので、特別な決め事や宣言をすることなく、とあるファイルから別ファイル内の定数、変数、型、関数を参照することができます。

Goコードの簡潔かつ慣用的な書き方については、このドキュメントの範囲外ですので、実践Go言語(Effective Go)をご覧ください。

テスト

Go言語にはgotestと呼ばれる軽量のテスト用フレームワークがあります。テストを記述するには、ファイル名の後ろに_test.goが付いたファイルを作成し、そこにシグネチャfunc (t *testing.T)を持つTestXXX という名前の関数を記述します。テストフレームワークがこれらの関数をそれぞれ実行します。その関数内でt.Errort.Failといったエラー通知関数を呼び出すと、テストは失敗したと判断されます。もっと詳しい説明はgotestコマンドのドキュメント(未訳)、およびtestingパッケージのドキュメントをご覧ください。

なお、この*_test.goファイルは Makefile内のリストに記述しないでください。

テストを実行するには、make testまたはgotest を実行してください。(このふたつは同じことです) テストファイルをひとつだけ実行したいときは、たとえばone_test.goであればgotest one_test.goを実行してください。

コードの変更がパフォーマンスに影響を及ぼすようなときは、ベンチマーク関数(gotestコマンドドキュメントを参照)を追加したのち、gotest -benchmarks=.を使って実行してください。

新しいコードをテストしてうまく動いたときが、レビューと投稿を行うタイミングです。

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


ウェブサーバ

Go言語のウェブサーバプログラムを作成して締めくくります。これは実際にはウェブ転送サーバの類です。Googleではhttp://chart.apis.google.comで、データをチャートやグラフに自動変換するサービスを提供していますが、このサービスはデータをクエリパラメータとしてURLに含めなくてはならないため対話的な使い方ができません。ここで紹介するプログラムは、文字を入力するとQRコード(テキストをエンコードした矩形マトリクス)を生成させるために先程のチャートサーバを呼び出す、ちょっとした画面を提供します。生成された画像を携帯電話のカメラで撮影するとURLなどに変換されるので、携帯電話の小さいキーを駆使してURLを入力する手間が省けます。

下はこのプログラム一式です。説明はそのあとに続きます。

package main

import (
    "flag"
    "http"
    "io"
    "log"
    "strings"
    "template"
)

var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
var fmap = template.FormatterMap{
    "html": template.HTMLFormatter,
    "url+html": UrlHtmlFormatter,
}
var templ = template.MustParse(templateStr, fmap)

func main() {
    flag.Parse()
    http.Handle("/", http.HandlerFunc(QR))
    err := http.ListenAndServe(*addr, nil)
    if err != nil {
        log.Exit("ListenAndServe:", err)
    }
}

func QR(c *http.Conn, req *http.Request) {
    templ.Execute(req.FormValue("s"), c)
}

func UrlHtmlFormatter(w io.Writer, v interface{}, fmt string) {
    template.HTMLEscape(w, strings.Bytes(http.URLEscape(v.(string))))
}

const templateStr = `
<html>
<head>
<title>QR Link Generator</title>
</head>
<body>
{.section @}
<img src="http://chart.apis.google.com/chart?chs=300x300&cht=qr&choe=UTF-8&chl={@|url+html}"
/>
<br>
{@|html}
<br>
<br>
{.end}
<form action="/" name=f method="GET"><input maxLength=1024 size=70
name=s value="" title="Text to QR Encode"><input type=submit
value="Show QR" name=qr>
</form>
</body>
</html>
`

main関数までは簡単に説明します。まずサーバのHTTPポート番号のデフォルト値をflagに設定しています。変数templは目新しい部分で、ページを表示するためにサーバ上で実行されるHTMLテンプレートを構築しています。これについては後でもう少し説明します。

main関数で起動パラメータの解析(flag.Parse)後、以前説明した方法でサーバのルートパスとQR関数とを紐付けています。続いてサーバを起動するためにhttp.ListenAndServeが呼び出されます。サーバが動いている間は、ここでブロックされ続けます。

QR関数では、入力フォームデータが含まれているリクエストを受け取り、そのフォーム内のsと名付けられたデータを使ってテンプレート処理を実行します。

このテンプレートパッケージは、json-templateを参考に作られており、とても高機能です。このサーバプログラムでは、ほんの触り程度しか紹介しませんが、テンプレートパッケージの主用途はテンプレートテキストの一部分をtempl.Executeに渡されたデータから得られた要素(この場合はフォームの値)で書き換えることです。テンプレートテキスト(templateStr)内の波括弧{}で囲まれている部分でテンプレートの動作を指定します。{.section @}から{.end}までの部分はデータ項目@の値を伴って実行されます。この@は「カレントのアイテム」を表しており、今回の値はフォーム値です。(文字列が空のときは、テンプレートの該当部分は出力されません。)

{@|url+html}の箇所では、フォーマッタマップ(fmap)に"url+html"という名前で登録されているフォーマット用関数を使ってデータを処理するよう指定しています。実際に登録されているUrlHtmlFormatter関数では、ウェブページ上で文字列を安全に表示させるためのサニタイズ処理を行います。

テンプレート内のその他の文字列はただのHTMLで、ページが読み込まれたときに表示されます。ここでは駆け足で説明したので、詳しくはテンプレートパッケージのドキュメントを参照ください。

数行のコードと、データと掛け合わせて出力するHTMLテキストだけで便利なウェブサーバが手に入りました。このようにGo言語はたった数行でも色々なことが行える高機能な言語です。

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


エラー情報

ライブラリルーチンでは、呼び出し元にエラー情報などを返す必要が度々発生します。以前説明したように、Go言語の複数戻り値を使うと通常の戻り値と一緒にエラーの詳細情報を返すことが簡単にできます。またエラーは慣例的にos.Errorというシンプルなインタフェースを実装しています。

type Error interface {
    String() string
}

ライブラリの作成者はこのインタフェースと併せて、より高機能なモデルを自由に実装して構いません。こうすることでエラーを知らせるだけでなく、何らかの状況も提供することができます。この例として、os.Openは下のos.PathErrorを返しています。

// PathErrorは、エラーとそれを引き起こした操作及び
// ファイルパスが記録されます。
type PathError struct {
    Op string    // "open", "unlink", など
    Path string  // 関連ファイル
    Error Error  // システムコールからの戻り値
}

func (e *PathError) String() string {
    return e.Op + " " + e.Path + ": " + e.Error.String()
}

PathErrorStringメソッドは次のような文字列を生成します。

open /etc/passwx: no such file or directory

このようにエラー情報内に問題の起きたファイル名、操作、引き金となったオペレーティングシステムのエラーを含んでいると、エラーとなった呼び出し箇所から離れた箇所でエラー情報を出力したときにも役立ちます。「そのようなファイルやディレクトリはありません」と単に返されるより、多くの情報を与えてくれます。

呼び出し元で、エラーの正確な詳細情報が必要なときは、型スイッチまたは型アサーションを使い特定のエラーを探して詳細情報を得ることができます。たとえばPathErrorsの場合、内部のErrorフィールドを調べることでリカバリ可能か判断できることがあります。

for try := 0; try < 2; try++ {
    file, err = os.Open(filename, os.O_RDONLY, 0)
    if err == nil {
        return
    }
    if e, ok := err.(*os.PathError); ok && e.Error == os.ENOSPC {
        deleteTempFiles()  // 空き容量を回復
        continue
    }
    return
}

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


並列性

通信による共有

並列プログラミングは大きなトピックではありますが、ここではGo言語の仕様の中で特徴的な部分だけを取り上げます。

一般的な環境における並列プログラミングは、共有変数にアクセスするためには適切な実装が厳密に求められるため扱いにくいものでした。Go言語では、共有変数をチャネル上で引き回し、実行中の異なるスレッドからは実際に同時アクセスさせないという今までとは異なる手法を推奨しています。あるタイミングで値にアクセスできるゴルーチンはひとつだけなので、設計上データ競合は起きえません。

共有メモリを使った通信は行わず、代わりに通信を使うことでメモリを共有するようにしてください。

この手法は、過剰となる側面もあります。参照カウンタであれば、たとえば整数型の変数とミューテックスを併用するほうが最適かもしれません。しかし、高水準な手法として、アクセス制御のためにチャネルを使うことは、明解で正確なプログラムを記述することをより容易くします。

このモデルについて考える手立てとして、1台のCPU上で動いている典型的なシングルスレッドプログラムがあると仮定します。これには同期プリミティブは不要です。同様に同期が不要であるインスタンスを新しく起動して、これらスレッド間を通信させます。この通信が同期していれば、それ以外の同期はとりあえず必要ありません。このモデルと一致する例としては、Unixのパイプラインが良く知られています。Go言語におけるこの並列性へのアプローチは、Hoare氏のCommunicating Sequential Processes (CSP)が基になっており、Unixパイプにおけるタイプセーフの汎用化にもこれが見受けられます。

ゴルーチン

ゴルーチンと呼ぶようにした理由は、スレッド、コルーチン、プロセスといった既存の用語では正確に伝わらないためです。ゴルーチンが持つモデルは単純で、その役割は同一アドレス空間内で他のゴルーチン同士を並列実行することです。またゴルーチンは軽量で、スタック空間を割り当てるより若干コストがかかる程度です。開始時のスタックサイズは節約のため小さく取り、必要に応じてヒープ領域を割り当て(自由に)拡張します。

ゴルーチンはOSの複数スレッド上へ多重化されるので、ひとつが例えばI/O待ちなどでブロックされていても、他のゴルーチンの実行は継続します。ゴルーチンの仕組みによってスレッドを作成・管理する複雑さの大部分が軽減されます。

関数またはメソッドの呼び出しを新規ゴルーチンとして実行するには、呼び出しの直前にキーワードgoと記述してください。呼び出しが完了した時点で、すでにゴルーチンは作成されています。(この効能はUnixシェルにてバックグラウンドでコマンドを実行するために使用する「&」表記とほぼ同じです。)

go list.Sort()  // list.Sortを並列実行する(処理の完了は待たない)

関数リテラルを使うと手軽にゴルーチンが実行できます。

func Announce(message string, delay int64) {
    go func() {
        time.Sleep(delay)
        fmt.Println(message)
    }()  // 括弧に注意。関数を呼び出すために必要。
}

Go言語における関数リテラルはクロージャです。このとき関数内から参照されている変数は関数が実行している間、生存しつづけます。

この例では関数が完了したことを通知する手立てがないため、あまり実践的ではありません。完了通知を行うにはチャネルを使います。

チャネル

マップと同じくチャネルは参照型であり、makeを使って割り当てられます。作成時にオプションの整数パラメータを指定すると、チャネルのバッファサイズとして使われます。この値のデフォルトはゼロであり、ゼロのときバッファリングは行われず同期チャネルとなります。

ci := make(chan int)            // 整数型のバッファなしチャネル
cj := make(chan int, 0)         // 整数型のバッファなしチャネル
cs := make(chan *os.File, 100)  // Fileへのポインタ型のバッファありチャネル

チャネルは、値の交換および同期という通信機能を兼ね備えており、2つの計算処理(ゴルーチン)が予期しない状態とならないことを保証します。

チャネルを利用した優れたイディオムはたくさんありますが、手始めにひとつ紹介します。前のセクションではソート処理をバックグラウンドで起動しましたが、これにチャネルを使って起動したゴルーチンのソート完了を待つよう変更できます。

c := make(chan int)  // チャネルの割り当て
// ゴルーチンとしてsortを起動。完了時にチャネルへ通知
go func() {
    list.Sort()
    c <- 1  // 通知を送信。値は何でも良い
}()
doSomethingForAWhile()
<-c   // sortの完了待ち。送られてきた値は破棄

受信側では常に、受信可能なデータが来るまでブロックされます。送信側はチャネルがバッファリングしていないときは、受信側が値を受信するまでブロックされます。チャネルがバッファリングしているときは、送信側がブロックされるのは値がバッファへコピーされる間だけです。このためバッファがいっぱいのときは、受信側で値を取り出すまで待機します。

スループットを制限するようなケースで、バッファありチャネルをセマフォのように使うことができます。下の例では、入って来たリクエストはhandleに渡されます。その中でまず値をチャネルへ送信し、リクエストを処理したのちチャネルから値を受信しています。チャネルのバッファが持つ容量を利用してprocessの同時呼び出し数を制限しています。

var sem = make(chan int, MaxOutstanding)

func handle(r *Request) {
    sem <- 1    // アクティブキューの空き待ち
    process(r)  // 時間のかかる処理
    <-sem       // 完了。次のリクエストを処理可能にする
}

func Serve(queue chan *Request) {
    for {
        req := <-queue
        go handle(req)  // handleの終了待ちはしない
    }
}

次も同様のアイデアで、一定数のhandleゴルーチンを起動しておき、その中でリクエストチャネルから全て読み込んでいます。ここではゴルーチンの数でprocessの同時呼び出し数を制限しています。このServe関数自体も、終了指示を受信するためのチャネルを引数として受け取り、ゴルーチンを起動したあと、このチャネルを受信することでブロックしています。

func handle(queue chan *Request) {
    for r := range queue {
        process(r)
    }
}

func Serve(clientRequests chan *clientRequests, quit chan bool) {
    // Start handlers
    for i := 0; i < MaxOutstanding; i++ {
        go handle(clientRequests)
    }
    <-quit  // 終了指示待ち
}

チャネル内のチャネル

Go言語の特徴のなかで重要なもののひとつは、チャネルがメモリを割り当てることができ、かつ他の値と同じように引き回すことができる「優れた値」であることです。この機能は一般的に並列、非多重化を安全に実装するために使われます。

前セクションの例におけるhandleはリクエストを処理するための理想的なハンドラではありましたが、そこで扱っていたRequest型は未定義でした。もしその型に応答用チャネルが含まれていれば、各クライアント側でそれぞれ処理結果を受信する経路を用意することができます。下はRequest型の定義の概略です。

type Request struct {
    args        []int
    f           func([]int) int
    resultChan  chan int
}

リクエストオブジェクト内の処理結果受信用チャネルと同様に、クライアント側で関数とその引数も準備します。

func sum(a []int) (s int) {
    for _, v := range a {
        s += v
    }
    return
}

request := &Request{[]int{3, 4, 5}, sum, make(chan int)}
// リクエスト送信
clientRequests <- request
// 応答待ち
fmt.Printf("answer: %d\n", <-request.resultChan)

サーバ側で変更するのは、ハンドラ関数だけです。

func handle(queue chan *Request) {
    for req := range queue {
        req.resultChan <- req.f(req.args)
    }
}

これを現実的なコードにするには、やるべきことが多々残されていることは明らかですが、このコードはあくまで帯域制限、平行処理、非ブロック型RPC機構を実装したフレームワークであり、相互排他は考慮していません。

並列化

これらの考え方のもう一つの応用は、複数のCPUコアすべてを使い計算を並列処理することです。計算処理を個々に分割できるのであれば、各計算の完了を伝えるチャネルを用意することで並列化可能です。

下のサンプルのように、アイテムを格納したVector上で実行される高コストの演算処理があり、またその各アイテムにて演算される値同士が、お互い関連性を持たないと仮定します。

type Vector []float64

// v[i], v[i+1] ... v[n-1]までを演算処理に適用
func (v Vector) DoSome(i, n int, u Vector, c chan int) {
    for ; i < n; i++ {
        v[i] += u.Op(v[i])
    }
    c <- 1    // この部分が終わったことを伝達
}

データをCPUコア数分割し、ループでコア毎に処理をひとつ起動します。演算処理が完了する順番は不定ですが、それは問題ではありません。単に全ゴルーチンを起動後、チャネルから受信した完了通知数をカウントするだけです。

const NCPU = 4  // CPUコア数

func (v Vector) DoAll(u Vector) {
    c := make(chan int, NCPU)  // 任意だが、バッファリングしたほうが賢明
    for i := 0; i < NCPU; i++ {
        go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c)
    }
    // チャネルを空に
    for i := 0; i < NCPU; i++ {
        <-c    // 処理の完了をひとつ待つ
    }
    // すべて完了
}

gc6g等)の現実装のデフォルトでは、このコードは各CPUコアへ並列化はされません。ユーザ処理に対してはコアをひとつしか使用しないようになっているためです。システムコールで任意の数のゴルーチンをブロックすることは可能ですが、デフォルトで実行されるユーザレベルコードは常にひとつだけです。これはもっと賢くあるべきで、また今後賢くしていく予定はありますが、そうなるまでCPUを並列処理させたいときはコードを同時実行したいゴルーチン数をランタイムに指定してください。この指定方法は2つあり、環境変数GOMAXPROCSに使用するコア数(デフォルト1)を設定してからジョブを実行するか、もしくはruntimeパッケージをインポートし、runtime.GOMAXPROCS(NCPU)を呼び出してください。また、スケジューリングとランタイムが改善されたときは、この設定は不要となる予定です。

溢れるバッファ

並列プログラミング向け機能を利用して、より実装が簡単な非並列処理を記述することもできます。下はRPCパッケージから取り出した例です。クライアント側のゴルーチンではループ内でどこかのソース(おそらくネットワーク)からデータを受信します。バッファの割り当て・開放を減らすために使わなくなったバッファはリストに格納しておきます。これを実現するためにバッファありチャネルを使い、このチャネルが空のときは新たにバッファを割り当てます。受信したメッセージをバッファに格納すると、そのバッファはserverChan経由でサーバへ送信されます。

var freeList = make(chan *Buffer, 100)
var serverChan = make(chan *Buffer)

func client() {
    for {
        b, ok := <-freeList  // バッファがあれば取得
        if !ok {              // なければ新たに割り当てる
            b = new(Buffer)
        }
        load(b)              // 次のメッセージをネットから読み込む
        serverChan <- b      // サーバへ送信
    }
}

サーバ側のループではクライアントからメッセージを受信し処理を行ったあと、バッファリストにバッファを返却します。

func server() {
    for {
        b := <-serverChan    // 作業待ち
        process(b)
        _ = freeList <- b    // 空きがあればバッファを再利用
    }
}

クライアントの非ブロック受信では、利用可能であればバッファリストからバッファを取得します。利用可能なバッファがないときは新しくバッファを割り当てます。サーバのfreeListへの非ブロック送信は、バッファリストがいっぱいでなければbfreeListに戻します。バッファが溢れたときはガーベジコレクタによって回収されます。(送信演算子をブランク識別子へ代入することで非ブロック送信となりますが、操作が成功したかどうかは無視されます。) この実装では、ある境界線で溢れてしまうバケツのような空きバッファリストを作成し、冗長なコードを記述する代わりにチャネルのバッファとガーベジコレクタ任せにしています。

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


埋込み

Go言語には型によるサブクラス化という典型的な概念はありませんが、構造体またはインタフェースに型を埋込み、実装を「借りる」仕組みがあります。

インタフェースの埋込みはとても単純です。下は以前説明したio.Readerio.Writerインタフェースの定義です。

type Reader interface {
    Read(p []byte) (n int, err os.Error)
}

type Writer interface {
    Write(p []byte) (n int, err os.Error)
}

ioパッケージではこれと同じように、オブジェクトに対しメソッドの実装を規定するためのインタフェースが、他にもいくつかエクスポートされています。たとえば、ReadWrite両方を持つインタフェースio.ReadWriterがあります。これら2つのメソッドを明示的に記述することでio.ReadWriterを定義することもできますが、次のようにして2つのインタフェースを埋込んで新しいインタフェースを作成するほうがより簡単で、意図が伝わりやすくなります。

// ReadWriteは、基本的なメソッドReadとWriteをグルーピングしたインタフェース
type ReadWriter interface {
    Reader
    Writer
}

このようにすることで、Readerが行えること、およびWriterが行えることをReadWriterが兼ね備えていて、このインタフェースが埋込みインタフェース(メソッド群内に共通のメソッドを持っていてはならない)の結合により作られていることが見て取れます。ただしインタフェース内に埋込むことができるのはインタフェースだけです。

この基本的な考え方は構造体にもあてはまりますが、構造体の場合はより広範囲に渡る影響があります。bufioパッケージは2つの構造体型、bufio.Readerbufio.Writerを持ち、当然それぞれパッケージioの対応するインタフェースを実装しています。bufioではさらに埋込みを利用して、ひとつの構造体にReaderWriterを組み込んでバッファ付きの読み書きを実装しています。下の例で構造体内に型を列挙していますが、このときフィールド名は付けていません。

// ReadWriterは、ReaderとWriterのポインタを格納している
// これはio.ReadWriterの実装
type ReadWriter struct {
    *Reader
    *Writer
}

この構造体はこう書き換えることも可能です。

type ReadWriter struct {
    reader *Reader
    writer *Writer
}

ただこうした場合は、下のようにして呼び出しを転送するメソッドを用意し、フィールド内の各メソッドを実装しioインタフェースを満たすようにしなければなりません。

func (rw *ReadWriter) Read(p []byte) (n int, err os.Error) {
    return rw.reader.Read(p)
}

しかし、直接構造体を埋込んでしまえば、この冗長な記述は要らなくなります。埋込まれた型が持っているメソッドは自動で持ち上げられるため、すなわちbufio.ReadWriterbufio.Readerbufio.Writerのメソッドを持つだけでなく、3つのインタフェース(io.Readerio.Writerio.ReadWriter)の全てを満たすようになります。

埋込みとサブクラス化では大きな違いがあります。型を埋込んでいるとき、埋込んだ型が持っているメソッドは埋込み先のメソッドともなりますが、実行しているメソッドのレシーバは埋込み先の型ではなく、あくまで元の型です。サンプルコードの bufio.ReadWriterReadメソッドが実行されることと、先程の転送メソッドが実行されることは結果としてまったく同じです。(レシーバはReadWriterフィールドのreaderで、ReadWriter自身ではありません。)

また、埋込みを使うことで少し扱いやすくもなります。下の例では、通常の名前付きフィールドと並んで、埋込みフィールドを記述しています。

type Job struct {
    Command string
    *log.Logger
}

これでJob型は、log.Logger型が持つLogLogfといったメソッドを持つようになりました。もちろん、Loggerにフィールド名を与えることはできますが、それは必須ではありません。これで、Jobを使ってログが記録できるようになりました。

job.Log("starting now...")

このLoggerは普通の構造体フィールドであるため、いままで通りの方法で初期化が行えます。

func NewJob(command string, logger *log.Logger) *Job {
    return &Job{command, logger}
}

埋込まれているフィールドを直接参照しなければならないときは、フィールドの型名(パッケージ名は不要)をフィールド名として用います。つまりJob型である変数job*log.Loggerにアクセスしたいときはjob.Loggerと書きます。これはLoggerのメソッドに手を加えたいときに役立ちます。

func (job *Job) Logf(format string, args ...) {
    job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args))
}

型を埋込むことで名前の競合が発生する恐れがありますが、その解決ルールは単純です。最初に、フィールドまたはメソッドXは、その他の、より深い入れ子内にあるXを隠蔽します。たとえばlog.LoggerCommandと名づけられたフィールドまたはメソッドが含まれていても、JobCommandフィールドがそれより優先されます。

2番目として、同一の入れ子階層に同じ名前が現れたとき、通常ではエラーとなります。たとえばJob構造体に、Loggerと名づけられた別のフィールドまたはメソッドが含まれているときlog.Loggerの埋め込みは不正です。ただし、このとき重複した名前が、型定義より外側のプログラムから一切アクセスされなければ大丈夫です。この決まりによって、埋め込まれた型へ外部から変更を加えてもある程度保護されます。つまり、フィールドの追加で他の型が持つフィールドとかち合ってしまっても、どちらのフィールドも一切使用されることがなければ何ら問題ありません。