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

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

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

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

スポンサーリンク

背景

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

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

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

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

スライスから配列または配列ポインタへの変換(Conversions from slice to array or array pointer)

スライスを配列に変換すると、スライスの基底配列の要素を含む配列が生成される。同様に、スライスを配列ポインタに変換すると、スライスの基底配列へのポインタが生成される。いずれの場合も、スライスの長さが配列の長さより小さい場合、実行時パニックが発生する。

s := make([]byte, 2, 4)

a0 := [0]byte(s)
a1 := [1]byte(s[1:])     // a1[0] == s[1]
a2 := [2]byte(s)         // a2[0] == s[0]
a4 := [4]byte(s)         // パニック: len([4]byte) > len(s)

s0 := (*[0]byte)(s)      // s0 != nil
s1 := (*[1]byte)(s[1:])  // &s1[0] == &s[1]
s2 := (*[2]byte)(s)      // &s2[0] == &s[0]
s4 := (*[4]byte)(s)      // パニック: len([4]byte) > len(s)

var t []string
t0 := [0]string(t)       // nil スライス t に対しても OK
t1 := (*[0]string)(t)    // t1 == nil
t2 := (*[1]string)(t)    // パニック: len([1]string) > len(t)

u := make([]byte, 0)
u0 := (*[0]byte)(u)      // u0 != nil

解説

この変換が必要になる場面

スライスは可変長で便利ですが、「このデータはちょうどN個の要素を持つ」と型レベルで保証したい場面があります。そのときに使うのがこの変換です。

// ネットワークプロトコルで「ヘッダーは正確に4バイト」と決まっている場合
data := receiveData()  // []byte が返ってくる
header := [4]byte(data[:4])  // 先頭4バイトを固定長配列として取り出す

2つの変換方法の違い

配列への変換(コピー)

s := []byte{1, 2, 3}
a := [3]byte(s)  // 値がコピーされる

a[0] = 99
fmt.Println(s[0])  // 1(元のスライスは変わらない)

配列への変換はデータのコピーを作ります。変換後の配列を変更しても元のスライスには影響しません。

配列ポインタへの変換(共有)

s := []byte{1, 2, 3}
p := (*[3]byte)(s)  // 基底配列を指すポインタが得られる

p[0] = 99
fmt.Println(s[0])  // 99(元のスライスも変わる!)

配列ポインタへの変換は基底配列を共有します。ポインタ経由で変更すると元のスライスにも影響します。以前学んだスライスの基底配列の共有と同じ仕組みですね。

パニックの条件:len で判定される

パニックが起きるかどうかは、スライスの len(長さ) で判定されます。cap(容量)ではありません。

s := make([]byte, 2, 4)  // len=2, cap=4

a2 := [2]byte(s)   // OK! len(s)=2 >= 2
a4 := [4]byte(s)   // パニック! len(s)=2 < 4(cap=4 だが関係ない)

容量が足りていても、長さが足りなければパニックします。容量の範囲内のデータにアクセスしたい場合は、先にスライス式で長さを伸ばします。

s := make([]byte, 2, 4)
s = s[:4]             // len を cap まで伸ばす
a4 := [4]byte(s)       // OK!

長さ0の配列は特別

長さ0の配列への変換は、どんなスライスに対しても安全に行えます。nil スライスに対しても成功します。

var t []string          // nil スライス
t0 := [0]string(t)     // OK! 要素が0個なのでパニックしない

ただし、配列ポインタへの変換では nil スライスと空スライスで結果が異なります。

var t []string           // nil スライス
t1 := (*[0]string)(t)   // nil(nil スライスからのポインタは nil)

u := make([]byte, 0)    // 空スライス(nil ではない)
u0 := (*[0]byte)(u)     // nil ではない(空でも基底配列は存在する)

この違いは、nil スライスには基底配列が存在しないのに対し、make で作った空スライスには(長さ0でも)基底配列が存在するからです。

実用的なパターン

1. 固定長データの取り出し

func parseHeader(data []byte) ([4]byte, error) {
    if len(data) < 4 {
        return [4]byte{}, fmt.Errorf("data too short")
    }
    return [4]byte(data[:4]), nil  // 安全に取り出せる
}

事前に長さをチェックしてからの変換がベストプラクティスです。

2. ハッシュ値の扱い

// sha256.Sum256 は [32]byte を返すが、スライスとして扱いたい場合
hash := sha256.Sum256(data)
slice := hash[:]  // 配列 → スライス

// 逆に、スライスで受け取ったハッシュを固定長配列にしたい場合
var fixedHash [32]byte
copy(fixedHash[:], slice)          // 従来の方法
fixedHash = [32]byte(slice)        // Go 1.20 以降の方法(簡潔!)

3. 配列ポインタでゼロコピーアクセス

// 大きなバッファの一部を配列として参照(コピーなし)
buf := make([]byte, 1024)
header := (*[16]byte)(buf[:16])  // コピーせず直接参照
// header を変更すると buf も変わる

パフォーマンスが重要な場面では、コピーを避けるために配列ポインタへの変換が有用です。ただし、基底配列が共有されることを常に意識しておく必要があります。

おわりに 

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

よっしー
よっしー

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

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

コメント

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