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

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

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

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

スポンサーリンク

背景

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

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

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

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

スライスへの追加とコピー(Appending to and copying slices)

組み込み関数 appendcopy は、一般的なスライス操作を支援する。両方の関数において、引数が参照するメモリが重複しているかどうかに関わらず、結果は独立している。

可変長引数関数 append は、型 S のスライス s にゼロ個以上の値 x を追加し、結果のスライス(これも型 S)を返す。値 x は型 ...E のパラメータに渡される。ここで ES の要素型であり、対応するパラメータ渡しのルールが適用される。特殊なケースとして、append[]byte 型に代入可能な型のスライスに対して、string 型の第2引数に ... を続ける形も受け付ける。この形式は文字列のバイトを追加する。

append(s S, x ...E) S  // E は S の要素型

S が型パラメータの場合、その型集合のすべての型は同じ基底スライス型 []E を持たなければならない。

s の容量が追加の値を収めるのに十分でない場合、append は既存のスライス要素と追加の値の両方を収める、十分に大きい新しい基底配列を割り当てる。そうでなければ、append は基底配列を再利用する。

s0 := []int{0, 0}
s1 := append(s0, 2)                // 単一要素を追加        s1 は []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // 複数要素を追加        s2 は []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // スライスを追加        s3 は []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // 重複するスライスを追加 s4 は []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}
t = append(t, 42, 3.1415, "foo")   //                       t は []interface{}{42, 3.1415, "foo"}

var b []byte
b = append(b, "bar"...)            // 文字列の内容を追加    b は []byte{'b', 'a', 'r' }

関数 copy はソース src からデスティネーション dst にスライス要素をコピーし、コピーされた要素数を返す。両方の引数は同一の要素型 E を持ち、型 []E のスライスに代入可能でなければならない。コピーされる要素数は len(src)len(dst) の最小値である。特殊なケースとして、copy[]byte 型に代入可能なデスティネーション引数と string 型のソース引数も受け付ける。この形式は文字列からバイトスライスにバイトをコピーする。

copy(dst, src []T) int
copy(dst []byte, src string) int

一方または両方の引数の型が型パラメータの場合、それぞれの型集合のすべての型は同じ基底スライス型 []E を持たなければならない。

例:

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:])            // n1 == 6, s は []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:])            // n2 == 4, s は []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hello, World!")  // n3 == 5, b は []byte("Hello")

解説

append の基本

append は、スライスに要素を追加して新しいスライスを返す関数です。Go のスライス操作で最もよく使う組み込み関数ですね。

s := []int{1, 2, 3}
s = append(s, 4)        // 1つ追加
s = append(s, 5, 6, 7)  // 複数追加
fmt.Println(s)           // [1 2 3 4 5 6 7]

必ず結果を代入する

以前の式文の節で学んだとおり、append は式文として使えません。結果を変数に代入しないと何も起きません

append(s, 4)      // コンパイルエラー! 結果を使っていない
s = append(s, 4)  // ✅ 結果を s に代入

なぜ s = append(s, 4) と自分自身に代入し直す必要があるのでしょうか? それは append が元のスライスを変更するのではなく、新しいスライスを返すからです。容量が足りない場合、新しい基底配列が確保され、ポインタが変わります。

容量が足りるかどうかで動作が変わる

ここが append で最も理解すべきポイントです。

容量に余裕がある場合:基底配列を再利用

s := make([]int, 2, 5)  // len=2, cap=5
s[0], s[1] = 10, 20

s2 := append(s, 30)
// s と s2 は同じ基底配列を共有!
// s2[0] を変更すると s[0] も変わる

容量が足りない場合:新しい基底配列を確保

s := []int{10, 20}  // len=2, cap=2

s2 := append(s, 30)
// 容量が足りないので、新しい基底配列が確保される
// s と s2 は別々の基底配列を持つ

この挙動を意識しないと、基底配列の共有によるバグが起きることがあります。以前学んだ完全スライス式 s[low:high:max] で容量を制限するのが、この問題への対策でしたね。

スライス同士の結合

スライスを別のスライスに追加するには ... を使います。

a := []int{1, 2, 3}
b := []int{4, 5, 6}
a = append(a, b...)  // b の全要素を a に追加
// a = [1 2 3 4 5 6]

... がないとコンパイルエラーになります。append の第2引数以降は ...E 型(個別の要素)として渡す必要があるからです。

文字列を []byte に追加する

特殊なケースとして、[]byte に文字列を追加できます。

var b []byte
b = append(b, "hello"...)  // 文字列のバイトを追加
// b = [104 101 108 108 111]

文字列をバイトスライスに変換して結合したいときに便利です。

copy の基本

copy は、スライスの要素を別のスライスにコピーする関数です。

src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)

n := copy(dst, src)
fmt.Println(n)    // 3(コピーされた要素数)
fmt.Println(dst)  // [1 2 3]

コピーされる要素数は len(src)len(dst)小さいほうです。はみ出す分は無視されます。

copy の安全性:重複があっても大丈夫

copy は、ソースとデスティネーションが同じ基底配列を共有していても正しく動きます。

s := []int{0, 1, 2, 3, 4, 5}
copy(s, s[2:])     // 自分自身の一部を自分にコピー
fmt.Println(s)      // [2 3 4 5 4 5]

内部的にメモリの重複を正しく処理してくれるので、安心して使えます。

copy で文字列からバイトスライスに

append と同様に、copy も文字列から []byte へのコピーを特別にサポートしています。

b := make([]byte, 5)
n := copy(b, "Hello, World!")
fmt.Println(n)        // 5(dst の長さ分だけコピー)
fmt.Println(string(b)) // "Hello"

よくある使い方

1. スライスの独立したコピーを作る

original := []int{1, 2, 3, 4, 5}
clone := make([]int, len(original))
copy(clone, original)
// clone を変更しても original に影響しない

Go 1.21 以降なら slices.Clone も使えます。

2. スライスの先頭に要素を挿入する

s := []int{2, 3, 4}
s = append([]int{1}, s...)  // 先頭に 1 を追加
// s = [1 2 3 4]

3. スライスから要素を削除する

s := []int{1, 2, 3, 4, 5}
i := 2  // インデックス 2 の要素を削除

s = append(s[:i], s[i+1:]...)
// s = [1 2 4 5]

4. nil スライスへの append

nil スライスにも append できます。make で事前に作っておく必要はありません。

var s []int          // nil スライス
s = append(s, 1, 2)  // OK! 自動的に基底配列が確保される
fmt.Println(s)        // [1 2]

これは Go の便利な特徴です。「最初は空で、必要に応じて追加していく」というパターンが自然に書けます。

おわりに 

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

よっしー
よっしー

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

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

コメント

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