Golangのnew()とmake()の違い

この記事は Go (その3) Advent Calendar 2017 の5日目の記事です。

本記事では、変数の宣言に用いられる組み込み関数new()make()の違いについてまとめます。

まとめ

さっそくですが、違いを表にまとめます。

new(T) make(T)
対象 任意の型 slice, map, channelのみ
初期化 初期化しない(ゼロ値になる) 初期化する
返り値 *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くんぬいぐるみ欲しいなぁ。。。

References