
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
インポート宣言
インポート宣言は、その宣言を含むソースファイルが、インポート先のパッケージの機能に依存していること(§プログラムの初期化と実行)を表し、そのパッケージのエクスポートされた識別子へのアクセスを可能にします。インポートでは、アクセスに使う識別子(PackageName)と、インポートするパッケージを指定する ImportPath を指定します。
ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
ImportSpec = [ "." | PackageName ] ImportPath .
ImportPath = string_lit .
PackageName は、インポートする側のソースファイル内で、そのパッケージのエクスポートされた識別子へアクセスするための修飾識別子に使われます。これはファイルブロックの中で宣言されます。PackageName を省略した場合、インポート先パッケージのパッケージ句で指定された識別子がデフォルトとして使われます。名前の代わりに明示的にピリオド(.)を置いた場合、そのパッケージのパッケージブロックで宣言されたエクスポート済み識別子のすべてが、インポートする側のソースファイルのファイルブロックに宣言され、修飾子なしでアクセスしなければなりません。
ImportPath の解釈は実装依存ですが、典型的にはコンパイル済みパッケージの完全なファイル名の一部であり、インストール済みパッケージのリポジトリからの相対パスである場合があります。
実装上の制限:コンパイラは ImportPath を、空でない文字列で、かつUnicodeの L、M、N、P、S の一般カテゴリに属する文字(スペースを除く図形文字)のみを使うものに制限してよく、さらに !"#$%&'()*,:;<=>?[\]^{|}` の各文字と Unicode 置換文字 U+FFFD を除外してもよいとされています。
package math というパッケージ句を持ち、関数 Sin をエクスポートするコンパイル済みパッケージを考え、それを "lib/math" で識別されるファイルにインストールしたとします。次の表は、各種のインポート宣言を経て、それをインポートするファイル内で Sin がどのようにアクセスされるかを示しています。
Import declaration Local name of Sin
import "lib/math" math.Sin
import m "lib/math" m.Sin
import . "lib/math" Sin
インポート宣言は、インポートする側とされる側のパッケージの間に依存関係を宣言します。あるパッケージが、直接的または間接的に自分自身をインポートすることは不正です。また、エクスポートされた識別子を1つも参照せずにパッケージを直接インポートすることも不正です。副作用(初期化)だけを目的としてパッケージをインポートするには、明示的なパッケージ名としてブランク識別子を使います。
import _ "lib/math"
解説
結論:import は他のパッケージを使うための宣言。書き方は4パターン(通常・別名・ドット・アンダースコア)あり、それぞれパッケージの呼び出し方が変わる。
インポートの基本
他のパッケージにある機能を使いたいとき、ファイルの先頭で import を書きます。これは「このファイルはこのパッケージに頼っていますよ」という宣言です。
import "fmt"
// 使うとき
fmt.Println("hello") // fmt パッケージの Println を呼ぶ
fmt.Println のように「パッケージ名.機能名」の形で書きます。この パッケージ名.機能名 という書き方を**修飾識別子(qualified identifier)**と呼びます。電話に例えると、fmt が「市外局番」、Println が「番号」のようなもので、どのパッケージの誰を呼ぶのかを特定しています。
複数まとめてインポートする
文法定義 ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) は、インポートには2通りの書き方があることを示しています。1つずつ書く方法と、丸括弧 ( ) でまとめる方法です。
// 1つずつ書く
import "fmt"
import "os"
// まとめて書く(実務ではこちらが一般的)
import (
"fmt"
"os"
)
ImportPath と PackageName の違い
ここが少しややこしいので整理します。ImportSpec = [ "." | PackageName ] ImportPath という定義で、ImportPath(インポートパス)が必須、その前の部分(. または PackageName)は [ ] で囲まれているので**省略可能(オプション)**です。
- ImportPath:パッケージの「ありか」を示す文字列(例:
"lib/math")。文字列リテラル(string_lit)なので必ずダブルクォートで囲みます。 - PackageName:そのパッケージをこのファイル内で呼ぶときの名前。省略すると、インポート先が自分で名乗っているパッケージ名(=パッケージ句の名前)が自動的に使われます。
4つのインポートパターン
原文の表に、副作用インポートを加えた4パターンを整理します。package math(Sin をエクスポート)を "lib/math" からインポートする例です。
| 書き方 | Sin の呼び方 | 説明 |
|---|---|---|
import "lib/math" | math.Sin | 通常。パッケージ句の名前 math で呼ぶ |
import m "lib/math" | m.Sin | 別名(エイリアス)。m という短い名前で呼ぶ |
import . "lib/math" | Sin | ドットインポート。修飾子なしで直接呼ぶ |
import _ "lib/math" | (呼べない) | ブランクインポート。副作用だけが目的 |
それぞれ具体的に見ていきます。
① 通常のインポート
import "lib/math"
x := math.Sin(1.0) // パッケージ名 math で呼ぶ
PackageName を省略しているので、インポート先のパッケージ句 package math の名前がそのまま使われます。
② 別名(エイリアス)インポート
import m "lib/math"
x := m.Sin(1.0) // m という別名で呼ぶ
パスの前に名前を書くと、その名前で呼べるようになります。名前が長いパッケージを短くしたり、同名パッケージの衝突を避けたりするときに使います。
③ ドットインポート
import . "lib/math"
x := Sin(1.0) // パッケージ名を付けずに直接呼ぶ
.(ピリオド)を書くと、そのパッケージのエクスポート済み識別子が、あたかも自分のファイルで宣言されたかのように使えるようになり、math. を付けずに Sin だけで呼べます。ただし、どの関数がどのパッケージから来たのか分かりにくくなるため、実務ではほとんど使いません(主にテストなどの限られた場面のみ)。
④ ブランク(アンダースコア)インポート
import _ "lib/math"
これは後述します。
「使わないインポートは禁止」というルール
原文の重要なルールが2つあります。
1つ目は「自分自身をインポートできない」こと。AがBを、BがAをインポートするような循環も禁止です。鏡が向き合って無限に映り込むような状態を防いでいます。
2つ目は 「インポートしたのに1つも使わないのは不正(コンパイルエラー)」 ということです。これはGo初心者が最もよく遭遇するエラーの1つです。
import "fmt" // fmt を一度も使わないと…
func main() {
// fmt を使っていない
}
// → エラー: "fmt" imported and not used
他の多くの言語では未使用のインポートは警告止まりですが、Goはエラーにして強制的に消させます。コードを常にきれいに保つための、Goらしい厳格な仕様です。
④ ブランクインポートの存在理由
「使わないインポートは禁止」なら、なぜ import _ "lib/math" という書き方が必要なのでしょうか。
答えは**「副作用(side-effect)だけが欲しいとき」**があるからです。パッケージの中には、インポートされた瞬間に初期化処理(init 関数など)が走るものがあります。パッケージの機能そのものは直接呼ばないけれど、その初期化処理だけは実行したい、という場面があります。
代表例がデータベースドライバの登録です。
import (
"database/sql"
_ "github.com/lib/pq" // PostgreSQLドライバ。直接は呼ばないが、登録処理だけ走らせたい
)
この _ "github.com/lib/pq" は、ドライバを内部的に登録する初期化処理だけを目的としています。pq.〜 のように直接呼ぶことはないので、通常のインポートだと「使っていない」エラーになってしまいます。そこで _ を付けることで「呼ばないのは承知のうえで、初期化のためだけにインポートする」とコンパイラに明示します。
家電に例えると、コンセントに挿す(インポートする)と自動で内部設定が走る機器を、操作パネルは一切触らずにただ電源だけ入れておく、というイメージです。
ImportPath の文字制限について
原文の「実装上の制限」の段落は、初心者が今すぐ覚える必要はありません。要点だけ言うと、インポートパスに使える文字には制限があり、空文字や一部の記号は使えないということです。実際には GitHub のURL風のパス(例:"github.com/lib/pq")を書くのがほとんどで、変な記号を入れることはまずないので、気にせず進んで構いません。
補足:現代のGo(Goモジュール)では、ImportPath は実際にはモジュールパス(github.com/ユーザー名/リポジトリ名/...)で書くのが標準です。原文の "lib/math" は仕様を説明するための簡略化された例であって、実在のパス形式ではない点に注意してください。
おわりに
本日は、Go言語の言語仕様について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント