この記事は Go (その3) Advent Calendar 2017 の5日目の記事です。
本記事では、変数の宣言に用いられる組み込み関数new()
とmake()
の違いについてまとめます。
まとめ
さっそくですが、違いを表にまとめます。
new(T) | make(T) | |
---|---|---|
対象 | 任意の型 | slice, map, channelのみ |
初期化 | 初期化しない(ゼロ値になる) | 初期化する[^1] |
返り値 | *T | T |
対象と初期化について
new()
とmake()
で、初期化しない/するの違いは、
slice, map, channelが、内部にデータ構造を持つことからきています。
以下にruntime
パッケージで、それぞれ型が定義されている箇所を引用します。
一番理解しやすいのが、sliceです。array(実データ), len, capを初期化してあげる必要があるため、make()
が用意されています。
slice
type slice struct { array unsafe.Pointer len int cap int }
https://golang.org/src/runtime/slice.go#L11-15
map
type hmap struct { // Note: the format of the Hmap is encoded in ../../cmd/internal/gc/reflect.go and // ../reflect/type.go. Don't change this structure without also changing that code! count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) extra *mapextra // optional fields }
https://golang.org/src/runtime/hashmap.go#L106-120
channel
type hchan struct { qcount uint // total data in the queue dataqsiz uint // size of the circular queue buf unsafe.Pointer // points to an array of dataqsiz elements elemsize uint16 closed uint32 elemtype *_type // element type sendx uint // send index recvx uint // receive index recvq waitq // list of recv waiters sendq waitq // list of send waiters // lock protects all fields in hchan, as well as several // fields in sudogs blocked on this channel. // // Do not change another G's status while holding this lock // (in particular, do not ready a G), as this can deadlock // with stack shrinking. lock mutex }
https://golang.org/src/runtime/chan.go#L31-50
初期化について補足
Effective Goでは、new()
では初期化が行われない、つまりゼロ値となることについて、helpfulであると述べられています。
これはゼロ値自体が意味を持つ場合には、初期化しているのと同じだけの意味があるということです。
Effective Goで述べられている例ですが、sync.Mutex
では以下のようにゼロ値自身がunlockなstateを表現します。
// A Mutex is a mutual exclusion lock. // The zero value for a Mutex is an unlocked mutex. // // A Mutex must not be copied after first use. type Mutex struct { state int32 sema uint32 }
自身で型を定義する際には、NewMyType()
のようなコンストラクタを用意する場合が多いでしょう。このときにゼロ値自身に意味を持たせ、new()
と組み合わせることでよい設計となると思います。
最後に
自分自身もあまりnew()
とmake()
の違いを認識できていなかったので、調べてまとめることができてよかったです。あといつか読もう読もうと思って着手できていなかったruntime
パッケージを読むきっかけにもなったので、思いの外収穫が多かったです。Goはソースコードにドキュメントレベルでコメントが書いてあるので、読むだけでなるほどと思うことが多く、とても勉強になります。
Githubの履歴を確認したらちょうどGoを書き始めてから1年が経過しました。書けば書くほど好きになっていくので、まだまだ書きますよー!
あぁ。水色gopherくんぬいぐるみ欲しいなぁ。。。