notexOPENOverview
Notex/Languages/Allocating with new
04.03LESSON 03 OF 20 · 5 MIN READ

Allocating with new

Always free to read

The built-in function new creates a pointer variable. It returns a pointer to an instance of the provided type or expression. If you pass a type to new, the pointer points to a zero-value instance of that type.

newFromType := new(int)
fmt.Println(newFromType == nil) // prints false
fmt.Println(*newFromType)       // prints 0

The new function provides a convenient way to generate a pointer for a constant value. Unlike a variable, you can’t use an & before a constant or a primitive literal (such as numbers, Booleans, and strings) because they don’t have memory addresses; they exist only at compile time. Instead, wrap the constant or literal with a call to new:

newFromConstant := new(10)
fmt.Println(*newFromConstant)   // prints 10

This works with struct fields as well:

p := person{
  FirstName:  "Pat",
  MiddleName: new("Perry"),
  LastName:   "Peterson",
}

To create a pointer to an empty struct, you can pass the struct’s type to new, pass a struct literal to new, or use an & before a struct literal. All of these statements are functionally equivalent:

x := new(person)
y := new(person{})
z := &person{}

The new() function, variables, and shallow copies

You can use new with a variable as well. There is, however, a big difference between using an & before a variable and passing a variable to new. When you use the & operator, it returns the memory address where the variable is stored. When you pass a variable to new, it allocates new memory (perhaps unsurprisingly, given the name), copies the value of the variable into that new memory, and returns the memory address of that copy. This means that modifications to the variable returned from new are made to a copy, but modifications to the variable returned by using the & operator are made to the original:

a := "hello"

newA := new(a)
fmt.Println(*newA)       // prints hello
*newA = "goodbye"
fmt.Println(*newA, a)    // prints goodbye hello

addressOfA := &a
fmt.Println(*addressOfA)     // prints hello
*addressOfA = "peace"
fmt.Println(*addressOfA, a)  // prints peace peace

The copy made by new is a shallow copy. All values are duplicated from the original to the copy, but when the value being copied is a pointer, the copy points to the same memory address as the original; the pointed-to memory isn’t duplicated.

The difference between new and &

Figure 5-3. The difference between new and &

The flow through this code is shown in the memory chart in Figure 5-3, which needs a little explanation. A string is implemented like a struct, with two fields. One field is the length of the string; the other is a pointer to memory where the string’s bytes are stored. When you call new(a), a new block of memory is created with a copy of the string’s length and pointer. A new pointer variable is also created; its value is the memory address where that copy is stored. The pointers in both a and newA point to the same memory, but Go does not let you directly access the memory holding the string’s bytes unless you do terrible things via the unsafe package. Changing the value of the string pointed to by newA modifies the copy and the original a is untouched.

Meanwhile, if you use the & operator, you create a variable whose value is the memory location of a. Since addressOfA points to a, any change made to the value stored at addressOfA also modifies a. This can lead to somewhat confusing behavior when you pass a struct variable to new:

bob := person{
    FirstName:  "Bob",
    MiddleName: new("Robert"),
    LastName:   "Bobberson",
}
newBob := new(bob)
newBob.FirstName = "John"
*newBob.MiddleName = "Jingleheimer"
fmt.Println(bob.FirstName, newBob.FirstName)     // prints Bob John
fmt.Println(*bob.MiddleName, *newBob.MiddleName) // prints Jingleheimer Jingleheimer

You might expect some consistency; either every change to newBob also changes bob, or none of them do. Why can you change the value of bob.MiddleName via newBob even though, in the previous example, you couldn’t change the value of a via newA? All of the fields in bob are copied to newBob. Making a change to FirstName or LastName in newBob has no effect on bob, because they are copies of a string. But MiddleName is a *string. Copying the value of the MiddleName field in bob means copying the memory address that MiddleName points to. Since the MiddleName field in both bob and newBob point to the same memory, a change to the value stored at that memory address shows up in both bob and newBob. The diagram in Figure 5-4 shows what happens in each step:

Using +new+ with a struct variable

Figure 5-4. Using new with a struct variable

If you assign a new pointer to newBob.MiddleName, bob and newBob will no longer share memory.

newBob.MiddleName = new("Doug")
fmt.Println(*bob.MiddleName, *newBob.MiddleName) // prints Jingleheimer Doug

This is shown in Figure 5-5:

Reassigning the pointer within the shallow copy

Figure 5-5. Reassigning the pointer within the shallow copy

Shallow copies are not unique to new; they’re everywhere in Go. As mentioned in “Copying Maps”, maps.Clone makes shallow copies of maps. The built-in copy function makes shallow copies of every duplicated slice entry. In fact, every time you call a function, the parameters are populated with shallow copies. The same is true in many programming languages with both value types and pointer types. (C++ and Swift are notable exceptions.)

Note

The new function gained the ability to create a pointer to an expression in Go 1.26. If you are using Go 1.25 or earlier, use a helper function instead:

func makePointer[T any](t T) *T {
    return &t
}

With that function, you can now write:

p := person{
  FirstName:  "Pat",
  MiddleName: makePointer("Perry"),
  LastName:   "Peterson",
}

Why does this work? When you pass a constant to a function, the constant is copied to a parameter, which is a variable. Since it’s a variable, it has an address in memory. The function then returns the variable’s memory address. This is effectively what the built-in new function is doing, too, which is why new creates a shallow copy of a variable when a variable is passed to it.

Writing generic functions is covered in the related discussion.

Progress is saved on this device.
KEEP EXPLORING

One note leads to another.

Back to the library →
⌕
Search across the entire library ⌘ K