Today’s topic: Allocation!
What? You thought allocation was relegated to languages like C and C++?
Yeah, that’s actually pretty close to true. We’re not learning about malloc today. Rather, we’re learning about the built-in function new, which gives us just one (of many!) ways to allocate memory for a variable.
Allocation
The built-in function new takes a type
T, allocates storage for a variable of that type at run time, and returns a value of type*Tpointing to it. The variable is initialized as described in the section on initial values.new(T)For instance
type S struct { a int; b float64 } new(S)allocates storage for a variable of type
S, initializes it(a=0, b=0.0), and returns a value of type*Scontaining the address of the location.
A natural, and expected, question after reading that, would be: Okay, but when would I use new, and why?
Before I answer that, let’s look at some equivalents and near equivalents.
type S struct { a int; b float64 }
x1 := &S{}
x2 := new(S)
var y *S
var z S
x3 := &z
In this code, x1, x2, and x3 are equivalent. They’re all pointers to an instance of the zero value of S. (They aren’t equal, in that they all point to distinct instances, so their pointer values are different.)
Of note, y, is not the same. It is of the same type, but it is actually a nil pointer of type *S.
So now then, let’s get back to when and why to use new as opposed to these alternatives.
First, var z S; x3 := &z is a rather cumbersome way to initialize x3, so it quickly falls out of the running. That leaves &S{} and new(S). Which to choose?
This mostly boils down to preference, in the majority of cases. My preferences is usually for the &S{} syntax. Most important, it’s easier to expand, if I later decide to initialize any of its members to a non-zero value:
x1 := &S{a: 3}
And that relates to the second point I’ll make, and when I might choose new as an alternative: If I want to convey semantically that the value I’m creating is explicitly a pointer to the zero value, I might choose new. It’s rare that I want to do this. It’s rare that I use new. Except…
The one exception to all this, is the one time when new is the only good option. When is that?
When you’re trying to create a new value within a generic function.
func Foo[T any]() (T, error) {
/* Some logic */
if err != nil {
return nil, err
}
/* more logic */
}
This above code won’t work, because nil is not a valid value for all members of T. T might be a string or int, which cannot be nil. So how do we return the correct zero value of T?
Technically, there are options, but the most concise one is:
return *new(T), err
Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)