Go言語入門:言語仕様 -Vol.62-

スポンサーリンク
Go言語入門:言語仕様 -Vol.62- 用語解説
Go言語入門:言語仕様 -Vol.62-
この記事は約7分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

複合リテラル(Composite literals)

複合リテラルは、評価されるたびに構造体、配列、スライス、およびmapの新しい値を構築する。リテラルの型の後に、(空でもよい)波括弧で囲まれた要素のリストが続く形で構成される。各要素には、対応するキーを任意で前置することができる。

CompositeLit = LiteralType LiteralValue .
LiteralType  = StructType | ArrayType | "[" "..." "]" ElementType |
               SliceType | MapType | TypeName [ TypeArgs ] .
LiteralValue = "{" [ ElementList [ "," ] ] "}" .
ElementList  = KeyedElement { "," KeyedElement } .
KeyedElement = [ Key ":" ] Element .
Key          = FieldName | Expression | LiteralValue .
FieldName    = identifier .
Element      = Expression | LiteralValue .

LiteralType が型パラメータでない限り、その基底型は構造体、配列、スライス、またはmap型でなければならない(この制約は、型が TypeName として与えられる場合を除き、構文によって強制される)。LiteralType が型パラメータである場合、その型集合のすべての型は同じ基底型を持たなければならず、その基底型は有効な複合リテラル型でなければならない。

要素とキーの型は、LiteralType の対応するフィールド、要素、およびキーの型にそれぞれ代入可能でなければならない。追加の変換は行われない。キーは、構造体リテラルではフィールド名として、配列およびスライスリテラルではインデックスとして、mapリテラルではキーとして解釈される。同じフィールド名または同じ定数キー値を持つ複数の要素を指定するとエラーとなる。リテラルは要素リストを省略することができる。そのようなリテラルは、その型のゼロ値に評価される。

LiteralType の TypeName 形式を使用する複合リテラルが、iffor、または switch 文のキーワードとブロックの開き波括弧の間にオペランドとして現れ、かつ複合リテラルが括弧、角括弧、または波括弧で囲まれていない場合、構文解析上の曖昧さが生じる。このまれなケースでは、リテラルの開き波括弧が文のブロックを導入するものとして誤って解析される。この曖昧さを解決するには、複合リテラルを括弧の中に入れなければならない。

if x == (T{a,b,c}[i]) { … }
if (x == T{a,b,c}[i]) { … }

解説

複合リテラルってなに?

複合リテラルは、構造体・配列・スライス・mapの値をその場で作る書き方です。Go を書いていると非常によく使います。

// 構造体の複合リテラル
p := Point{x: 1, y: 2}

// スライスの複合リテラル
nums := []int{10, 20, 30}

// mapの複合リテラル
ages := map[string]int{"Alice": 30, "Bob": 25}

// 配列の複合リテラル
rgb := [3]int{255, 128, 0}

どれも 型{要素のリスト} という形になっていますね。これが複合リテラルの基本形です。

キーあり・キーなし

要素を書くとき、キー(名前やインデックス)をつけてもつけなくてもOKです。

// キーあり:どのフィールドに何を入れるか明示
p := Point{x: 1, y: 2}

// キーなし:宣言順に値が入る
p := Point{1, 2}

キーなしのほうが短く書けますが、フィールドの順番を覚えていないといけません。フィールドが増えたり順番が変わったりすると壊れるので、構造体ではキーありの書き方がおすすめです。

スライスや配列でもキー(=インデックス)を使えます。

// インデックス 0, 1, 2 に順番に入る
a := []int{10, 20, 30}

// インデックスを指定して飛ばし飛ばしに入れることも可能
b := []int{0: 10, 5: 60}  // b は [10, 0, 0, 0, 0, 60]

配列の ... 記法

配列のサイズを要素の数から自動で決めてもらえる便利な書き方があります。

// 自分でサイズを数えて書く
a := [3]int{1, 2, 3}

// ... を使うとコンパイラが数えてくれる
b := [...]int{1, 2, 3}  // [3]int になる

要素の数を変えたときにサイズの数字を直し忘れる心配がなくなりますね。

空の複合リテラル=ゼロ値

要素リストを省略すると、ゼロ値が得られます。

p := Point{}               // x: 0, y: 0
nums := []int{}             // 空のスライス(長さ0)
ages := map[string]int{}    // 空のmap

同じキーの重複はエラー

同じフィールド名やキーを2回書くとコンパイルエラーです。

p := Point{x: 1, x: 2}                    // エラー! x が重複
m := map[string]int{"a": 1, "a": 2}       // エラー! "a" が重複

「評価されるたびに新しい値を構築する」とは

原文の冒頭に「評価されるたびに」とありました。これは、ループの中で複合リテラルを使うと、毎回新しい値が作られるという意味です。

for i := 0; i < 3; i++ {
    p := Point{x: i, y: i}  // 毎回新しい Point が作られる
    fmt.Println(p)
}

前のループで作った p とは別物です。値が使い回されて意図しない共有が起きる、ということはありません。

構文解析の曖昧さ(ちょっとマニアックな話)

最後の段落は、iffor の条件式の中で複合リテラルを使うときの落とし穴です。

if x == T{a, b, c}[i] { ... }

人間の目には「T{a, b, c} を作ってインデックス i で要素を取り出し、x と比較する」と読めます。しかしコンパイラは T{{if文のブロックの始まりと解釈してしまいます。

解決方法は、括弧で囲むだけです。

if x == (T{a, b, c}[i]) { ... }  // OK! 括弧で曖昧さが解消される
if (x == T{a, b, c}[i]) { ... }  // これもOK!

実際にこの問題に当たることはめったにありませんが、if の条件式で複合リテラルを使ったときにコンパイルエラーが出たら、「括弧で囲めばいいんだった」と思い出してください。

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

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

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました