NO IMAGE

【Go】つぶやきmake()

NO IMAGE

この頃担当しているシステムで、`make()`関数をよく見かけます。少し気になったので調べてみました。

make()関数


スライス(slice)、マップ(map)、チャネル(channel)といった、Goの参照型を生成するときに使う組み込み関数です。それぞれの参照型に対して2つずつ、呼び出し方が用意されています。ここではさらっと、その中身を見ていくことにします。

スライスのmake


スライスは、他の言語でいうところの「可変長配列」を表現する型です。`make(T, n)`を使うことで、要素数と容量がnであるT型のスライスを生成できます。

package main

import "fmt"

func main() {
	strs := make([]string, 3)
	fmt.Println(strs)
}
$ go run main.go
[  ]

また、`make(T, n, m)`を使うことで、要素数がnで容量がmであるT型のスライスをつくれます。ここで「要素数」と「容量」の関係ですが、ざっくりいうと「なかみ」と「はこ」です。たとえば要素数=「なかみ」が2で容量=「はこ」が3のスライスは生成できますが、要素数=「なかみ」が3で容量=「はこ」が2のスライスは生成できません。

func main() {
	strs := make([]string, 3, 2)
	fmt.Println(strs)
}
$ go run main.go
# command-line-arguments
./main.go:6:14: len larger than cap in make([]string)

マップのmake


マップについては、`make(T)` でT型のマップを、`make(T, n)`でT型のマップを要素数nをヒントにして生成できます。


ここで「要素数nをヒントにして生成」とは、いわば「その要素数が入る分のメモリ領域を確保しといてね」とGoのランタイムにお願いする、というようなイメージです。たとえば、10万ほどの要素が追加されると見込まれるような時にこのmake(T, n)を使うと、パフォーマンス向上も見込める場合があります。

func main() {
	strs := make(map[int64]string)
	strs[1] = "First"
	strs[2] = "Second"
	strs[3] = "Three"

	// 10000要素が入るくらいのメモリを確保してもらう
	ints := make(map[string]int32, 10000)
	ints["one"] = 1
	ints["two"] = 2
	ints["three"] = 3

	fmt.Println("make(T):", strs)
	fmt.Println("make(T, n):", ints)
}

チャネルのmake


Go言語では「go文」を用いて、非同期に複数実行されるゴルーチンを生成できます。チャネルは、あるゴルーチンと別のゴルーチンの間でデータの受け渡しができるようデザインされた、Goに特有の型です。`make(T)`によって、「バッファ」のサイズが0のチャネルを生成できます。

チャネルのバッファ

そもそもチャネルは、「キュー(Queue, 待ち行列)」の性質を備えたデータ構造です。チャネルにおけるバッファとは、このキューを格納しておくための領域だとみなせます。先の`make(T)`は、このバッファのサイズが0のチャネルを生成する、ということです。もしバッファサイズをnだと指定するときは、`make(T, n)`が使えます。