Home > front end >  Why would I make() or new()?
Why would I make() or new()?

Time:12-16

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 is 0 for int, "" for string and nil 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, thus new([]int) returns a pointer to nothing

  • make(T) - Allocates memory for referenced data types (slice, map, chan), plus initializes their underlying data structures

    Example: 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.

    1. 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.

    1. 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;

    enter image description here

    var p *[]int = new([]int)
    or
    // *p == nil; with len and cap 0
    p := new([]int)
    

    which is only rarely useful.

    enter image description here

    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.

    •  Tags:  
    • go
    • Related