このドキュメントは、http://golang.org/doc/go_faq.htmlの翻訳です。


起源
このプロジェクトの目標は?
名前の由来は?
このマスコットの由来は?
このプロジェクトの歴史は?
新しく言語を作った理由は?
Go言語の基になった言語は?
設計方針は?
利用
Google社内で、Go言語が使われているか?
Go言語のプログラムは、C/C++のプログラムとリンク可能か?
Go言語は、Googleのプロトコルバッファをサポートしているか?
Go言語のホームページを他の言語に翻訳してもよいか?
設計
Unicode識別子とは?
機能Xを実装していない理由は?
Generic型がない理由は?
例外(exception)がない理由は?
アサート(assert)がない理由は?
並列性がCSPをベースにしている理由は?
スレッドではなくゴルーチンである理由は?
マップ操作がアトミックに行われない理由は?
Go言語はオブジェクト指向言語か?
メソッドを動的に呼び出す方法は?
型の継承を採用しなかった理由は?
lenが、メソッドでなく関数である理由は?
メソッドと演算子のオーバロードをサポートしていない理由は?
Go言語に「implements」宣言がない理由は?
自作の型がインタフェースを満たしていることを保証するには?
T型は、なぜEqualインタフェースを満たしていないのか?
[]Tを[]interface{}に変換できるか?
nilとerror型のnilとがイコールにならないのは?
C言語のようなunion(タグ無し)がない理由は?
バリアント型がない理由は?
暗黙的な数値変換機能を提供しない理由は?
マップが言語に組み込まれている理由は?
スライスをマップのキーに使用できない理由は?
配列は値であるが、マップ・スライス・チャネルが参照型である理由は?
コードの記述
ライブラリからドキュメントをどうやって作成しているか?
Go言語のプログラミングスタイルガイドはあるか?
Go言語のライブラリにパッチをサブミットするには?
ポインタとアロケーション
関数パラメータは値渡しか?
値とポインタどちらでメソッドを定義すべきか?
newとmakeの違いは?
64ビットマシン上でint型が32ビットである理由は?
変数がヒープ、スタックのどちらに割り当てられているか知りたい
並列処理
どの操作がアトミックか? ミューテックスについては?
ゴルーチンを複数使ったプログラムが、複数のCPUを利用しない理由は?
GOMAXPROCS > 1にすると、プログラムが遅くなることがある理由は?
関数とメソッド
Tと*Tとが、異なるメソッド群を持つのは?
クロージャをゴルーチンとして動かすとどうなるか?
制御フロー
Go言語に、?:演算子はあるか?
パッケージとテスト
複数のファイルから成るパッケージを作成するには?
単体テストの書き方は?
テスト用のヘルパー関数がみあたりません
実装
Go言語のコンパイラのビルドには、どのコンパイラを使っているのか?
ランタイムサポートはどう実装されているのか?
小さなプログラムでもバイナリサイズが大きくなってしまうのは?
未使用の変数/インポートに対するエラーを抑止できますか?
パフォーマンス
ベンチマークXにおいて、Go言語のパフォーマンスが良くない理由は?
C言語との違い
C言語と、これほど構文に違いがある理由は?
宣言を逆に入れ替えた理由は?
ポインタの演算がない理由は?
++と--が、式ではなくステートメントである理由は? 変数の前ではなく、後ろにある理由は?
波括弧があるがセミコロンがない理由は? 開始波括弧は次の行に置けないのか?
ガベージコレクションを採用した理由は? コストがかかり過ぎないか?

起源

このプロジェクトの目標は?

ここ10年以上、メジャーなシステム言語は誕生しておりませんが、その間にコンピュータの世界は大きく変化しています。この変化には、次の傾向が見受けられます。

  • コンピュータ自身は非常に早くなりましたが、ソフトウエア開発は遅いままです。
  • 今日のソフトウェア開発にとって依存関係を管理することはとても重要ですが、C言語で使われている「ヘッダファイル」では、依存関係を正しく解析したり、高速なコンパイルを行うことはできません。
  • JavaやC++のような扱いにくい型システムに対する反発が大きくなりつつあり、これがPythonやJavaScriptといった動的な型言語を後押ししています。
  • 基本的なコンセプトのうち、ガベージコレクションや並列処理などいくつかは、人気のあるシステム言語においてもほとんどサポートされていません。
  • マルチコアコンピュータの出現は、心配事と混乱を生み出しました。

我々は、コンパイルが早く、並列処理とガベージコレクションを持つ新しい言語に取り組むことに価値があると確信しました。この言語のポイントを説明します。

  • 一台のコンピュータ上で、大きなGo言語のプログラムをほんの数秒でコンパイルできます。
  • Go言語では、依存関係の解析を容易にし、C言語スタイルのインクルードファイルとライブラリで発生していたオーバヘッドの大部分を軽減したソフトウエア構築モデルを提供します。
  • Go言語の型システムには階層がないため、型間の関係性を定義することに時間を費やす必要はありません。また、Go言語は静的な型を持っているので、一般的なオブジェクト指向言語より気軽に新しい型を作成できます。
  • Go言語は、完全なガベージコレクションを実装しており、並列実行と通信を言語としてサポートしています。
  • Go言語では、マルチコアマシン上でシステムソフトウエアを構築する際に、この設計思想を活用してもらうことを推奨しています。

名前の由来は?

「Ogle」です。これは、Goデバッガの名前となりました。

このマスコットの由来は?

マスコットとロゴはRenée Frenchの手でデザインされています。彼女は、Plan 9のウサギのマスコットGlendaもデザインしています。このgopher(ホリネズミ)は、彼女が数年前にWFMUのTシャツのデザインとして使った内の1つから派生しています。このロゴとマスコットは、「Creative Commons Attribution 3.0」ライセンスで保護されています。

このプロジェクトの歴史は?

Robert Griesemer、Rob Pike、Ken Thompsonがホワイトボードに新しい言語の理想像を描き始めたのは、2007年9月21のことでした。それから数日の内に、そこから実行すべき計画と、実現すべき妥当なアイデアがまとめられました。言語の設計は、他の仕事の合間に進められました。アイデアを探るために、Kenはコンパイラの研究を2008年1月のころにはすでに開始していました。そのコンパイラは、C言語のコードを生成するものでした。その年の中頃には、この言語はフルタイムのプロジェクトになっており、またプロダクションコンパイラの試みとしても充分な域に達していました。2008年5月にIan Taylorは、ドラフト仕様を基に、新たにGo言語用GCCフロントエンドの作成に着手しました。Russ Coxが参加したのは、2008年後半で、言語とライブラリのプロトタイプから実装化する手伝いをしました。

2009年11月10日に、Go言語はオープンソースプロジェクトとなりました。コミュニティの大勢の方々から、アイデア、議論、コードを寄贈いただきました。

新しく言語を作った理由は?

Go言語は、既存言語とシステムプログラミング環境への不満から生まれました。プログラミングはあまりに複雑になり過ぎました。言語を選択することは部分的に責任を負います。開発者は、効率的なビルド、効率的な実行、容易なプログラミングのうちから選択を強いられます。主流の開発言語で、これら3つを満たすものはありませんでした。言語の選定が可能なプログラマは、C++や、(それよりは使われていない)Javaなどより、PythonやJavaScriptのような動的な型言語に移行しており、安全性や能率より容易にプログラミングできる言語が選ばれています。

