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

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

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

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

スポンサーリンク

背景

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

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

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

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

長さと容量(Length and capacity)

組み込み関数 lencap はさまざまな型の引数を取り、int 型の結果を返す。実装は結果が常に int に収まることを保証する。

呼び出し     引数の型           結果

len(s)    文字列型             バイト単位の文字列長
          [n]T, *[n]T         配列の長さ(== n)
          []T                 スライスの長さ
          map[K]T             mapの長さ(定義されたキーの数)
          chan T              チャネルバッファにキューされた要素数
          型パラメータ         以下を参照

cap(s)    [n]T, *[n]T         配列の長さ(== n)
          []T                 スライスの容量
          chan T              チャネルバッファの容量
          型パラメータ         以下を参照

引数の型が型パラメータ P の場合、呼び出し len(e)(または cap(e))は P の型集合の各型に対して有効でなければならない。結果は、P がインスタンス化された型引数に対応する型を持つ引数の長さ(または容量)である。

スライスの容量は、基底配列に空間が割り当てられている要素数である。常に以下の関係が成り立つ:

0 <= len(s) <= cap(s)

nil のスライス、map、またはチャネルの長さは0である。nil のスライスまたはチャネルの容量は0である。

s が文字列定数の場合、式 len(s) は定数である。s の型が配列または配列へのポインタであり、式 s がチャネル受信や(非定数の)関数呼び出しを含まない場合、式 len(s)cap(s) は定数である。この場合、s は評価されない。それ以外の場合、lencap の呼び出しは定数ではなく、s は評価される。

const (
	c1 = imag(2i)                    // imag(2i) = 2.0 は定数
	c2 = len([10]float64{2})         // [10]float64{2} は関数呼び出しを含まない
	c3 = len([10]float64{c1})        // [10]float64{c1} は関数呼び出しを含まない
	c4 = len([10]float64{imag(2i)})  // imag(2i) は定数であり、関数呼び出しは発行されない
	c5 = len([10]float64{imag(z)})   // 不正: imag(z) は(非定数の)関数呼び出し
)
var z complex128

解説

len と cap の基本

len は「今いくつ入っているか」、cap は「最大いくつ入れられるか」を返します。

s := make([]int, 3, 10)
fmt.Println(len(s))  // 3(現在の要素数)
fmt.Println(cap(s))  // 10(基底配列のサイズ)

型ごとの使い方

// 文字列:バイト数(文字数ではない!)
len("hello")      // 5
len("こんにちは")  // 15(1文字3バイト × 5文字)

// 配列:常に固定
a := [5]int{1, 2, 3}
len(a)   // 5
cap(a)   // 5(配列では len == cap)

// スライス
s := make([]int, 3, 10)
len(s)   // 3
cap(s)   // 10

// map:キーの数
m := map[string]int{"a": 1, "b": 2}
len(m)   // 2
// cap(m)  // コンパイルエラー! map に cap は使えない

// チャネル
ch := make(chan int, 5)
ch <- 1
ch <- 2
len(ch)  // 2(バッファに入っている要素数)
cap(ch)  // 5(バッファの容量)

mapに cap が使えないのは、mapのサイズは動的に伸縮するため「最大容量」という概念がないからです。

nil のときは 0

nil のスライス、map、チャネルに対して lencap を呼んでも、パニックしません。0 が返ります。

var s []int
var m map[string]int
var ch chan int

fmt.Println(len(s))   // 0
fmt.Println(cap(s))   // 0
fmt.Println(len(m))   // 0
fmt.Println(len(ch))  // 0

nil チェックをせずにそのまま len を呼べるので、コードがシンプルになります。

// nil チェック不要
func isEmpty(s []int) bool {
    return len(s) == 0  // s が nil でも正しく動く
}

0 <= len(s) <= cap(s) の関係

スライスでは常にこの不等式が成り立ちます。

s := make([]int, 3, 10)
// len=3, cap=10

s = s[:5]   // len=5, cap=10(len を cap の範囲内で伸ばせる)
s = s[:10]  // len=10, cap=10(cap まで伸ばせる)
// s = s[:11]  // パニック! cap を超えている

len と cap が定数になる場合

配列の lencapコンパイル時に決まる定数です。この性質を使って、定数式の中で len を使えます。

const size = len([10]int{})  // 10(コンパイル時定数)

これが以前学んだ range の節で「len(testdata.a) が定数なのでレンジ式が評価されない」というケースにつながります。

var testdata *struct {
    a *[7]int
}
for i, _ := range testdata.a {
    // testdata.a は評価されない(nil でもパニックしない)
    // len(testdata.a) は定数 7
    f(i)  // i は 0 から 6
}

[7]int の長さは常に 7 なので、コンパイラは testdata.a を実際に評価せずに長さを知ることができます。

文字列の len は「バイト数」

これは繰り返しになりますが、非常に重要なので改めて強調します。

s := "Go言語"
fmt.Println(len(s))          // 8(バイト数)
fmt.Println(len([]rune(s)))  // 4(文字数)

文字数を知りたい場合は utf8.RuneCountInString を使うのが最も効率的です。

import "unicode/utf8"

n := utf8.RuneCountInString("Go言語")  // 4

[]rune に変換する方法だと新しいスライスが確保されるため、文字数を数えるだけなら RuneCountInString のほうがメモリ効率がよいです。

よくある使い方

// スライスが空かどうか
if len(s) == 0 { ... }

// map にキーがあるか(len で判定するのではなく、ok パターンを使う)
if v, ok := m[key]; ok { ... }

// チャネルにデータがあるか
if len(ch) > 0 { ... }  // ただし並行処理では信頼できない

// スライスに append の余地があるか
if len(s) < cap(s) {
    // append しても新しい配列は確保されない
}

チャネルの len は並行処理の文脈では注意が必要です。len(ch) > 0 をチェックした直後に別のゴルーチンが値を受信してしまう可能性があるため、この値に依存した制御は避けるべきです。

おわりに 

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

よっしー
よっしー

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

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

コメント

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