The introduction documents dedicate many paragraphs to explaining the difference between new()
and make()
, but in practice, you can create objects within local scope and return them.
Why would you use the pair of allocators?
CodePudding user response:
Go has multiple ways of memory allocation and value initialization:
&T{...}
, &someLocalVar
, new
, make
Allocation can also happen when creating composite literals.
new
can be used to allocate values such as integers, &int
is illegal:
new(Point)
&Point{} // OK
&Point{2, 3} // Combines allocation and initialization
new(int)
&int // Illegal
// Works, but it is less convenient to write than new(int)
var i int
&i
The difference between new
and make
can be seen by looking at the following example:
p := new(chan int) // p has type: *chan int
c := make(chan int) // c has type: chan int
Suppose Go does not have new
and make
, but it has the built-in function NEW
. Then the example code would look like this:
p := NEW(*chan int) // * is mandatory
c := NEW(chan int)
The *
would be mandatory, so:
new(int) --> NEW(*int)
new(Point) --> NEW(*Point)
new(chan int) --> NEW(*chan int)
make([]int, 10) --> NEW([]int, 10)
make(Point) // Illegal
make(int) // Illegal
Yes, merging new
and make
into a single built-in function is possible. However, it is probable that a single built-in function would lead to more confusion among new Go programmers than having two built-in functions.
Considering all of the above points, it appears more appropriate for new
and make
to remain separate.
CodePudding user response:
Things you can do with make
that you can't do any other way:
- Create a channel
- Create a map with space preallocated
- Create a slice with space preallocated or with len != cap
It's a little harder to justify new
. The main thing it makes easier is creating pointers to non-composite types.
The two functions below are equivalent. One's just a little more concise:
func newInt1() *int { return new(int) }
func newInt2() *int {
var i int
return &i
}
CodePudding user response:
make
function allocates and initializes an object of type slice, map, or chan only. Like new
, the first argument is a type. But, it can also take a second argument, the size. Unlike new, make’s return type is the same as the type of its argument, not a pointer to it. And the allocated value is initialized (not set to zero value like in new). The reason is that slice, map and chan are data structures. They need to be initialized, otherwise they won't be usable. This is the reason new() and make() need to be different.
The following examples from Effective Go make it very clear:
p *[]int = new([]int) // *p = nil, which makes p useless
v []int = make([]int, 100) // creates v structure that has pointer to an array, length field, and capacity field. So, v is immediately usable
CodePudding user response:
new(T)
- Allocates memory, and sets it to the zero value for type T..
..that is0
for int,""
for string andnil
for referenced types (slice, map, chan)Note that referenced types are just pointers to some underlying data structures, which won't be created by
new(T)
Example: in case of slice, the underlying array won't be created, thusnew([]int)
returns a pointer to nothingmake(T)
- Allocates memory for referenced data types (slice, map, chan), plus initializes their underlying data structuresExample: in case of slice, the underlying array will be created with the specified length and capacity
Bear in mind that, unlike C, an array is a primitive type in Go!
That being said:
make(T)
behaves like composite-literal syntax
new(T)
behaves like var
(when the variable is not initialized)
func main() {
fmt.Println("-- MAKE --")
a := make([]int, 0)
aPtr := &a
fmt.Println("pointer == nil :", *aPtr == nil)
fmt.Printf("pointer value: %p\n\n", *aPtr)
fmt.Println("-- COMPOSITE LITERAL --")
b := []int{}
bPtr := &b
fmt.Println("pointer == nil :", *bPtr == nil)
fmt.Printf("pointer value: %p\n\n", *bPtr)
fmt.Println("-- NEW --")
cPtr := new([]int)
fmt.Println("pointer == nil :", *cPtr == nil)
fmt.Printf("pointer value: %p\n\n", *cPtr)
fmt.Println("-- VAR (not initialized) --")
var d []int
dPtr := &d
fmt.Println("pointer == nil :", *dPtr == nil)
fmt.Printf("pointer value: %p\n", *dPtr)
}
Run the program
-- MAKE --
pointer == nil : false
pointer value: 0x118eff0 # address to underlying array
-- COMPOSITE LITERAL --
pointer == nil : false
pointer value: 0x118eff0 # address to underlying array
-- NEW --
pointer == nil : true
pointer value: 0x0
-- VAR (not initialized) --
pointer == nil : true
pointer value: 0x0
Further reading:
https://golang.org/doc/effective_go.html#allocation_new
https://golang.org/doc/effective_go.html#allocation_make
CodePudding user response:
There are already a lot of good answers but let me explain the need for new() and make() as separate allocators.
- new(T) allocates uninitialized zeroed memory of the given type T and returns a pointer to that memory so that it is ready to use. Zeroed out just means that the allocated memory will have zero value of given type. Zero values of some go types are -
- int - 0
- bool - false
- float - 0
- string - ""
- struct - Zero value of each member
Problem with new() arises when it needs to handle three other composite types - chan, slice and map. These three types are special in essence that their underlying type is not just an another type but rather a state that needs to be initialized. For example , the underlying state of a slice consists of a pointer to the first element of internal array storage, a length that determines number of elements that can be accessed and a capacity that increases as the number of elements grow. new() certainly cannot handle allocation of such types due to their need for extra initialization step, that is where make() come into play.
- make(T, args) is specially made for chan, slice and map types. It not only allocates the internal storage type of the chan, slice and map but also initializes their underlying state to make them ready to use. For example, for a slice it allocates the internal array storage, set the pointer to refer to first element in that array and set the length and capacity values.
CodePudding user response:
new(T)
: it returns a pointer to type T
a value of type *T
, it allocates and zeroes the memory. new(T)
is equivalent to &T{}
.
make(T)
: it returns an initialized value of type T
, It allocates and initializes the memory. Its used for slices, map and channels.
CodePudding user response:
Difference between new() and make():
- new(T) allocates zeroed storage for a new item of type T and returns its address, a value of type *T: it returns a pointer to a newly allocated zero value of type T, ready for use; it applies to value types like arrays and structs; it is equivalent to &T{ }
- make(T) returns an initialized value of type T; it applies only to the 3 built-in reference types: slices, maps and channels.
In other words, new allocates; make initializes;
var p *[]int = new([]int)
or
// *p == nil; with len and cap 0
p := new([]int)
which is only rarely useful.
p := make([]int, 0)
our slice is initialized, but here points to an empty array.
Both these statements aren't very useful, the following is:
var v []int = make([]int, 10, 50)
// Or
v := make([]int, 10, 50)
This allocates an array of 50 ints and then creates a slice v with length 10 and capacity 50 pointing to the first 10 elements of the array.
Find out some rules for make() and new():
- For slices, maps and channels: use make
- For arrays, structs and all value types: use new
package main
type Foo map[string]string
type Bar struct {
s string
i int
}
func main() {
// OK:
y := new(Bar)
(*y).s = "hello"
(*y).i = 1
// NOT OK:
z := make(Bar) // compile error: cannot make type Bar
z.s = "hello"
z.i = 1
// OK:
x := make(Foo)
x["x"] = "goodbye"
x["y"] = "world"
// NOT OK:
u := new(Foo)
(*u)["x"] = "goodbye" // !!panic!!: runtime error:
// assignment to entry in nil map
(*u)["y"] = "world"
}
Channel:
func main() {
// OK:
ch := make(chan string)
go sendData(ch)
go getData(ch)
time.Sleep(1e9)
// NOT OK:
ch := new(chan string)
go sendData(ch) // cannot use ch (variable of type *chan string)
// as chan string value in argument to sendData
go getData(ch)
time.Sleep(1e9)
}
func sendData(ch chan string) {
ch <- "Washington"
ch <- "Tripoli"
ch <- "London"
ch <- "Beijing"
ch <- "Tokio"
}
func getData(ch chan string) {
var input string
for {
input = <-ch
fmt.Printf("%s ", input)
}
}
CodePudding user response:
You need make()
to create channels and maps (and slices, but those can be created from arrays too). There's no alternative way to make those, so you can't remove make()
from your lexicon.
As for new()
, I don't know of any reason offhand why you need it when you can use struct syntax. It does have a unique semantic meaning though, which is "create and return a struct with all fields initialized to their zero value", which can be useful.
CodePudding user response:
Apart from everything explained in Effective Go, The main difference between new(T)
and &T{}
is that the latter explicitly performs a heap allocation. However it should be noted that this is implementation dependent and thus may be subject to change.
Comparing make
to new
makes little sense as the two perform entirely different functions. But this is explained in detail in the linked article.
CodePudding user response:
The benefits of "make" are heavily covered in other answers, but "New" has an added bonus over make not mentioned above: generics (as of 1.18).
Let's say you have a set of flat (all fields are primitives) structs, like the following:
type SomeStruct struct {
V1 string `json:"v1"`
V2 string `json:"v2"`
}
and you want to create a mapping function that turns a map[string]string into any struct. Then you could write:
func GetStructFromMap[T any](values map[string]string) (T, error) {
myStr := T{}
bytes, err := json.Marshal(values)
if err != nil {
return *myStr, err
}
if err := json.Unmarshal(bytes, str); err != nil {
return *myStr, err
}
return *myStr, nil
}
but, this code will throw an error, with regards to the line myStr := T{}
, about an invalid composite value. Replacing it with myStr := make(T)
will through another error about no underlying type. So, you'll to replace the line with myStr := new(T)
which will create a reference to a zeroed value instance of the struct.
As can be seen, when dealing with generics, new
can be used to instantiate a type that is unknown at compile time.
As a side, you could have also used named return types in this specific example, but the more general usage still stands.