Go言語は、インタプリタ言語のプログラミングの容易さと、動的型言語の効率、コンパイル言語の静的な型の安全性を併せ持つことを目指しました。また、Go言語は、近代的な言語としてネットワークとマルチコアコンピューティングをサポートしています。最後になりますが、性能を重視しており、大きな実行モジュールであっても、一台のコンピュータ上で数秒以内でビルドできることを目指しました。これらのゴールに到達するには、言語としての問題をいくつも 解決しなければなりませんでした。それは、多様な表現が可能で軽量な型システム、並列性とガベージコレクション、厳密な依存関係などです。これらは、ライブラリやツールでは対処できないため、新しい言語が必要でした。

Go言語の基になった言語は?

Go言語の大部分はC言語系(基本構文)であり、Pascal/Modula/Oberon系(宣言とパッケージ)からも多くが取り入れられ、加えてTony HoareのCSPの影響を受けているNewsqueak、Limbo(並列処理)などからもいくつかのアイデアが取り入れられています。しかし、Go言語は全く新しい言語です。Go言語は、プログラマが何を行い、どのようにプログラミングするか、少なくとも我々が作成している類のプログラムでは、より効果的になるよう、あらゆる点において考え抜き設計しいます。これはプログラミングがより楽しいものになることも意味しています。

設計方針は?

今日のプログラミングは、あまりに多くの冗長な記述、繰り返し、事務的作業を必要とします。Dick Gabrielは、こう言いました。「昔のプログラムは、コンパイラとの言い争いなどではなく、『丁寧な言葉遣いの研究員』と『マシンを熟知した同僚』との間で交わされる穏やかな会話のようだった。言語が改良された代償として、こんなノイズを誰が予想したのか?」  改良された言語には価値があり、誰も古い言語に戻りたくはありませんが、もっとノイズを減らせないのでしょうか?

Go言語では、型(type)とキーボードのタイピング(typing)量を減らしました。我々はGo言語の設計を通して、不要な物と複雑さを減らそうと試みました。前方宣言とヘッダファイルは存在せず、すべては一回だけ宣言されます。初期化では、様々な表現が可能で自動化されていて使いやすくなっています。構文は、キーワードにより読みやすくかつシンプルになっています。変数宣言時の型名の繰り返し(foo.Foo* myFoo = new(foo.Foo))は、宣言と初期化を兼ねた:=記号を使ったシンプルな形式の「型派生」によって短くなります。そして、おそらく最も本質的な点は、型に階層がないことです。型に、型同士の関係性を宣言する必要はありません。これらの簡略化は、Go言語の表現力を高め、理解し易くしますが、洗練さを犠牲にすることはありません。

もう一つの重要な原則は、コンセプトに一貫性を持たせることです。メソッドは、どんな型に対しても実装することができ、構造体はデータを表現し、インタフェースは抽象化を表現するといった具合です。一貫性によって、何かが組み合わされたときに、そこで起きていることを理解しやすくなります。

利用

Google社内で、Go言語が使われているか?

はい。現在、いくつかのGo言語のプログラムがGoogle社内で稼動しています。公開されている実例はhttp://golang.orgの裏で動いているサーバです。これはGoogle App Engine上で動作させているgodocドキュメントサーバです。

Go言語のコンパイラの実装は、gc(6gおよびそれに類するプログラム)とgccgoの2種類が用意されています。gcでは、特殊な関数の呼び出し方法とリンカを使っており、そのため、同じ呼出方法を使っているCプログラムとのリンクだけが可能です。この呼び出し方法に対応したCコンパイラはありますが、C++コンパイラは存在しません。gccgoは、GCCのフロントエンドであり、若干注意が必要ですがGCCでコンパイルされたCおよびC++プログラムとリンクが可能です。

cgoプログラムは、Go言語のコードからCライブラリの安全な呼び出しを可能にする「外部関数インタフェース」用のメカニズムを提供します。SWIGを使うと、C++ライブラリも扱えます。

Go言語は、Googleのプロトコルバッファをサポートしているか?

別のオープンソースプロジェクトで、必要なコンパイラのプラグインとライブラリを提供しています。これは、http://code.google.com/p/goprotobuf/から入手可能です。

Go言語のホームページを他の言語に翻訳してもよいか?

ぜひ。我々は、開発者がそれぞれの言語でGo言語のサイトを作ることを奨励しています。ただし、Googleのロゴまたは商標(golang.orgには表示されていません)をサイト上に表示させるときは、http://www.google.com/permissions/guidelines.htmlのガイドラインを守る必要があります。

設計

Unicode識別子とは?

識別子として使える文字をASCIIコードの範囲から広げることが、我々としては重要でした。Go言語のルール(識別子の文字は、Unicodeで定義されている文字または数字でなければならない)は、単純で分かりやすく、実装が容易ですが制約もあります。例えば、Unicodeの結合文字は、仕様として除外しています。識別子として使用可能な文字を外部定義しており、それに加えて、文字を正規化した定義(曖昧にならないことを保証していること)が用意されるまでは、結合文字を混在させない方が良いと思っています。このように、あとから拡張可能かつシンプルなルールをもっているので、あいまいな識別子を許すようなルールに起因するバグによるプログラムの中断を防ぎます。

これらと関連しますが、エクスポートされる識別子は、先頭の文字が大文字でなければならないため、特定の言語の「文字」を使った識別子は仕様上、エクスポートすることができません。今のところの唯一の解決策は、X日本語のようにアルファベットを使うことですが、これでは明らかに不十分です。これについて、我々は他の解決策を模索中ですが、この大文字のときエクスポートするというルールを変えるつもりはありません。我々が、Go言語のこの部分を特に気に入っているからです。

機能Xを実装していない理由は?

あらゆる言語において、新しい機能を取り入れる一方で、誰かのお気に入りの機能が省かれます。Go言語は、手際のよいプログラミング、コンパイル速度、コンセプトの一貫性、必要性の高い並列処理とガベージコレクション機能、といった視点で設計されています。お気に入りの機能が実装されていない理由は、相応しくない、コンパイル速度に影響する、仕様を明確にできない、システムモデルの基礎として作成するのがとても難しいといった理由です。

Go言語が機能Xを持たないことで、あなたを悩ませているなら、どうか容赦いただき、Go言語が持っている機能について調べてみてください。きっと機能Xを代用できる他の方法が見つかることと思います。

Generic型がない理由は?

どこかの時点で、ジェネリックは追加されることでしょう。我々は、ジェネリックを急いで実装する必要はないと思っていますが、一部のプログラマにとって必要であることは理解しています。ジェネリックは便利ではありますが、型システムとランタイムに複雑さによるコストをもたらします。我々は、この複雑さに見合うだけの価値を持つ設計のアイデアをまだ見つけることができていませんが、これからも継続して考えていきます。

差し当たっては、Go言語に組み込まれているマップとスライス、および空インタフェースを使ったコンテナ(明示的なunboxingが必要)を利用することで、ほとんどの場合、ジェネリックで実現可能なことと同等のコードを如才なくとはいきませんが記述することが可能です。

この問題は、未解決のままにしておきます。

例外(exception)がない理由は?

