Repository navigation
spec: expression to create pointer to simple types #45624
Description
(Latest proposal at #45624 (comment); --adonovan)
This notion was addressed in #9097, which was shut down rather summarily. Rather than reopen it, let me take another approach.
When &S{} was added to the language as a way to construct a pointer to a composite literal, it didn't quite feel right to me. The allocation was semi-hidden, magical. But I have gotten used to it, and of course now use it often.
But it still bothers me some, because it is a special case. Why is it only valid for composite literals? There are reasons for this, which we'll get back to, but it still feels wrong that it's easier to create a pointer to a struct:
p := &S{a:3}
than to create a pointer to a simple type:
a := 3
p := &a
I would like to propose two different solutions to this inconsistency.
Now it has been repeatedly suggested that we allow pointers to constants, as in
p := &3
but that has the nasty problem that 3 does not have a type, so that just won't work.
There are two ways forward that could work, though.
Option 1: new
We can add an optional argument to new. If you think about it,
p := &S{a:3}
can be considered to be shorthand for
p := new(S)
*p = S{a:3}
or
var _v = S{a:3}
p := &_v
That's two steps either way. If we focus first on the new version, we could reduce it to one line by allowing a second, optional argument to the builtin:
p := new(S, S{a:3})
That of course doesn't add much, and the stuttering is annoying, but it enables this form, making a number of previously clumsy pointer builds easy:
p1 := new(int, 3)
p2 := new(rune, 10)
p3 := new(Weekday, Tuesday)
p4 := new(Name, "unspecified")
... and so on
Seen in this light, this construct redresses the fact that it's harder to build a pointer to a simple type than to a compound one.
This construct creates an addressible form from a non-addressible one by explicitly allocating the storage for the expression.
It could be applied to lots of places, including function returns:
p := new(T, f())
Moreover, although we could leave out this step (but see Option 2) we could now redefine the & operator applied to a non-addressible typed expression to be,
new(typeOfExpression, Expression)
That is,
p := &expression
where expr is not an existing memory location is now just defined to be shorthand for
p := new(typeOfExpression, expression)
Option 2
I am more of a fan of the new builtin than most. It's regular and easy to use, just a little verbose.
But a lot of people don't like it, for some reason.
So here's an approach that doesn't change new.
Instead, we define that conversions (and perhaps type assertions, but let's not worry about them here) are addressible.
This gives us another mechanism to define the type of that constant 3:
p := &int(3)
This works because a conversion must always create new storage.
By definition, a conversion changes the type of the result, so it must create a location of that type to hold the value.
We cannot say &3 because there is no type there, but by making the operation apply to a conversion, there is always a defined type.
Here are the examples above, rewritten in this form:
p1 := &int(3)
p2 := &rune(10)
p3 := &Weekday(Tuesday)
p4 := &Name("unspecified")
Discussion
Personally, I find both of these mechanisms attractive, although either one would scratch the itch.
I propose therefore that we do both, but of course the discussion may end up selecting only one.
Template
Would you consider yourself a novice, intermediate, or experienced Go programmer?
I have some experience.
What other languages do you have experience with?
Fortran, C, Forth, Basic, C, C++, Java, Python, and probably more. Just not JavaScript
Would this change make Go easier or harder to learn, and why?
Perhaps a little easier, but it's a niche problem.
Has this idea, or one like it, been proposed before?
Yes, in issue #9097 and probably elsewhere.
If so, how does this proposal differ?
A different justification and a new approach, with an extension of new.
Who does this proposal help, and why?
People annoyed by the difficulty of allocating pointers to simple values.
What is the proposed change?
See above.
Please describe as precisely as possible the change to the language.
See above.
What would change in the language spec?
The new operator would get an optional second argument, and/or conversions would become addressible.
Please also describe the change informally, as in a class teaching Go.
See above.
Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit.
Yes. Don't worry.
Show example code before and after the change.
See above.
What is the cost of this proposal? (Every language change has a cost).
Fairly small compiler update compared to some others underway. Will need to touch documentation, spec, perhaps some examples.
How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
Perhaps none? Not sure.
What is the compile time cost?
Nothing measurable.
What is the run time cost?
Nothing measurable.
Can you describe a possible implementation?
Yes.
Do you have a prototype? (This is not required.)
No.
How would the language spec change?
Answered above. Why is this question here twice?
Orthogonality: how does this change interact or overlap with existing features?
It is orthogonal.
Is the goal of this change a performance improvement?
No.
If so, what quantifiable improvement should we expect?
More regularity for this case, removing a restriction and making some (not terribly common, but irritating) constructs shorter.
How would we measure it?
Eyeballing.
Does this affect error handling?
No.
If so, how does this differ from previous error handling proposals?
N/A
Is this about generics?
No.
If so, how does this differ from the the current design draft and the previous generics proposals?
N/A
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsAccepted
Activity
seebs commentedon Apr 19, 2021
How much would it break things to let
new()take either a type or an expression which has an unambiguous type? Thus,new(int)ornew(fnReturningInt()), or possibly evennew(int(3)), but notnew(3)because that hasn't got an unambiguous type? This would address the stuttering, I guess?I think I dislike the implicit allocation on taking the address of non-addressible things, because I think basically all this means is that we will finally be able to replace the "loop variable shadowing and goroutines" thing with "i took the address of a thing in a map but writes to it aren't changing it" as the most frequently asked question about Go. If it only happens with &conversion(), though, that seems significantly more clear; conversion is clearly logically creating a new object, even if you convert a thing to exactly the type it already is.
So far as I can tell, object names and type names are the same namespace, it's not like C's struct-tag madness, but at any given time a given identifier refers only to one or the other.
clausecker commentedon Apr 19, 2021
Alternatively, what's the problem with adding composite literals of simple types, like
int{3}?JAicewizard commentedon Apr 19, 2021
If we have this new
new(typeOfExpression, Expression), would it be possible to donew(int32, int64(5))?? Not necessarily this specific case, but for any expression that does not match the specified type will there be an implicit conversion?I think I like the
newidea better, it is more explicit about what happens when you are taking an address of an unaddressable value.Adding
int{3}feels more of a workaround to me, than a solution. Adding a new way to do the same thing, just to solve a problem.faiface commentedon Apr 19, 2021
What about generalizing the second approach and simply allow taking the address of a function result?
Conversions are just special functions, so this would cover them.
eaglebush commentedon Apr 19, 2021
I like the
newproposal option to handle just the simple types and initialize to a value. I have created functions just for this. With the proposal approved, some of the constructs will be soon like this:...much shorter than package prepended codes like this:
Consequently...
peterbourgon commentedon Apr 19, 2021
I prefer
&T{...}tonewwhenever possible, because it permits both construction and initialization in a single expression, which I think is important. The only circumstance where it didn't work is addressed by this proposal's second option. Nice! +1 from me.As far as I can tell, this would make it possible to express all valid Go programs without using the
newbuiltin. Bonus challenge: do the same formake:) I think it would boil down to extending the struct literal initialization syntax in some way that could cover just these 4 things:benhoyt commentedon Apr 19, 2021
@peterbourgon Probably we shouldn't derail this to try to get rid of
make. :-) My preference is also the&int(3)type conversion syntax, as I too almost never usenew-- not because I dislikenew, just because it's not usually necessary. I also want to link to other previous discussions for reference (aside from #9097):aws.Intandaws.Stringto work around the lack of this feature.new(value)in a Google doc here. I don't think that's as good or orthogonal asnew(T, value), but wanted to link it for the history.I used to be in favor of
&expression(it that Rob's option "1a"?), but now I think there are too many concerns with it. For example, it means&would mean something different for an expression vs a variable:&expressionwould always give a new address, but&variablewould always given the same address -- that seems non-intuitive. Related to this is the point @seebs made that you could then write&m[k], which would make it look like map entries are addressable, but they're not. For these reasons, I think plain&expressionis a bad idea, despite it being nice and terse.faiface commentedon Apr 19, 2021
@benhoyt If you only restrict
&to variables and function calls, not arbitrary expressions, it's quite consistent because a result of a function call will naturally have a fresh address.benhoyt commentedon Apr 19, 2021
@faiface Yeah, I think that would be fine -- it doesn't have the problems with
&arbitraryExpressionthat I noted. My issue #22647 actually grew out of trying to type&time.Now()when I was fairly new to Go.clausecker commentedon Apr 19, 2021
When supporting taking the address of return values, the question on whether returning makes a copy of the return value obtains. For example, consider code like this:
@benhoy Not really in favour of the
new(value)proposal as it opens the can of worms that is having to distinguish between types and expressions in the parser (at least it seems so).clausecker commentedon Apr 19, 2021
I also kinda wonder why the obvious
&int{3}idea is not mentioned. Though yes, the type conversion comes with the obvious advantage (or possibly disadvantage?) of being more flexible with the type of its argument. Supporting both uses might even be sensible (one for when you want a type conversion to happen, possible with ago vetif there is none) and one for when you do not want a type conversion.281 remaining items
doc/next: improve new(expr) release note
adonovan commentedon Oct 27, 2025
All done, in only eleven years since #9097. ;-)
gopherbot commentedon Oct 30, 2025
Change https://go.dev/cl/716561 mentions this issue:
go/analysis/passes/modernize: newexpr: add //go:fix inline directivesgo/analysis/passes/modernize: newexpr: add //go:fix inline directives
arvenil commentedon Feb 11, 2026
Hello,
Curiosity, why the example insist on json serialization package using pointer to represent an optional value when optional value can now be also achieved with
omitzerowhich lets us get rid of pointer all together?swtch1 commentedon Feb 11, 2026
Because 0 != not specified. I can think of a trivial example. Let's say a program says "specify a value to set the gauge".
adonovan commentedon Feb 11, 2026
The answer is in the example:
A newborn baby has an Age of zero; an Age of nil means the Person declined to reveal their age. The
omitzerotag would cause the baby's known age of zero to be lost during marshaling and unmarshaling. What we want is that the nilness or otherwise of the field is preserved, and that we can distinguish nil from a non-nil pointer to an age of zero.Merovius commentedon Feb 11, 2026
FWIW I assumed @arvenil was referring to something like
and then adding
json:"omitzero"to anyOptional[T]field. Which, previously, didn't work, because there was no way to make a field marshal into an omitted field.That being said
I don't think it insists on anything. If you don't want to use pointers for optionals, you don't have to. But empirically, a lot of packages do and in those cases,
new(expr)is useful. It's just an example, no need to read more into it than that.siutsin commentedon Feb 18, 2026
In https://cs.opensource.google/go/go/+/refs/tags/go1.26.0:src/builtin/builtin.go;l=223-226, it still mentions:
gopherbot commentedon Feb 18, 2026
Change https://go.dev/cl/746561 mentions this issue:
builtin: update new function commentgopherbot commentedon Feb 18, 2026
Change https://go.dev/cl/746481 mentions this issue:
builtin: document new(T)gopherbot commentedon Feb 18, 2026
Change https://go.dev/cl/746481 mentions this issue:
builtin: document new(V)aclements commentedon Feb 20, 2026
Just to close the loop: #77584 tracks the incorrect documentation in the
builtinpackage. Thanks for pointing that out, @siutsin !Refactor To function to use new for pointer creation