new()
new(T)はtypeがTのインスタンスを、Tのゼロ値で生成して、そのポインタを返してくれる。
構造体でよく使われる。
make()
make(T, args)における、Tはsliceかmapかchannelのいずれかである。
make(T, args)はtypeがTのインスタンスをargsを元に初期化して返してくれる。
new()とmake()の使い分け
そもそも、make()はsliceかmapかchannelでしか使えない。
これら3つの型には初期化情報を与えることで事前にメモリを確保しておくことが可能だ。
sliceにはlenとcapを、mapとchannelにはlenを与えられる。
あらかじめ使用するメモリ幅が決まっているのなら事前に確保しておくことで、処理時間を短くできる。
一応、new()でsliceやmapのインスタンスを生成することもできる。
package main
import (
"fmt"
)
func main() {
s := new([]string)
(*s) = append((*s), "hello")
fmt.Println((*s)[0]) // output: hello
}
package main
import (
"fmt"
)
func main() {
m := new(map[string]string)
m = &map[string]string{} // この行がないとエラーになる
(*m)["hello"] = "world"
fmt.Println((*m)["hello"]) // output: hello
}
ご覧の通り、sliceとmapのゼロ値はnilであったり、new()がポインタを返すせいで、冗長な書き方になってしまう。
処理を短時間にする観点に加えて、sliceやmapやchannelでは初期化情報を与えないとすぐに使えないという事情もありそうだ。
new()の使い所はというと、sliceでもmapでもchannelでもない型のインスタンスを生成したい時、ということになる。
ただ、それらは初期化子としてリテラルが使えることがほとんどだ。
package main
import (
"fmt"
)
func main() {
str := new(string)
*str = "hello"
fmt.Println(*str)
}
👆このように書くよりも、
👇このように書いた方が早い。
package main
import (
"fmt"
)
func main() {
str := "hello"
fmt.Println(str)
}
ということなので、new()の使い所はある程度限られてきそうだ。
使い所の一つにはゼロ値の初期化で十分使える状態になるという場合がある。
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
p := new(SyncedBuffer)
sync.Mutexのゼロ値はUnlockedなmutexで、
bytes.Bufferのゼロ値は、すぐに使える空のバッファーである。
これはゼロ値が便利なのでnew()を使いたい。
もう一つには、構造体をnew()で初期化しておいて、メンバを一つずつ計算して当てはめていきたい場合もあるかもしれない。
また、構造体で、ほとんどのメンバのゼロ値は使えるが、一部のメンバのゼロ値はやっかいな場合に、とりあえずnew()してから一部のメンバを計算するという使い方もできそうだ。
まとめ
Effective Goにはこう書かれている。
"Since the memory returned by new is zeroed, it's helpful to arrange when designing your data structures that the zero value of each type can be used without further initialization. "
ゼロ値がすぐに使えるもので、追加の初期化処理がいらないときにnew()を使うと便利だよ、ということらしい。
これからは立ち止まって初期化の方法を考えていきたい。