我々は、処理構造を制御するためのtry-catch-finally形式の例外処理機構によって、コードが入り組んでしまうと考えています。しかも、ファイルを開けないといった、ごく一般的なエラーをさも特別なエラーであるかのように扱わせる傾向があります。

Go言語では、異なるアプローチを取りました。Go言語では戻り値として複数の値が返せるので、一般的なエラーハンドリングの場合、戻り値といっしょにエラー情報を返すことができます。エラー型は標準化されており、Goの他の機能と相まってエラーハンドリングがすっきりしたものとなります。これは、他の言語と大きく異なる点です。

それとは別に、Go言語にはエラーシグナルの発行機構と、本当に例外的な状況から回復する機構があります。エラーの発生によって、関数のコールスタックの巻き戻りが開始する中でのみ、このエラー回復メカニズムは実行可能です。このメカニズムは、大きな障害をハンドリングするのにも充分な上に、処理構造に特別な制御を行う必要もありません。また上手に使えば、エラーハンドリングのコードが読みやすくもなります。

詳細は、Defer, Panic, and Recover(英語)の記事を参照ください。

アサート(assert)がない理由は?

Go言語では、アサートを提供していません。アサートが便利である点は疑う余地はありませんが、我々の経験的には、プログラマはアサートを、適切なエラーハンドリングとエラーレポートを考慮せずに済ますためのツールとして使っています。適切なエラーハンドリングとは、致命的ではないエラーが起きたときにクラッシュさせずに、処理を継続させることです。また、適切なエラーレポートとは、そのレポートされたエラーが直接的かつ適切な内容で、プログラマが巨大なクラッシュトレースに対して行う調査を手助けします。正確なエラー情報は、エラーを調べているプログラマがコードを熟知していないときは、特に重要です。

我々は、これが議論の対象となることは理解しています。Go言語とそのライブラリには、皆が過去に実践してきたこととの違いが数多くありますが、これは単に、我々が異なるアプローチを試みることを常に意識しているからです。

並列性がCSPをベースにしている理由は?

並列性とマルチスレッドプログラミングは難易度が高いと言われています。これらはpthreadsなどの複雑な設計、ミューテックスなど低レベルな細部にまで意識し過ぎであること、変数の状態、メモリバリアに原因があると我々は考えています。高レベルのインタフェースは、たとえミューティックスがその下に隠されていたとしても、コードをよりシンプルなものにします。

並列性のサポートを言語的に高レベルで提供するモデルのうちで、最も成功しているものは、Hoareの「Communicating Sequential Processes」、すなわちCSPに由来します。OccamとErlangは、2つともCSPから生まれた有名な言語です。Go言語の並列性プリミティブは、その系図の他のところに由来しており、そこから、ファーストクラスオブジェクトとしてのチャネルの強力なアイデアを受け継ぎました。

スレッドではなくゴルーチンである理由は?

ゴルーチンは、並列性を扱いやすくするための重要な機能です。複数スレッドの上で、多重かつ独立し実行される関数というアイデアは以前から在りました。例えば、ブロッキングシステムコールを呼び出すことでコルーチンがブロックするとき、ランタイムは自動的に、同一オペレーティングシステムスレッド上の他のコルーチンも一緒にブロックされないように、他の実行可能なスレッド上に移動します。プログラマがこの動作を意識しない点が重要です。ゴルーチンを呼び出すコストは、非常に小さく抑えることができます。ゴルーチンが時間の掛かるシステムコールに多くの時間を費やさない限り、スタックとして使用する数キロバイト程度のメモリ以外には、ほとんどコストがかかりません。

スタックサイズを小さくするために、Go言語のランタイムはセグメントスタックを使用しています。生まれたばかりのゴルーチンには、数キロバイトのスタックが与えられますが、たいていはそれで足ります。不足したときは、ランタイムが自動的に拡張セグメントの割り当て(と開放)を行います。このオーバヘッドは、一回の関数呼び出しにつき、平均して3つの安価なインストラクションしか必要としません。同一アドレス空間で何十万ものゴルーチンを作成することも、実際に可能です。ゴルーチンとスレッドが同じものであるならば、これよりもっと少ない数でシステム資源の上限に達してしまうことでしょう。

マップ操作がアトミックに行われない理由は?

長い議論の末、典型的なマップの使用方法では複数のスレッドからの安全なアクセスは必要なく、安全なアクセスが必要なケースにおいては、マップは他の大きなデータ構造の一部であったり、すでに同期されている計算処理の一部であるとの結論に至りました。したがって、すべてのマップ操作でミューテックスを取得する必要があるならば、大多数のプログラムの動作を遅くし、安全性の恩恵を受けるのは一部のプログラムです。同期制御されないマップへのアクセスがプログラムをクラッシュさせる可能性があるため、この決断は簡単ではありませんでした。

言語上は、アトミックなマップ更新を禁止はしていません。必要に応じて、例えば、信頼できないプログラムをホストするようなとき、ランタイムの実装によってマップアクセスをお互い連動させることも可能です。

Go言語はオブジェクト指向言語か?

Yesであり、Noでもあります。Go言語は型とメソッドを持ち、オブジェクト志向スタイルでプログラミングが可能ですが、型の階層はありません。Go言語における「インタフェース」のコンセプトは、他の言語と異なるアプローチを取っており、簡単に使え、いくつかの点で他の言語より一般的であると我々は思っています。型に、別の型を埋め込むことでサブクラスのように機能の提供を受ける方法もありますが、これはサブクラスと同じではありません。その上、Go言語のメソッドはC++やJavaよりも普遍的であり、どのようなデータ、たとえばビルトイン型である整数型に対してもメソッドが定義可能です。構造体(クラス)である必要はありません。

また、型階層を持たないことによりGo言語の「オブジェクト」は、C++やJavaのような言語と比べて軽量です。

メソッドを動的に呼び出す方法は?

メソッドを動的に呼び出すには、インタフェース経由で呼び出すしかありません。構造体型などのコンクリート型に実装されたメソッドは、常に静的に解決されます。

型の継承を採用しなかった理由は?

オブジェクト指向のプログラミング、少なくとも名の通った言語においては、大抵の場合は自動的に型同士の関係性を抽出できるのに関わらず、必要以上に型の関連性について検討をしなければなりません。そこでGo言語では、異なるアプローチを取りました。

Go言語では、2つの型に関連性があることを事前に宣言させる必要はなく、ある型が所有しているメソッドの一部分を規定している全てのインタフェースを、その型が自動的に満たしていることになります。このアプローチには、不要な記述を減らす以外にも、真の利点があります。型は一度に数多くのインタフェースを従来の多重継承の複雑さを必要とせずに満たすことができます。インタフェースはとても軽量です。メソッドをひとつしか持たない、もしくはひとつも持たないインタフェースをコンセプトを表現する手段としたり、また、後から思いついた新しいアイデアやテストのためにインタフェースを後付することも可能です。この場合、実装先の型に手を加える必要はありません。型とインタフェース間には明示的な関連性がないため、管理や検討が必要となる型階層は存在しません。

この考え方を、型セーフなUnixパイプのように利用できます。それの例として、fmt.Fprintfはフォーマットした文字列をファイルだけでなく様々な出力先に出力できること、bufioパッケージがファイルI/Oとは完全に分離されていること、imageパッケージが圧縮画像ファイルを生成することに役立っています。これらの仕組みは全て、ひとつのメソッド(Write)を持ったひとつのインタフェース(io.Writer)を利用しています。これらはほんの一例です。Go言語のインタフェースはプログラムの「造り」に大きな影響を及ぼしています。

これには少々慣れが必要となりますが、この暗黙的な型の依存スタイルは、Go言語のなかでも最も大きな成果の一つでもあります。

lenが、メソッドでなく関数である理由は?

我々はこの問題を議論しましたが、lenとその類を関数として実装することにしたのは、実際その方が優れていることと、基本的な型が実装しているインタフェースを複雑にしないためです。

メソッドと演算子のオーバロードをサポートしていない理由は?

メソッドを呼び出す際に、メソッドの型をいちいち調べしなくてよい方がシンプルです。他の言語に接した経験から言えることは、同じ名前で、かつシグネチャが異なるメソッドの寄せ集めを持つことは、ときに役に立ちますが、混乱を招くだけで充分に機能しません。型の中で、メソッドが名前だけで検索できるよう制約を課すことは、Go言語の型システムを単純な仕組みにする上での大きな決断でした。

演算子のオーバーロードに関しては、絶対に必要というより、あれば便利という程度であり、採用しないことでシンプルさを保ちました。

Go言語に「implements」宣言がない理由は?

Goの型がインタフェースを満たすには、そのインタフェースのメソッドを実装する以外、何も必要ありません。この特性により、既存のコードを修正せずにインタフェースを規定・使用することを可能にします。これは、関連性の分離とコードの再利用を促進する「ダック・タイピング」の一種で、コード開発時のパターン構築を容易にします。このインタフェースの仕組みは、Goの小回りと軽量さに大きく寄与しています。

詳細は、型の継承についての質問を参照ください。

自作の型がインタフェースを満たしていることを保証するには?

TがインタフェースIを実装しているかは、代入を行うことでコンパイラにてチェックできます。

type T struct{}
var _ I = T{} // TがIを実装しているか検証

TIを実装していなければ、誤りはコンパイル時に判明します。

インタフェースの利用者に対し、そのインタフェースを実装していることを明示的に宣言してもらいたいときは、次の例のように説明的な名前を持つメソッドをインタフェースのメソッド群に加えます。

type Fooer interface {
    Foo()
    ImplementsFooer()
}

この場合、型をFooerとするためにはImplementsFooerメソッドを実装しなければなりませんので、きちんとドキュメント化し、godocの出力で宣言する必要があります。

type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}

このような制約はインタフェースの利便性を損なうため、大抵のコードでは行われませんが、時として類似したインタフェースの曖昧さを解決する手段として必要となります。

T型は、なぜEqualインタフェースを満たしていないのか?

次のように、あるオブジェクトが自分と他の値とを比較できることを表した、ごく単純なインタフェースがあるとします。

type Equaler interface {
    Equal(Equaler) bool
}

さらに次のT型を用意します。

type T int
func (t T) Equal(u T) bool { return t == u } // Equalerを満たしていない

ポリモーフィックな型システムを持つ他の言語の場合とは異なり、TEqualerを満たしません。T.Equalの引数の型はTであって、Equalerを満たすために必要なEqualer型ではないためです。

Go言語の型システムでは、Equalの引数の型を自動的にEqual型へ昇格するようなことはしません。プログラマーは下に例示するT2型の方法でEqualerを実装しなくてはいけません。

type T2 int
func (t T2) Equal(u Equaler) bool { return t == u.(T2) }  // Equalerを満たす

これもまた他の型システムとは違い、Equalerを満たすどのような型でもT2.Equalの引数として渡せるため、引数がT2型であることを実行時にチェックしなくてはいけません。一部の言語では、コンパイル時にこういった型の保証を行うものもあります。

次は、まったく別の例です。

type Opener interface {
   Open(name) Reader
}

func (t T3) Open() *os.File

Go言語ではT3Openerを満たしませんが、他の言語では満たすかも知れません。

このような場合、プログラマにとってGo言語の型システムは見劣りするのは事実ですが、型を継承するサブ型がないことでインタフェースを満たすためのルールがとても簡単になりました。このルールとは、「関数名とシグネチャが、インタフェースが持つそれらと完全一致するか?」だけです。またGo言語のルールは効率的な実装がし易くもあります。型の自動昇格がないという欠点をこれらの利点で補っていると我々は考えています。いつかGo言語が、何らかのジェネリック型を採用するときには、先ほどまでの例におけるアイデアを実現する方法が存在し、かつ静的チェックが行えるようになるだろうと思います。

[]Tを[]interface{}に変換できるか?

メモリ上の表現が異なるため直接は変換できません。要素を個々に変換先のスライスへコピーする必要があります。int型のスライスをinterface{}型のスライスへコピーするには、下の例のようにします。

t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
    s[i] = v
}

nilとerror型のnilとがイコールにならないのは?

内部的にインタフェースは「型」と「値」の2要素から成ります。この「値」は実際の任意な値であり、インタフェースの「動的な値」と呼ばれます。「型」はその値の型です。仮に、あるインタフェース値がint型の3であるとき(int, 3)と表すとします。

インタフェースの値がnilとなるのは、内部の値と型がともにセットされていないとき(nil, nil)だけです。詳しく述べれば、nilインタフェースは常にnil型を保持しています。ポインタ型である*intをインタフェース内に格納したとき、ポインタの値に関わらず内部の型は*int(*int, ?)になります。ポインタの値がnilであっても、このインタフェース値はnilにはなりません。

このようなケースは混乱を招き、errorのようなインタフェース型の値にnil値を格納して返すときにしばしば問題となります。

func returnsError() error {
	var p *MyError = nil
	if bad() {
		p = ErrBad
	}
	return p // 常に非nilであるerrorが返る
}

正常時にこの関数はnilであるpを返しますが、戻り値は(*MyError, nil)を保持しているerrorインタフェース型の値です。そのため呼び出し側で戻されたerrornilとを比較すると、エラーが起きていなくても常にエラーが起きたと判断されてしまいます。error型であるnilを呼び出し元に適切に返すには、関数側で明示的にnilを返す必要があります。

func returnsError() error {
	if bad() {
		return ErrBad
	}
	return nil
}

関数内でエラー情報が正しく作成されることを保証するためには、*MyErrorのような具体的な型ではなく、関数のシグネチャにerror型を使用して(上で行ったように)エラー情報を返す方がよい考えです。例としてos.Openが返すerrorは、nilでないときは必ずコンクリート型の*os.PathErrorを返します。

インタフェースが使われる箇所では、ここで説明したのと同様のことが起こりえます。インタフェースに具体的な値が格納されたときは、インタフェースの値はnilとならないことを覚えておいてください。詳しくはThe Laws of Reflection(未訳)を参照ください。

C言語のようなunion(タグ無し)がない理由は?

union(タグ無し)は、メモリの安全性を保証できません。

バリアント型がない理由は?

バリアント型(もしくは代数型)で提供しようとしていたのは、型のセット内のいずれかの型の値として取り出せることを指定する方法です。一般的なシステムプログラミングにおけるバリアント型の使用例としては、エラーの種類(ネットワークエラー、セキュリティエラー、アプリケーションエラーなど)を特定できるようにすることで、呼び出し側はそのエラーの種類から問題の原因を判別できます。別の例として、構文ツリーの各ノードは、それぞれ異なる種別(宣言、ステートメント、代入等)となり得ます。

我々は、Go言語にバリアント型を取り入れることを考えましたが、バリアント型はインタフェースと重複する部分があり混乱を招くため議論の末に除外することにしました。例えば、バリアント型に格納された実際の値が、そのバリアント型のインタフェースと同じだったらと考えていただくとわかりやすいでしょう。

また、バリアント型のアドレスとほぼ同じものははすでに言語としてカバーされていますので、先ほどのエラーの例を使うと、エラーを保持するためにインタフェースの値を使い、型スイッチでエラーの種類を判別させれば簡単に実現できます。構文ツリーの例も同様ですが、あまり良い方法とは言えません。

暗黙的な数値変換機能を提供しない理由は?

C言語において、数値型同士の自動変換の利便性より、それが引き起こしてしまう混乱の方が大きく、たとえば、式がいつから「符号なし」になってしまったのか、どちらのサイズの方が大きいのか、オーバーフローを起こさないのか、どのマシン上で実行しても変換結果がマシンに依存することなく同じになるか、といった問題が起きます。それと同時に、コンパイラを複雑にしてしまいます。「一般的な算術変換」を実装するのは容易でなく、マシンのアーキテクチャ間で矛盾を起こします。移植性の理由から、これらを明確にするため、コード内で明示的な変換を直接行うことはやむを得ないと結論しました。Go言語の定数の定義では、値の精度、符号、サイズの指定が不要ですが、それでも問題はかなり改善されます。

これと関連した話ですが、C言語と異なり、Go言語では、たとえintが64ビット型であるとしても、intint64は別の型です。int型はジェネリックではありませんので、整数が何ビットまで値を保持できるのか気がかりであれば、明示的なサイズを持った型を使うことを推奨します。

マップが言語に組み込まれている理由は?

これは文字列と同じ理由からで、これらは強力かつ重要なデータ構造であり、文法的にもサポートされている優れた実装は、プログラミングをより快適にします。我々は、Go言語のマップの実装が、大多数の用途として充分に強力であると思っています。独自にマップを実装した方が有益であると判断すれば、アプリケーション個別に実装することは可能ですが、構文的に使い易いものとはなりません。このトレードオフは理にかなっていると思われます。

スライスをマップのキーに使用できない理由は?

マップからの検索にはイコール演算子を必要としますが、スライスにはイコール演算子が実装されていません。実装されていない理由は、スライスには「等価条件」が定義できないためです。考慮すべき問題が複数あり、たとえば、シャローとディープ比較、ポインタと値の比較、再帰的構造体の扱いなどです。我々は、この問題(と、スライスにイコールを実装したことで、既存のプログラムに影響を及ぼさないこと)を再検討するかもしれませんが、今のところスライスのイコールの扱いについて解決策がないため、これらを使えないようにしました。

事前リリースとは異なり、Go1からは構造体と配列の等価条件が定義され、マップのキーとして使用可能になりました。しかしスライスにはまだ等価条件が定義されていません。

配列は値であるが、マップ・スライス・チャネルが参照型である理由は?

この話題には、多くの経緯があります。初期の頃は、マップとチャネルは構文的にはポインタであり、宣言を行ったり、非ポインタのインスタンスとして扱うことはできませんでした。またそのころ、配列がどう機能すべきかについて検討していました。その結果、我々はポインタと値を厳密に分離すると、使いにくい言語になるとの結論に達しました。参照型(配列の参照を扱うスライスを含む)を導入することで、これらの問題を解決しました。残念ながら、参照型は言語に若干の複雑さを与えてしまいますが、ユーザビリティには大きく貢献します。これらが導入されたことで、Go言語はより生産的で快適な言語になりました。

コードの記述

ライブラリからドキュメントをどうやって作成しているか?

Go言語で記述されたgodocというプログラムがあり、これはソースコードからパッケージドキュメントを抽出します。このプログラムは、コマンドラインまたはウェブ上で使用できます。http://golang.org/pkg/に実行中のインスタンスがあります。godocは実際には、http://golang.org/のサイト全体を動かしています。
[訳注:ライブラリのドキュメントの日本語訳は、http://golang.jp/pkgにあります]

Go言語のプログラミングスタイルガイドはあるか?

結局のところ、命名規則、配置、ファイル構成といったルールが若干あります。また「実践Go言語」には、スタイルについてのアドバイスがいくつか記述されています。より直接的な話をすると、gofmtプログラムをソースコードに使うと、強制的にスタイルルールを適用したソースコードが得られます。このプログラムを実行することが、解説の代わりであり、解説を不要としています。リポジトリ内のすべてのGo言語のコードは、gofmtを通しています。

Go言語のライブラリにパッチをサブミットするには?

ライブラリのソースは、go/src/pkgにあります。重要部分に変更を行いたいときは、着手する前にメーリングリストで議論してください。

手順についての情報は、Contributing to the Go project(未訳)を参照ください。

ポインタとアロケーション

関数パラメータは値渡しか?

C言語系の言語と同様にGo言語ではすべて値渡しです。すなわち関数は常に値をパラメータに代入する代入ステートメントがあるかのように、渡されたもののコピーを受け取ります。たとえば関数にint型の値を渡すとint型のコピーが作成され、ポインタの値を渡すとポインタのコピーが作成されますが、ポインタが指し示すデータがコピーされるのではありません。(これがメソッドのレシーバにどのような影響を及ぼすかは、次のセクションを参照ください)

マップおよびスライスの値は、ポインタのように振舞います。これらは、内部のマップまたはスライスデータへのポインタを持つデスクプリタです。マップまたはスライスの値をコピーしても、それが指し示すデータは複製しません。インタフェースの値をコピーすると、そのインタフェースの値に格納されているものをコピーします。インタフェースの値が構造体を格納しているときに、そのインタフェースの値をコピーすると構造体のコピーを製作します。インタフェースの値がポインタを格納しているときに、そのインタフェースの値をコピーすると、これもまたポインタが指し示すデータではなく、ポインタのコピーを作成します。

値とポインタどちらでメソッドを定義すべきか?

func (s *MyStruct) pointerMethod() { } // ポインタメソッド
func (s MyStruct)  valueMethod()   { } // 値メソッド

ポインタに慣れていないプログラマにとっては、例示したこれら2つ違いは混乱を招くかも知れませんが、違いはとても単純です。型にメソッドを定義する際、レシーバ(上の例のs)はメソッドの引数と同じ扱いです。レシーバを値/ポインタどちらとして定義すべきかは、関数の引数が値/ポインタのどちらであるべきかという問題と同じです。ここに、いくつか考慮すべき点があります。

まず最初に、そして特に重要なのはメソッドがレシーバの値を変更する必要があるかです。必要であるならレシーバはポインタにしなくてはいけません。(スライスとマップは参照型なので、これらの扱いは少し異なりますが、メソッド内でスライスの長さを変更するには、やはりレシーバはポインタでなければなりません。) 先ほどの例のpointerMethodで、sのフィールドを変更した場合は、呼び出し元でその変更を参照できますが、valueMethodは呼び出し元の引数のコピーと共に呼び出される(値渡し)ので、呼び出し元からは参照できません。

話は変わりますが、ポインタレシーバはJava言語の場合と同じ扱いですが、一方Javaのポインタ自体は覆い隠されていて直接ポインタとして扱えません。どちらかというとGo言語の値レシーバのほうが特殊です。

2番目は、効率への考慮です。レシーバが巨大なとき(例えば大きなstructなど)は、ポインタレシーバを使用したほうが安上がりです。

もう一つは、一貫性です。もし、ある型のメソッドがポインタレシーバを持たなければならない場合、その型がどのように使われるかにかかわらず、メソッドの群として一貫性を持たせるためレシーバはポインタにすべきです。メソッド群のセクションを参照ください。

例えば基本型、スライス、小さなstructのような型の場合は、値レシーバはとても低コストであり、メソッドがポインタを必要としない限り、値レシーバのほうが効率的で明快です。

newとmakeの違いは?

手短に言えば、newはメモリをアローケートし、makeは、スライス、マップ、チャネル型の初期化を行います。

詳細は、実践Go言語の関連するセクションを参照ください。

64ビットマシン上でint型が32ビットである理由は?

intuintのサイズは実装に依存しますが、特定のプラットホーム上ではお互い同じサイズです。64ビットのGoコンパイラ(gcとgccgo)では、intに32ビット表現を使います。値が特定のサイズであることに依存しているコードでは、int64のように明示的にサイズが決められている型を使わなければなりません。一方、浮動小数点スカラーと複素数は、float32complex64等、常にサイズが指定されています。これは浮動小数点数を扱うとき、プログラマは精度を知っていなければならないためです。浮動小数点の定数のデフォルトのサイズは、float64です。

intのサイズは本来任意ですが、現時点ではすべてのGo言語の実装において32ビットが使用されています。しかし、Go言語の将来のリリースでは64ビットアーキテクチャ上では64ビットに拡張されるだろうと考えています。

変数がヒープ、スタックのどちらに割り当てられているか知りたい

正確さを期するなら知る必要はありません。Goの各変数は参照されている限り存在し続けます。Goの実装によって選択された格納場所がどこかは、Go言語的には意味を持ちません。

効率の良いプログラムを記述するとき、格納場所は重要となります。Goコンパイラは可能であれば、関数のローカルな変数にその関数のスタック・フレームを割り当てます。ただし、関数から復帰した後に、その変数が参照されることがないとコンパイラが判断できないときは、ポインタの示す先が参照できなくなることを避けるため、ガベージコレクト対象のヒープ領域に変数を割り当てなくてはなりません。また、ローカル変数が非常に巨大になるときは、スタックよりヒープ上に格納したほうが理にかなっているかもしれません。

現時点のコンパイラでは変数のアドレスが取得される場合にその変数はヒープ上に割り当てる候補となります。ただし基本的なエスケープ解析では、関数から復帰後は生存しない変数などを検知するとスタック上に割り当てます。

並列処理

どの操作がアトミックか? ミューテックスについては?

我々は、これについてまだ完全には定義できていませんが、アトミックについての詳細をGoのメモリモデルにいくつか記述があります。

ミューテックスに関しては、syncパッケージで実装していますが、我々が望むGo言語のプログラミングスタイルとしては、それより高レベルな技術を試すことを推奨します。特に、プログラムの構築の際には、一度にひとつのゴルーチンだけが、データの特定部分に対して管理責任を持つよう検討してください。

共有メモリを使って通信しないでください。それとは逆に、通信によってメモリを共有してください。

Share Memory By Communicating(英語)と、この概念の詳細を解説した付随記事(英語)を参照ください。

ゴルーチンを複数使ったプログラムが、複数のCPUを利用しない理由は?

ランタイムサポートにOSのスレッドを複数利用させるには、シェル環境変数GOMAXPROCSをセットするか、もしくはruntimeパッケージの同名の関数(runtime.GOMAXPROCS)を使用する必要があります。

並列処理を行うプログラムは、GOMAXPROCSを増やしたことによる恩恵を受けるでしょう。

GOMAXPROCS > 1にすると、プログラムが遅くなることがある理由は?

これはプログラムの「造り」によります。本質的に連続した処理はゴルーチンを増やしても速度を上げることはできません。プログラムが並列処理するよう作成された場合にのみ並列に処理されます。

実際の例で言うと、複数スレッドを使用する場合、チャネル通信に時間を費やすプログラムは計算処理を行うプログラムよりパフォーマンスが落ちます。これはスレッド間でデータを送信する際にコンテキストを切り替える必要があり、そのコストが大きいからです。例えば、Go言語仕様の「素数のふるい」では、多くのゴルーチンを起動していますが並列処理としては意味がなく、GOMAXPROCSを増やしても性能が上がるどころか下がる可能性があります。

Go言語のランタイムスケジューラの出来は、充分なレベルに達していません。今後、こういったケースを認識した上で、OSスレッドの使い方を最適化して行かなければなりません。ここしばらくは、アプリケーション毎にGOMAXPROCSを設定する必要があり続けます。

関数とメソッド

Tと*Tとが、異なるメソッド群を持つのは?

Go言語仕様より抜粋:

インタフェース以外の型を仮にTとすると、そのT型のメソッド群はT型のレシーバを持ったすべてのメソッドです。ポインタ型*Tに対応するメソッド群は、*TまたはT型のレシーバを持ったすべてのメソッドです。(すなわちT型が持つメソッド群が含まれます。)

インタフェースに格納された値がポインタ*Tのときは、メソッドを呼び出す際にポインタを間接参照することで値を得ることができますが、インタフェースに格納された値が値Tのときは、メソッドを呼び出す際にポインタを得る手段はありません。

仮にコンパイラが、メソッドを呼び出すために値からアドレスを自動的に取得できたとして、コピーされた値に対しメソッド内で変更を加えてもその変更は呼び出し元には反映されません。次のコードはその典型的な例です。

var buf bytes.Buffer
io.Copy(buf, os.Stdin)

上のコードはコンパイルエラーとなります。io.Copy関数の第一引数はポインタでなければならないからです。このコードがコンパイルを通ってしまうのであれば標準入力をbufにではなく、bufのコピーに対してコピーします。これは全く望んでいない動きです。

クロージャをゴルーチンとして動かすとどうなるか?

並列にクロージャを使うときには、少し困惑させてしまうかもしれません。下のプログラムをみてください。

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done 
    }
}

abcと出力されると思われるかもしれませんが、その代わりに多分、cccと出力されるはずです。これはループの繰り返し部分で変数vの同一インスタンスを使っており、各クロージャでは一つの変数を共有しているためです。実行されたクロージャーは、fmt.Printlnを実行した時点のvの値を出力しますが、vの値はゴルーチンが起動された時点の値とは変わっています。

ゴルーチンを起動したときのvの値を、各クロージャと結びつけるために、下のようにループ内を変更します。

    for _, v := range values {
        go func(u string) {
            fmt.Println(u)
            done <- true
        }(v)
    }

この例では、vの値を引数として匿名関数に渡しています。渡された値はこの関数内で、変数uとしてアクセス可能です。

制御フロー

Go言語に、?:演算子はあるか?

三項演算子は、Go言語にはありません。下のようにすれば、三項演算子と同じ結果が得られます。

if expr {
    n = trueVal
} else {
    n = falseVal
}

パッケージとテスト

複数のファイルから成るパッケージを作成するには?

パッケージ内の全ソースファイルを、パッケージディレクトリに置いてください。ソースファイルから、別ファイル内のアイテムを自由に参照可能です。これには、事前の宣言やヘッダファイルは不要です。

これは、パッケージが複数のファイルに分割されただけであって、ひとつのファイルで構成されたパッケージと全く同じようにコンパイルやテストが可能です。

単体テストの書き方は?

パッケージソースと同じディレクトリに、ファイル名が_test.goで終わるファイルを作成してください。このファイル内にimport "testing"と、次の形式の関数を記述してください。

func TestFoo(t *testing.T) {
    ...
}

そのディレクトリでgo testを実行してください。このスクリプトがTest関数を探して、テスト用のバイナリをビルドし、実行を行います。

testingパッケージおよびgo testサブコマンドの詳細ドキュメントは、Goコードの書き方を参照ください。

テスト用のヘルパー関数がみあたりません

Go言語の標準testingパッケージは単体テストの記述を容易にしますが、他の言語のテストフレームワークで提供されている、例えばアサーション関数などがありません。このドキュメントの前の方のセクションでGo言語がアサートを持たない理由を説明しましたが、テストにおけるアサートについても同じ議論があてはまります。

適切なエラーハンドリングをしていれば、一箇所でエラーとなってもテストが継続できるので、デバッグしている人はエラーとなった箇所すべてを一度に得られます。たとえば、「isPrime関数(素数判定)に2を与えたときの回答が間違っている」とレポートして以降のテストを中断するより、「2, 3, 5, 7(または2, 4, 8, 16)を与えたときの回答が間違っている」とレポートする方が役立ちます。エラーを発生させたテスト実施者は、そのエラーを起こしたコードに詳しくないこともあります。良いエラーメッセージを記述するために費やした時間は、後にテストを中断することになる時間と充分見合います。

これらと関連しますが、一般的なテストフレームワークにおいて条件・制御・出力機構を持つ専用のミニ言語が用意される傾向がありますが、Go言語にはすでにこれらが備わっています。これらを再び作成するより、我々はGo言語のテストを進めたかったのです。このようにしたことで余計な言語を覚える必要がなくなり、テストを直接的かつ理解しやすくしています。

優れたエラーを書くために必要なコードの量が膨大かつ冗長なときは、入出力をデータ構造として定義したリストをイテレートする「テーブル駆動型」のテストにするのもよいでしょう。(Go言語ではデータ構造をリテラルとして簡単に記述できます) 優れたテストコードおよびエラーメッセージを書く作業は、その後のテスト実施時に報われます。たとえばfmtパッケージにおける書式化テストなど、標準Go言語ライブラリには多くの実例があります。

実装

Go言語のコンパイラのビルドには、どのコンパイラを使っているのか?

gccgoは、再帰下降パーサを使うC++フロントエンドを持ち、標準GCCバックエンドに結合されています。gcは、C言語で書かれており、yacc/bisonをパーサとして使っています。これは新規に作られたプログラムではありますが、Plan 9 Cコンパイラスイート(http://plan9.bell-labs.com/sys/doc/compiler.html)に適合しており、Plan 9系のローダを使ってELF/Mach-O/PEバイナリを生成します。

我々は、オリジナルのGo言語コンパイラgcをGo言語自身で書くことを考えましたが、ブートストラッピングと、取り分けオープンソースの配布の難しさ(Go言語の環境のセットアップするためにGo言語のコンパイラが必要になる)から断念しました。後発のgccgoによって、コンパイラをGo言語で記述可能になったので、実装が行われています。(Go言語はコンパイラを実装するには、最適な言語であり、既にGo言語を直接解釈できるlexerとparserがgoパッケージに用意されています)

我々はgcにLLVMを使うことも考えましたが、LLVMは大きすぎて遅く、パフォーマンス目標を達せそうにありませんでした。

ランタイムサポートはどう実装されているのか?

再びブートストラッピング問題を取り上げます。ランタイムコードの大部分はC言語(と部分的にアセンブラ)ですが、現在では、大部分をGo言語で実装できるようになっています。gccgoのランタイムサポートにはglibcを使っています。一方gcでは、フットプリントのコントロール権を持ち続けるために独自のライブラリを使用しています。これはゴルーチンのセグメンテッド・スタックをサポートするPlan 9 Cコンパライラとともにコンパイルされます。gccgoコンパイラはgoldリンカで最近サポートされるようになったセグメンテッド・スタックをLinuxでのみ実装しています。

小さなプログラムでもバイナリサイズが大きくなってしまうのは?

gcツール・チェーン(5l6l8l)のリンカは静的リンクを行います。このため、すべてのGoバイナリには、動的型チェック、リフレクション、パニック時のスタックトレース時に必要となる実行時の型情報と一緒に、Goのランタイムが含まれています。

C言語の簡単な「hello, world」プログラムをgccを使いLinux上でコンパイル・静的リンクを行うと約750kBであり、これにはprintfの実装も含まれています。fmt.Printfを使った同様のGo言語のプログラムでは約1.2MBですが、これには高機能なランタイムサポートが含まれています。

未使用の変数/インポートに対するエラーを抑止できますか?

使用していない変数はバグの要因となり、使用していないインポートはコンパイル速度を低下させます。コードツリーに未使用のインポートが増えるととても遅くなります。このため、Go言語ではどちらも許していません。

コードの開発時には、一時的にこのような未使用の変数/インポートがある状態になるのは普通であり、プログラムが完了する前に、これらを取り除かなければならないことは腹立たしく思うでしょう。

何名かがこのチェック機能をオフにするか、もしくはワーニングに落とすためのコンパイルオプションの必要性を訴えました。コンパライラ・オプションが言語仕様に影響を及ぼしてはならないこと、およびGo言語のコンパイラが出力するのはコンパイルの妨げとなるエラーだけで、ワーニングは出力しないため、このようなオプションが加えられることはありませんでした。

ワーニングを出力しないのには2つの理由があります。ひとつ目は、ワーニングを発生させる価値があるなら、同様にコードを修正する価値もあるはずです。 すなわち、修正する価値がないなら、報告する必要もありません。ふたつ目は、コンパイラにワーニングを出力させると、不必要なワーニングが多く出力されるようになり、これによりコンパイル時のノイズが増え、修正されるべき本当のエラーを覆い隠してしまいます。

しかし、このエラーへの対処は簡単です。開発中に未使用の変数/インポートを残しておくにはブランク識別子を使ってください。

import "unused"

// これは、パッケージからアイテム参照するために
// インポートしたことの印として宣言しています。
var _ = unused.Item  // TODO: コミット前に削除すること!

func main() {
    debugData := debug.Profile()
    _ = debugData // デバッグ中しか使用しない
    ....
}

パフォーマンス

ベンチマークXにおいて、Go言語のパフォーマンスが良くない理由は?

Go言語の設計のゴールのひとつは、同等のプログラムのパフォーマンスでC言語に近づくことですが、test/bench/shootout内のベンチマークにおいて、いくつかはまだ不十分です。最も実行速度の遅いものは、パフォーマンスの比較相手が、Go言語からでは利用できないライブラリを使っています。たとえば、pidigits.goは多精度演算パッケージに依存していますが、これのC言語版は、Go言語版とは異なり、GMP(最適化されたアセンブラで記述されている)が使われています。正規表現を利用するベンチマーク(例えば、regex-dna.go)は、基本的にGo言語のネイティブregexpパッケージと、PCREのように高度に最適化された成熟した正規表現ライブラリとが比較されます。

ベンチマークゲームは広範囲なチューニングを行った方が勝利を納めます。ベンチマークのGo言語版の大部分には見直しが必要です。C言語とGo言語のプログラムの比較を測定する際、ベンチマークスイートが示すよりも、2つの言語間における低レベルな処理のパフォーマンス(例:reverse-complement.go)が肉薄していることが分かることでしょう。しかし、まだ改善の余地はあります。良いコンパイラではありますが、まだ改良可能であり、ライブラリの多くではパフォーマンスの改善を必要としています。ガベージコレクションは、まだ十分な速度ではありません。(たとえそうであっても、不要なガベージを生成しないよう気を付けることには大きな効果があります)

ともあれGo言語は殆どの場合で、速度的によい結果がでています。言語およびツールの開発に伴い、プログラムの大部分が大幅に改善されています。役立つ例がありますので、ブログ記事のGo言語プログラムのプロファイリング(英語)を参照ください。

C言語との違い

C言語と、これほど構文に違いがある理由は?

宣言構文以外には、重要な差異はほとんどなく、次の2点の要望だけを取り入れました。一つ目として、構文はシンプルであるべきで、義務的なキーワード、繰り返し、不条理なものは排除しました。二つ目として、Go言語は設計上、構文解析がし易くなっており、シンボルテーブルは要りません。これは、例えばデバッガ、依存解析、自動化したドキュメント抽出、IDEプラグインといったツールの作成をとても容易にします。C言語系の言語は、この点について評判が悪く、これらのツール作成は困難です。

宣言を逆に入れ替えた理由は?

C言語に慣れているのであれば、単に入れ替えただけと考えてください。C言語では、変数は、その型を表す式として宣言されます。これは、よいアイデアですが、型と式の文法を混ぜられません。その結果、混乱を招くことがあります。たとえば、関数ポインタを例にとりますと、Go言語では多くの場合、式と型の構文を分けて記述するので、宣言が単純化されます。(ポインタのときだけ例外としてプレフィックス*を使います。これを使ってルールを説明します) 下の例は、C言語を使った宣言です。

    int* a, b;

aは、ポインタとして宣言されましたが、bはポインタではありません。次は、Go言語の例です。

    var a, b *int

この場合、両変数がポインタとして宣言されるので、より明確で規則的です。また、型名を省略した短縮形式の宣言:=と同じことが、通常の変数宣言でも行えることも、これの利点です。

    var a uint64 = 1

上の例は、次と同じ結果になります。

    a := uint64(1)

式の文法とは別に、型用の文法があるので、funcchanなどのキーワードによりコードが明確になり、構文解析も簡略化できました。

より詳細な記事は、Go’s Declaration Syntax(英語)を参照ください。

ポインタの演算がない理由は?

安全のためです。ポインタの演算をなくすことで、不正なアドレスを誤って取得しまうことのない言語を作成できます。配列のインデックスを使ったループが、ポインタ演算を使ったループと同等の効率性を得られるまでにコンパイラとハードウェアのテクノロジーは向上しています。また、ポインタ演算がないことは、ガベージコレクションの実装の簡略化にも寄与しています。

++と--が、式ではなくステートメントである理由は? 変数の前ではなく、後ろにある理由は?

便利ではありましたが、ポインタの演算がないので、前または後ろにインクリメント演算子を取る値は採用していません。これらを式の中から完全に排除したおかげで、式の構文は簡単になり、++--の評価順に関するやっかいな問題(例:f(i++)p[i] = q[++i])もなくなりました。この簡略化は、とても重要です。前置き/後置きは、どちらでもよいのですが、どちらかと言うと後ろに付ける方が一般的です。STLにおいては、これら演算子を前置きで使うことが定められていました。(このSTLは、C++言語用のライブラリで、この言語は皮肉にも名前に「後置きのインクリメント」が使われています)

波括弧があるがセミコロンがない理由は? 開始波括弧は次の行に置けないのか?

Go言語では、ステートメントのグループ化に波括弧{}を使用します。これは、C言語系の言語を使ったことのあるプログラマにはお馴染みの構文です。しかし、セミコロンは、構文解析に必要なのであって、人々のためではありません。そこで、できるだけセミコロンを排除したいと考えました。Go言語で、これを実現させるために、BCPLから手法を拝借しました。正式な文法としては、ステートメントの分割にセミコロンを使いますが、ステートメントの終りと成り得る各行末に、lexerによって先読みすることなく、自動的にセミコロンを挿入します。これは実際にとてもよく機能しているのですが、これによって波括弧の書き方が強制されます。たとえば、関数の開始波括弧{を、それだけを一行に単独で記述することはできません。

何人かは、波括弧を次の行に記述できるよう、lexerは先読みをすべきであると主張しましたが、これには同意できません。Go言語のコードは、gofmt(未訳)によって自動的にフォーマットされますし、コードのスタイルは決めておかなければなりません。このスタイルは、あなたがC言語やJavaで使ったスタイルとは異なるかもしれませんが、Go言語は新しい言語であるとともに、gofmtのスタイルは、他のスタイルと比べても見劣りはしないからです。最も重要な点は、Go言語のすべてのコードのスタイルがプログラム的に統一される利点ひとつを考えても、スタイルが固定されることによって起きる不都合を大いに上回ります。これも重要なことですが、このGo言語のスタイルは、Go言語の対話型実行ツールにおいて、特殊なルールを使うことなく、標準的な構文を一行単位に取り扱えることを意味しています。

ガベージコレクションを採用した理由は? コストがかかり過ぎないか?

システムプログラムの記述が冗長になる大きな要因のひとつがメモリ管理です。我々は、これをプログラマの頭から排除することが重要であると思いました。また、ここ数年のガーベージコレクションの技術進化は、充分な程に小さなオーバヘッドで、かつ大きな待ち時間が発生しないようにガーベージコレクションを実装可能なことを我々に確信させました。

もうひとつのポイントは、並列なマルチスレッドプログラミングの難しさの大部分もメモリ管理である点です。スレッド間でやり取りされたオブジェクトが、安全に開放されることを保証することは厄介です。自動ガーベージコレクションは、並列処理を行うコードの記述を非常に容易にします。もちろん、並列環境でガーベージコレクションを実行することは、それ自体が難問ですが、各プログラム側で対応するより、一度解決してしまえば、すべての人の役に立ちます。

最後に、並列性とは無関係になりますが、ガベージコレクションは、プログラミング間インタフェースの簡素化にも役立ちます。なぜなら、メモリがインタフェース間でどのように管理されるのか規定する必要がないからです。

現在の実装はパラレル・マーク・アンド・スウィープ・コレクタですが、将来は他のアプローチを取る予定です。

パフォーマンスの話題で覚えておいてもらいたいのは、他のガベージコレクションを有する一般的な言語に比べ、メモリレイアウトおよび割り当てに関して、Go言語ははるかに多くの制御権限をプログラマに与えています。プログラマが細心の注意を払いGo言語を駆使すれば、ガベージコレクションのオーバヘッドを劇的に減らすことが可能です。実際に動くサンプルや、Go言語のプロファイリングツールのデモは、Go言語プログラムのプロファイリング(英語)の記事を参照ください。