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
Comment From: seebs
How much would it break things to let new()
take either a type or an expression which has an unambiguous type? Thus, new(int)
or new(fnReturningInt())
, or possibly even new(int(3))
, but not new(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.
Comment From: clausecker
Alternatively, what's the problem with adding composite literals of simple types, like int{3}
?
Comment From: JAicewizard
If we have this new new(typeOfExpression, Expression)
, would it be possible to do new(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 new
idea 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.
Comment From: faiface
What about generalizing the second approach and simply allow taking the address of a function result?
p := &f(...) // for any f
Conversions are just special functions, so this would cover them.
Comment From: eaglebush
I like the new
proposal 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:
i := new(int, 42)
...much shorter than package prepended codes like this:
i := stdutil.NewInt(42)
Consequently...
i := new(int, func() int {
r := rand.New(rand.NewSource(99))
return r.Int()
}())
Comment From: peterbourgon
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.
I prefer &T{...}
to new
whenever 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 new
builtin. Bonus challenge: do the same for make
:) I think it would boil down to extending the struct literal initialization syntax in some way that could cover just these 4 things:
make(chan T, n)
make(map[T]U, n)
make([]T, n)
make([]T, n, m)
Comment From: benhoyt
@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 use new
-- not because I dislike new
, just because it's not usually necessary. I also want to link to other previous discussions for reference (aside from #9097):
- I opened a similar issue a few years ago (#22647), which I think is useful because it has a brief "experience report" in the description -- for example, the AWS SDK has functions like
aws.Int
andaws.String
to work around the lack of this feature. - There's also a 2014 proposal to allow
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: &expression
would always give a new address, but &variable
would 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 &expression
is a bad idea, despite it being nice and terse.
Comment From: faiface
@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.
Comment From: benhoyt
@faiface Yeah, I think that would be fine -- it doesn't have the problems with &arbitraryExpression
that I noted. My issue #22647 actually grew out of trying to type &time.Now()
when I was fairly new to Go.
Comment From: clausecker
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:
func addressTaker(x int, z **int) (y int) {
y = x
*z = &y
}
func example() {
var ptr *int
x := &addressTaker(42, &ptr)
// at this point, does x == ptr hold?
}
@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).
Comment From: clausecker
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 a go vet
if there is none) and one for when you do not want a type conversion.
Comment From: mcandre
Rob, don't tell me about such a gap. I was implementing Bliss interface for so long.
Comment From: robpike
@clausecker Because why add a new construct (&int{3}
) when you can use an existing one?
Comment From: clausecker
@robpike Compound literals too are an existing construct and taking the address of them is already legal. So it's as much “adding a new construct” as the &int(3)
idea is; in both cases the rules need to be made more lenient to support a case that was previously not allowed with no syntactical changes; in case of &int(3)
taking the address must be made legal, in case of &int{3}
using a composite literal for a scalar.
Comment From: ninedraft
The new(T, value)
variant has an unpleasant feature: for boolean and string values, it adds excessive visual noise. For example: new(bool, true)
, new(string, "bottle of ram")
.
As far as I understand, only numeric literals have a problem with unambiguous type inference.
With the above in mind, &
+ typecast seems like a more viable approach for me, if it will allow us to omit type in string and boolean cases.
Examples:
_ = &int(42)
_ = &true
_ = &"brains"
type Name string
_ = &Name("what's my name?")
type Count int64
_ =&Count(100500)
Comment From: thejerf
The Go 2 playground permits the function:
func PointerOf[T any](t T) *T {
return &t
}
If I break this issue up into cases, I end up with either "I need this zero times in a module" (by far the dominant case), "I need this once or twice" in which case I would just take the couple of extra lines, and "I need this all over the place" in which case, either define that function or pull it in from somewhere once the generics are out. If one is using this a lot one may prefer a shorter name than PointerOf
, I was just going for maximum clarity over length.
I'd suggest just waiting for generics to drop and writing/providing that function.
Comment From: zkosanovic
@ninedraft
With the above in mind, & + typecast seems like a more viable approach for me, if it will allow us to omit type in string and boolean cases.
But you can't omit the type. The description clearly says that type conversion will be addressable, not the values themselves.
It would have to be:
_ = &bool(true)
_ = &string("brains")
And TBH I'm fine with that. Having something like &"foobar"
feels a bit... odd.
But either way, having the Option 2 would be very cool IMO.
Comment From: smasher164
I'd suggest just waiting for generics to drop and writing/providing that function.
While it's true that generics would allow you to write the PointerOf
function, I think this (second) proposal would make it much easier to learn the language. Having to write/use a function for something that has first-class syntax with composite literals is counterintuitive.
Comment From: sanggonlee
If I can add voice here, I would much prefer option 2 than 1.
The fact that simple type literals had deeper underlying types was hidden away from convenience syntax anyway (for example, 3
having int
type as default while it could also have been int32
).
Syntax in option 1 seems a bit awkward passing two args separately, one for type and one for expression even though the two are inherently bound with each other.
Technically the same goes for option 2, but in this case at least it gives a stronger visual cue that the 3
belongs to the int32
type in &int32(3)
, which seems more consistent with type conversion form used widely.
Comment From: sethvargo
Do we have any data on new()
vs &{}
usage in the wild? Anecdotally (and supported by others on the threads), I feel like &{}
is far more common than new
, but it would be excellent if we had some data to back that up.
I'm definitely preferential to option 2 (&int64(11)
).
Comment From: rh-kpatel4
Why not &(int64(11))
? This is proper scoping to take the output of ()
and return pointer to it &()
?
Comment From: FiloSottile
What about generalizing the second approach and simply allow taking the address of a function result?
go p := &f(...) // for any f
Indeed, I understand the difference between conversion and function calls, but I feel like people learning Go will be confused by &int(3)
working while &add(1, 2)
doesn't.
Function calls have defined types, so I can't think of any issue with taking their pointer, and I definitely had to be reminded by the compiler that it wasn't allowed a few times.
I never use new()
simply because I don't want to choose between two ways of doing the same thing, so I am partial to doing just Option 2, but the last part of Option 1 feels like a better landing place for &
completeness.
Comment From: earthboundkid
I like that Jerf's PointerOf
function adds nothing to the language itself. It could be added to the builtins as newof
or newval
or something. With the addressTaker example above, it allocates a new pointer for x, which is unambiguous.
Comment From: bcmills
All of the proposed options seem better than the status quo, but still have the downside of requiring types to be written out explicitly even when they are obvious from the value. Compare:
d := time.Millisecond
p1 := &d // No noise from types!
vs.
p1 := new(time.Duration, time.Millisecond)
p2 := &time.Duration(time.Millisecond)
In contrast, the generic approach (https://github.com/golang/go/issues/45624#issuecomment-822487722) does not stutter on types, but requires the introduction of a new name for the generic function.
So I wonder if it would be preferable to add a generic builtin instead:
d := ptrTo[time.Duration](time.Millisecond)
or
d := ptrTo(time.Millisecond)
I don't feel strongly about the specific name, but I think the ergonomics of a generic function are much nicer than the proposed ergonomics of new
.
Comment From: fkarakas
When initially @chai2010 made the first proposal, it was considered as "adding a third syntax seems not a good plan" now that rob pike propose it, it is wonderful !!! so go maintainers you can do whatever you like....
Comment From: clausecker
One thing to keep in mind about the &foo(x)
syntax is that by design, it cannot catch type mismatches. For example, if you accidentally use a constant belonging to the wrong enumeration, there's no way for the compiler to catch that as you have an explicit type cast there. If it was &foo{x}
(possibly supported as an additional option), the compiler could reject such code as being wrongly typed. Do we really want to introduce mandatory quasi implicit casting for this feature?
Comment From: rsc
The overloading of & for "take address of existing value" and "allocate copy of composite literal" has always been unfortunate. An alternative to expanding the overloading of &
would be to overload new
instead, so that it is the generic ptrTo
function as well as the original new(T)
, as in new(1)
. Then &T{...}
can be explained retroactively as mere syntactic sugar for new(T{...})
.
Comment From: rsc
@fkarakas:
When initially @chai2010 made the first proposal, it was considered as "adding a third syntax seems not a good plan" now that rob pike propose it, it is wonderful !!! so go maintainers you can do whatever you like....
For what it's worth, Rob clearly credits the proposal you mentioned and says that he thinks it was closed too quickly. Restarting a discussion is clearly better than never changing our minds as new evidence arrives and never admitting when we may have made a mistake. I talked at length about context and how added context or new experience can lead to different outcomes in my talk at https://blog.golang.org/toward-go2. None of us are perfect, and whether an idea is adopted inevitably depends as much on whether the time is ripe for that idea as on the details of the idea itself. Cheers.
Comment From: randall77
The problem that I see with &int{1}
is that it begs the question: what does just int{1}
mean? Is it the same as int(1)
? Why are there two ways to say the same thing?
Comment From: rsc
Another problem with &foo{x} is what it means when foo and x are both type []interface{}. Then there is only one way to say two different things.
Comment From: Laremere
Does this proposal limit this behavior to literals? The forms used are not obviously limited. If they're not: Comparing and contrasting multiple examples with this proposal:
Go today:
a := 1
b := &a
*b = 2
fmt.Print(a,*b) // 2 2
Using cast to pointer:
a := 1
b := &int(a)
*b = 2
fmt.Print(a, *b) // 1 2
Using extended new:
a := 1
b := new(int, a)
*b = 2
fmt.Print(a, *b) // 1 2
ptrTo with generics:
a := 1
b := ptrTo(b)
*b = 2
fmt.Println(a, *b) // 1 2
All three of these examples show that the behavior is subtly different than taking a pointer to a local variable. It would be reasonable to use this new construct in a more complicated context (ie, more lines between the different statements in the examples). For a novice or someone otherwise not familiar with the syntax, it's important it's obvious the code is doing something different, and what that difference is.
I feel that casting performs worst in this test. A cast may look like a function call, but it doesn't feel like one. So this proposal breaks some new ground on the specifics of their behavior. I think extended new performs better on this test, and ptrTo performs best. They simply follow the convention of a value type passed to a function call.
Alternatively,
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.
It's not obvious to me why this wouldn't work.
If I never specify a type in
v := 3
p := &v
Then why does p := &3
need a type? What's wrong with using the same rules as declaring a variable with no type specified?
Comment From: rsc
p := &3 doesn't work because it must be limited to some narrow set of forms. Otherwise the meaning of &f().x is different for f() returning pointer-to-struct and f() returning struct. Similarly &m["x"] is a compile error today but would silently make a copy tomorrow rather than produce a pointer to the value in a map. All of that would be incredibly confusing and the source of many subtle bugs.
Comment From: seebs
Gosh this whole thing turns out to be ridiculous, we already have a completely transparent and easy to type way to do what people mean when they try to write x := &int{3}
:
x := &((&[1]int{3})[0])
I think I'd like to put in a vote for "allow {} initializers for non-compound types by treating them as sort of an implicit [1] of their type". (Oh, but I do see the difficulty with cases like []interface...)
But that leads to the question: should you be allowed to specify the key?
x := &int{0: 3}
Comment From: xaionaro
Personally I use:
p := &[]int{v}[0]
May be it just makes sense to allow shorthanding of []int{v}[0]
to {v}
:
p := &{v}
Thus:
* p := &{v}
points to the copy of v
. Here, v
might be anything, for example myFunc()
: p := &{myFunc()}
; or 3.1416
: &{3.1416}
(will be float64*
, since f := 3.1416
is float64
).
* p := &v
points to the v
itself.
Though I'm not sure if Go is about syntax sugar.
Comment From: seebs
So, in times of longago, the C standard just sort of handwaved a ton of stuff by saying "well, obviously, any object of a type is also an array of one of that type". So you're allowed to bracket initializers all over the place:
int i = (int){8};
int *ip = &(int){8};
int j = {8};
Of course, they don't have interface{}
to deal with.
Right now, the reason that &literal{...} works is sort of a subtle side-effect of garbage collection and escape analysis; you're allowed to declare objects, and if they escape, they can get allocated, so if the pointer escapes, it causes an allocation, and otherwise it's not a "real" allocation any more than any other variable declaration is, and we're just using a stack address. Whereas new()
sort of carries the implication that it's going to "be allocated" even if in fact the pointer doesn't escape and doesn't need heap allocation.
I think that letting new(expr)
work like p := new(T); *p = expr
is probably reasonable and harmless, and given that, I might well use new(expr) more and &literal{} less, because it would be clearer what it was doing and why. The reason I mostly don't use new
is that it's stuttery and requires me to distinguish between allocating the zeroed object and populating it.
Comment From: Laremere
@rsc Thanks, I see the issue in that. That's not quite what I was asking, but working through asking the question more precisely, I now see the issue. Adding here for the benefit of the proposal or others who don't see the problem:
Today the go spec states:
Taking the address of a composite literal generates a pointer to a unique variable initialized with the literal's value.
It seems reasonable that "composite" could be removed from that sentence. That is, any Literal
(as defined here) value can be initialized as a pointer to the literal's value.
The only literal values beyond composite literals are numbers, runes, strings (all are BasicLit
), and functions (FunctionLit
).
Runes, strings, and functions all have well defined types and would all work fine. Custom types would be a bit awkward, but otherwise still work:
p4 := (*Name)(&"unspecified")
That leaves numbers, which already have well defined rules from determining their type when none is specified. eg, &3
would be *int
, and &1.2
would be *float64
. However how would you get a pointer to a byte? Typically a cast is used to coerce the number constant to resolve into the desired type. However &byte(3)
is not getting the address of a Literal
, it's getting the address of a result from a cast.
Without the issue of numbers, I think it would be a reasonable extension of the current behavior, making composite literals less special. It would still be the case that &
has two meanings, just one of them would be slightly more powerful.
I suppose you could allow for (*byte)(&3)
, where &3
is a "pointer number literal" which would be resolved to a pointer to a specific number type using similar rules to how plain numbers are resolved. That certainly adds complexity equal to or greater than the main proposal, though it would be limited to just number literals. I'm not sure if I like it or not.
Comment From: nemith
As a point of why this would be nice, thrift uses pointers for optional fields in the generated code with nil representing a missing field (zero value is not an option). So thrift library contains a bunch of functions to pointerize literals.
https://github.com/apache/thrift/blob/master/lib/go/thrift/pointerize.go
Moving forward with generics perhaps this could be dealt with a optional wrapper type, or a generic pointerize function.
Comment From: DeedleFake
I've wanted this on more than one occasion, but most of the time that I've wanted it I wanted it to give a value to something optional, such as when initializing struct fields:
type Config struct {
Address *string
}
// ...
c, err := CreateClient(Config{
Address: &string("localhost:12345"), // Doesn't work, obviously.
})
I have to wonder if this issue will disappear automatically over time once generics are in as optionality is technically only a side effect of pointers, which is why a lot of things also return a boolean to signal validity of their primary return instead of just returning a pointer. Generics, though, can create a more properly signaled optionality:
type Optional[T any] struct {
v T
ok bool
}
func Some[T any](v T) Optional[T] {
return Optional[T]{v: v, ok: true}
}
func None[T any]() Optional[T] {
return Optional[T]{ok: false}
}
type Config struct {
Address Optional[string]
}
// ...
c, err := CreateConfig(Config{
Address: Some("localhost:12345"),
})
And then, after finishing writing this, I took a look at the new comment that loaded in right above... You beat me to it, @nemith.
Comment From: slrz
The new
extension looks very nice and clean. Probably worth it even without introducing the &expression
shorthand for new(typeOfExpression, expression)
.
Comment From: travisjeffery
I prefer adding the new
parameter---e.g. new(int, 3)
. Looks a lot cleaner and simpler from a language standpoint by not overloading &
.
Comment From: mdempsky
I like either option and would support adding both. Within option 1 though, I favor simply new(3)
rather than new(int, 3)
, as several have suggested above already.
How much would it break things to let
new()
take either a type or an expression which has an unambiguous type?
It would not break anything. The parser and type checker already need to be able to distinguish whether e1(e2)
is a conversion or function call depending on whether e1
is a type expression or value expression.
Thus,
new(int)
ornew(fnReturningInt())
, or possibly evennew(int(3))
, but notnew(3)
because that hasn't got an unambiguous type?
3
has an unambiguous type: the default type int
. Only the value nil
has no default type.
Comment From: HALtheWise
I would be in support of Option 2 or the extension to all function calls mentioned several times in this thread because it most obviously feels like simply removing an existing restriction, rather than adding any new behavior that needs explaining. Go doesn't generally encourage or make use of variadic functions with different behavior depending on their argument count, and when I hear of a two-argument form of "new" I intuitively expect it to behave like the multiple argument form of make(), which is the only other weird built-in like that today. Option 1 doesn't really do that, and as a result adds some extra mental overhead to remember a rule I almost never use, or for new users to look up what it means when they come across it.
I know that @rsc wishes that everyone had standardized on the new() form rather that &t{}, but my sense is the latter is more common today, and we should not try to fight that too hard.
Comment From: icholy
Allowing the following 2 forms would address the majority of use-cases without any of the footguns:
&AnyLiteral
&Type(AnyLiteral)
As @Laremere pointed out, it's also a minimal change to the spec.
Comment From: smasher164
uses pointers for optional fields in the generated code with nil representing a missing field (zero value is not an option).
This is the same approach taken with many of the GraphQL and Avro libraries in Go. I would venture to say any serialization or RPC framework encounters this issue. Codebases end up either pulling in or redefining functions like PtrTo[Int|Float64|...]
.
Comment From: ysmood
How about:
type Cube struct {
Size int
}
a := new(Cube{Size: 10})
b := new(10) // by type infer it's int
var c int64 = new(10) // tell compile what type we want
d := new("string")
e := new(1.0) // float64
Comment From: rogpeppe
To reiterate this comment a bit, Limbo had an builtin-function named ref
which did exactly that. In Go-with-generics, it could be defined as:
// ref returns a pointer to the value of t.
func ref[T any](t T) *T {
return &t
}
Given this possibility, I don't see that there's any need to change new
or the language syntax itself to accommodate this functionality.
We could define a built-in function with exactly this signature and behaviour (ptr
?) which would fulfil the requirements of this proposal in a more ergonomic and flexible way than proposed (no need to mention the type name; can operate fine on any expression including function calls).
We could even do that before generics land in the language without problem.
So all these would work:
ref(123) // *int
ref(make([]string, 0, 3)) // *[]string
ref("hello") // *string
ref(ref(string)) // **string
ref(os.Stdin.Name())
For what it's worth, I still like the name ref
even though Go, unlike Limbo, doesn't call pointers "ref".
Here's the above in the playground: https://go2goplay.golang.org/p/UV0z1TxjxRh
Comment From: rogpeppe
FWIW I suggested the above solution back in 2016.
Comment From: ziutek
I think @rsc and @ysmood proposal about overloading new
built-in function fits best into the language.
If the x := new(int)
can be treated as the short form of
var a int
x := &a
then x := new(10)
can be the short form of
a := 10
x := &a
@ysmood, in my opinion the
var c int64 = new(10)
should return "type mismatch" error.
I don't know what to do with
var c *int64 = new(10)
It probaly require to introduce something like untyped int pointer to the language. Without such addition we need to write it like this
var c *int64 = new(int64(10))
Comment From: davecheney
I’d like to throw my hat into the ring for option 2, i := &int(3)
Despite the pain that proto has inflicted on Go users, I don’t think the use case is so general that it requires extending new.
Comment From: ziutek
The problem with overloading new
I see is:
A := 56
x := new(A)
If the x := new(A)
is far away from A := 56
it's not obvious does A is a type or constat/variable.
Comment From: carleeto
I prefer the two parameter version of new.
Yes, it is a little more typing, but it requires you to be explicit about the type and therefore, your intent:
x := new(int,3) x := new(int8,3) x := new(uint16,3)
That said, if all you wanted was an int, I don't see the harm in supporting a single parameter version too:
x := new(3)
Or, for that matter float64:
x := new(3.4)
Comment From: rogpeppe
I'm not so keen on overloading the first parameter of new
, as there are no other cases in the language (AFAIK) where a built-in function takes either a type or an expression as an argument.
Neither do I like the idea of adding an optional extra parameter to new
when most use cases can avoid mentioning the type name.
Given that we can implement this functionality with nothing beyond what's already proposed for the language, I vote strongly for the "less magic" approach of defining a generic built-in function which will become still less magic when generics actually arrive.
The choice of name is tricky though: I'm not keen on ptrTo
because no other language built-in uses camel case; ref
doesn't fit with any other existing naming: and ptr
uses a contracted form which isn't seen elsewhere (except for the rarely used println
, I guess).
Comment From: ruyi789
int x=1,y=2,z=3 or var (x=1,y=2,z=3} int
if()//support it
Comment From: earthboundkid
The choice of name is tricky though: I'm not keen on
ptrTo
because no other language built-in uses camel case;ref
doesn't fit with any other existing naming: andptr
uses a contracted form which isn't seen elsewhere (except for the rarely usedprintln
, I guess).
At the risk of devolving into 🚲-ing, I like newof
or newval
because the returned value is not a pointer or reference to any existing value. It's a pointer to the copy made by the function call. Having new in the name makes it clear that e.g. ptr := newof(mymap[key])
is not equivalent to (the illegal) ptr := &mymap[key]
because it's a new pointer.
Comment From: urbanishimwe
I would prefer &int(3)
.
I think we would need to allow this syntax: &T
.
like p := &int
and value
at p
address will be the zero value of int
type.
this is because we already have new(T)
which does pretty much the same thing.
Comment From: hardboiled
My team has a lot of configuration data from our users that is similar to but does not exactly mirror internal API endpoints that use this configuration data.
This results in a lot of situations where structs composed of pointer value members need to have sane assignments for defaults, especially in tests. Because go doesn't currently support inlining non-struct pointer assignments, we have situations like this:
// example init statement from a test
multiAz := true
storageEncrypted := true
dbConfig := &DatabaseConfig{
Identifier: "id",
InstanceClass: "db.t3.small",
InstanceType: "mysql",
EngineVersion: "1.1.1",
MultiAz: &multiAz,
StorageEncrypted: &storageEncrypted,
}
It would be nice to update this test to use the following format in the future.
dbConfig := &DatabaseConfig{
Identifier: "id",
InstanceClass: "db.t3.small",
InstanceType: "mysql",
EngineVersion: "1.1.1",
MultiAz: &bool(true),
StorageEncrypted: &bool(true),
}
It definitely would improve readability and code quality in our code IMO. Hope this example was useful to others here to explain why this could be helpful, and thanks for debating the merits here!
Comment From: dolmen
Edit: this comment is redundant with https://github.com/golang/go/issues/45624#issuecomment-822594892
Original content
Note that **pointer expressions** to any value **already exist**. They are just ugly. Example (see [on the Go Playground](https://play.golang.org/p/78bRuT-u5TZ)):// Pointer to an int64 value (3)
int64Ptr := &(&(struct{ int64 }{3})).int64
fmt.Printf("%T %v\n", int64Ptr, *int64Ptr)
myfunc := func() string { return "foo" }
// Pointer to a value returned by a func
stringPtr := &(&(struct{ string }{myfunc()})).string
fmt.Printf("%T %v\n", stringPtr, *stringPtr)
Edit: shorter expression, thanks to @mdempsky:
int64Ptr := &[]int64{3}[0]
Comment From: dolmen
A common workaround is to use a package named ptr
which exposes conversion functions for each type.
boolPtr := ptr.Bool(true)
I don't think that new(bool, true)
would be better than what has become an idiom.
Examples: * go.uber.org/thriftrw/ptr * github.com/aws/smithy-go/ptr * github.com/AlekSi/pointer * others...
Comment From: xaionaro
A common workaround is to use a package named
ptr
which exposes conversion functions for each type.
It is impossible. If I will create a custom type then it will be unknown to any of those libraries.
Comment From: dolmen
It is impossible. If I will create a custom type then it will be unknown to any of those libraries.
If you create a custom type T
, you can as well create its pointer constructor: func ptrT(t T) *T { return &t }
. And it doesn't have to be in the same package. A little copying is better than a little dependency.
Comment From: mdempsky
@dolmen
A little copying is better than a little dependency.
That proverb is about making sure decisions about code structure weigh the cost of adding dependencies. It's not meant to suggest that copying code is inherently good or desirable, as you seem to apply it here. As evidence of that, notice that this proposal was submitted by the same person who coined that phrase (or at least popularized it).
Comment From: deanveloper
I’d like to throw my hat into the ring for option 2, i := &int(3)
Despite the pain that proto has inflicted on Go users, I don’t think the use case is so general that it requires extending new.
I agree with this, many builtin function signatures are already hard to read, especially when there are optional parameters. It would be unfortunate to dirty up new
’s signature into something like make
’s. I would much prefer to adapt &
to work like it does for structs than change new
’s signature to have optional arguments.
I honestly would like to get rid of new
altogether, it’s a builtin function which I am personally not a big fan of. I feel that builtin functions are best for defining operations on builtin types, and for functions impossible to define because of a lack of generics. new
serves no purpose other than saving a temporary variable, and discourages from people from using new
as a variable name (which is a very nice variable name to be able to use)
Comment From: davecheney
If you create a custom type T, you can as well create its pointer constructor
If you declare a new type T, then you can use &T{} or new(T) expressions to gain a pointer to a T.
ISTM that the various ptr packages can solve this problem for all the 20 something universe types without extending the lanaguge itself.
Comment From: dolmen
@mdempsky My reference to the proverb was on point. Packages such as go.uber.org/thriftrw/ptr are just collections of func ptrT(v T) *T { return &v }
definitions. Those functions can be aggegated in a package for convenience, or just defined locally in your code when you need them without adding a dependency.
Edit: Additional comment redundant with a previous comment about a generic `ptrTo` (but with code examples)
Also, with generics, we will be able to define a `ptr` function once for all.func ptr[T any](v T) *T {
return &v
}
intPtr := ptr(3)
fmt.Printf("%T %v\n", intPtr, *intPtr)
floatPtr := ptr(3.14)
fmt.Printf("%T %v\n", floatPtr, *floatPtr)
xPtr := ptr(map[string][1]struct{bool; string; *int}{"foo": {{true, "bar", nil}}})
fmt.Printf("%T %v\n", xPtr, *xPtr)
[See on the go2go Playground](https://go2goplay.golang.org/p/cqsC8Mf8U2M)
Comment From: ayang64
i really like that option 2 makes the syntax for obtaining the address of literals (composite or otherwise) consistent. i think extending new or adding a function or keyword leaves that inconsistently dangling so hopefully at least option 2 goes forward.
i don't think we need to extend new()
if we can get the address of any literal and i definitely don't think we need a new function or keyword.
Comment From: ayang64
I think we would need to allow this syntax:
&T
. likep := &int
andvalue
atp
address will be the zero value ofint
type. this is because we already havenew(T)
which does pretty much the same thing.
i think what we're talking about it getting the address of a value not a type. i don't see how getting the address of a type is meaningful -- that is, what do you call the &
operator when applied to a type? also, we already have foo := (*string)(nil)
.
Comment From: davecheney
@dolmen what's the use case for a pointer to a map?
xPtr := ptr(map[string][1]struct{bool; string; *int}{"foo": {{true, "bar", nil}}})
Comment From: kylelemons
Option 1a, new(typeOfExpression, Expression)
This is a bit repetitive, so while I think it does prevent needing helper functions or libraries, I don't think it's worth the addition of the second parameter.
Option 1b, p := &expression
This is convenient but I am concerned about the implications for addressbility and orthogonality. Consider:
a := &m[k]
a := &m.(T)
a := &f()
Today, none of these are addressable.
When I teach Interfaces in Go classes, I spend a lot of time talking about addressability, because a common issue I see is when new Gophers try to pass values when a pointer is necessary to satisfy an interface, and I think the concept is important to understand.
Briefly, I usually have a portion that goes something like this:
when you write
t.M()
the compiler can help you:
| (*T).M | (T).M
---+----------+----------
T | (*t).M() | t.M()
*T | t.M() | (&t).M()
However, when you store a value in an interface:
var i interface{M()}
i = t // only legal sometimes depending on Method Sets
i.M()
The compiler can (conceptually) help you only sometimes:
| (*T).M | (T).M
---+----------+----------
T | (*i).M() | i.M()
*T | i.M() | ????
The compiler can't (correctly) write
(&i).M()
But, with this proposal, it would sort-of be legal to "take the address of the value in an interface", and I feel like it starts to get a bit blurry and non-orthogonal, which might make it even harder for new folks to really grok method sets and addressability.
Option 2: &type(C)
If we think that we need a solution here, this would be my choice. It's a bit repetitive, but I think it maintains the orthogonality of the syntax, and it makes it more clear (to my reading) that the code is asking for a pointer to a newly allocated memory location, rather than how &f()
might imply that it is evaluating to the address of something that already exists in the heap.
I would personally advocate for restricting the value to being a compile-time constant, at least to start with.
Since they're not officially part of the proposal, I won't talk too much about the other suggestions, but briefly:
new(v)
(i.e. changing the first parameter of new) seems strictly worse than adding a new builtin to me.- Adding a new builtin ala
ptr(v)
to subsume the packages would be interesting, but that name is already often used. - Letting folks wait until Generics and make their own generic
PtrTo
seems fine, and those can live along with the other helpers that already exist, especially in the domains (like protobuf) that often need them.
Comment From: dolmen
@urbanishimwe wrote:
I think we would need to allow this syntax: &T. like p := &int and value at p address will be the zero value of int type. this is because we already have new(T) which does pretty much the same thing.
This could change the meaning of existing code (or at least add confusion in reader's mind) because this statement is already valid if a variable with the same name as the type exists in the scope. So I don't think this is a good idea.
// This code works now
var int int
p := &int
While this example is contrived, it is much more common to have variables named like a named type.
Example:
// In package scope:
type date string
// Deep in a function
var date time.Time
p := &date
Comment From: dolmen
@davecheney wrote:
what's the use case for a pointer to a map?
As a general question:
var m map[string]bool
p := &m
json.Unmarshal([]byte(`null`), p)
json.Unmarshal([]byte(`{"foo": true}`), p)
In the context of this proposal (pointer to a non zero value expression), I don't see any use case, as well as for pointers to func/interface/channel/pointer. I could not even find a niche use case.
Comment From: deanveloper
@urbanishimwe wrote:
I think we would need to allow this syntax: &T. like p := &int and value at p address will be the zero value of int type. this is because we already have new(T) which does pretty much the same thing.
This could change the meaning of existing code (or at least add confusion in reader's mind) because this statement is already valid if a variable with the same name as the type exists in the scope. So I don't think this is a good idea.
// This code works now var int int p := &int
While this example is contrived, it is much more common to have variables named like a named type.
Example:
``` // In package scope: type date string
// Deep in a function var date time.Time p := &date ```
wouldn’t &int and &date still point to the variables rather than the types still? nothing changed.
Comment From: rogpeppe
@davecheney
what's the use case for a pointer to a map?
There might not be a use case for a pointer to a map, but there's definitely a use case for a pointer to a slice, which has similar issues: https://play.golang.org/p/fXlCMX6EYUr
type S struct {
// Slice gets omitted even when the slice itself is non-nil.
Slice []int `json:",omitempty"`
// PtrSlice is omitted exactly when it's non-nil, even
// if the slice is empty.
PtrSlice *[]int `json:",omitempty"`
}
@kylelemons
Adding a new builtin ala ptr(v) to subsume the packages would be interesting, but that name is already often used.
I'm not sure it's used as much as you suspect. I looked in my $GOPATH (approx 25 million lines of Go code) and found only 1994 uses of ptr
as an identifier in 235 packages. Do you think it would be a problem in practice?
For comparison, I did the same search for ref
and found 7059 uses across 1897 packages, so it seems like ptr
wins by that metric.
I also looked for ptrTo
and found no uses at all.
Comment From: clausecker
We could also name the function dup
and have it work as a generic shallow-copy function. This would satisfy the requirements of this issue and be obvious in function to the reader (as it makes a copy of the argument). Issues with untyped operands remain though.
Comment From: jalavosus
@rogpeppe I'm slightly confused as to why you'd need a pointer to a slice, since slices are already nil
-able. I did up a quick playground example for this (and please excuse the variable names, I haven't slept in a day or so): https://play.golang.org/p/9XO6Q0ZI1mI, where a freshly unmarshaled struct with an omitempty
-d slice is nil.
Comment From: thejerf
The use case for "a pointer to anything that you wouldn't normally take a pointer to" is as a parameter for atomic.CompareAndSwapPointer. Niche, absolutely, and swapping a channel pointer still sounds like bad design, but the rest can happen.
Comment From: quenbyako
This is a wonderful change. I would also want to remind you about #12854, since these are very related things (i.e., it may not be necessary to explicitly indicate the type of the value to which the pointer is needed.
Guys how thumbs down be like:
Comment From: rogpeppe
@rogpeppe I'm slightly confused as to why you'd need a pointer to a slice, since slices are already
nil
-able. I did up a quick playground example for this (and please excuse the variable names, I haven't slept in a day or so): https://play.golang.org/p/9XO6Q0ZI1mI, where a freshly unmarshaled struct with anomitempty
-d slice is nil.
The issue arises when encoding, not decoding (omitempty doesn't have any effect when decoding).
Specifically, it is sometimes useful to marshal a slice as []
even when it's nil (and also to be able to choose whether to include the field when the slice has zero length while retaining the ability to omit it)
Comment From: kylelemons
@rogpeppe re ptr
naming
Thanks for pulling the data! I checked a few of the places that I use them, and they don't seem like they are likely to be a problem even if I were to allow the shadowing. It usually comes up for me when I am accepting an interface that I expect to be a pointer that is passed along to some reflect library as a way of "self documenting" the parameter's expectations. Something like:
func (r *R) Next(ptr interface{}) error {
return r.jsonDecoder.Decode(ptr)
}
I don't see any of my examples both using ptr
in this "documentation focused" way and still also needing to initialize a protobuf literal, which is where I think I would find myself using the proposed ptr
function.
So, this is a long way of saying the name ptr
would be fine with me if that's the direction this goes 👍.
Comment From: JAicewizard
I didnt fully follow the discussion, but I really feel like a function is a really non-go way to do this. We already have 2 ways of creating pointers, new
and &
(and maybe more). I really dont think we should add another way to do this. It may be very political to do it, no discussions about &int()
vs &int{}
, and simple to implement. However (in my opinion) it doesnt fit in with how go does things.
Comment From: davecheney
I stated a preference for the &int(n) form, but given this problem occurs when using encodings like gRPC, it seems like a simpler answer is for those projects to include a version of https://pkg.go.dev/go.uber.org/thriftrw/ptr for the small set of primitive types that don't support new
or compact literal initialisation.
Comment From: thejerf
@JAicewizard, the root problem here is that there's two "how go does things" that are in some conflict here.
The obvious one is generalized Go simplicity, which leads to the &PrettyMuchAnythingHere
syntax being preferred to consistently do whatever it takes to provide a pointer to whatever the result of the expression was.
The subtle one is that despite several syntax glosses that make it superficially act like a dynamic language that elides all allocations from the user, Go is explicit about allocations. It's easy to read :=
as a declaration, especially with it ignoring already-allocated values, but it's not just a declaration, it's an allocation.
Likewise, it's easy to see
for ... {
var x int
}
as a "declaration" of an int variable in the for loop, but it's not; it's an allocation.
Letting &
take a pointer to almost anything, regardless of whether what &
is operating on is an allocation or not, further elides and complicates the question of "what is an allocation".
Given that all the technical solutions proposed to date in this thread are simple and of generally low consequences of them (inasmuch as any change at this level ever is), the real issue here is a philosophical one. Should Go continue being as explicit as it is about allocations (bearing in mind it's already not exactly 100%, but neither does it completely hide them from you), or should it move to elide yet more details about allocations in the interests of being a generally easier language?
Until that question is answered, the technical conversation here is going to go in circles, as the technical issues have largely been hashed out.
Comment From: JAicewizard
Allowing x := &int(55)
or x := &int{55}
will both cause as much allocation as x := &struct{a:55}
. (that is, if x := &struct{a:55}
wouldnt allocate neither would the first 2).
Since the latter is already allowed, the first 2 dont change much wrt the explicitness of allocations right?
I understand that trying to make everything &
-able would indeed cause some problems, but I would argue that it would also make people write longer lines which is also (maybe implicitly) against the go principles.
And if we do want to have some generic function that copies and take the addres off something, that sounds very similar to the original proposal with new
. only real diference I see is how the name
@thejerf Thank you for explaining it! these threads get quite long, and its hard to follow them sometimes.
Comment From: dolmen
In #46105 I propose a less concise syntax but that allows wider use cases, with a familiar syntax (think about ShortVarDecl used in if/switch):
p1 := (x := 3; &x)
p2 := (c := rune(10); &c)
p3 := (day := time.Tuesday; &day)
p4 := (unspec := Name("unspecified"); &unspec)
ptrTime := (t := time.Now(); &t)
Comment From: quenbyako
@dolmen
p1 := (x := 3; &x)
yikes 😧
It looks like lambda functions, i don't think that this could be more useful then ambiguous in go...
Btw, i think this is overkill for this issue, f.e. p3 := (day := time.Tuesday; &day)
can be replaced by p3 := &time.Tuesday
already. so variants like p3 := &Name("unspecified")
or ptrTime := &time.Now()
looks more obvious especially for new gophers, imho
Comment From: dolmen
@quenbyako p3 := &time.Tuesday
doesn't work today. My list of examples comes from the examples in Rob's proposal. Also &time.Now()
is not covered by this proposal.
Comment From: justjoeyuk
Why is there so much complexity being thrown around? Can we not just literally bake some utilities into the core language?
pointers.String(mystring)
pointers.Int64(myint)
pointers.SomeOtherPrimitive(myotherprimitive)
The main issue I see here is tons of libraries creating their own functions for basic primitive conversions to pointers. Maybe that issue should be solved before exploring literally every other edge case imaginable. Creating basic utilities like this and baking them into the language stops immense amounts of repetition across a large number of hugely popular libraries.
Step One: Create utility functions for primitive types
Step Two: Extend functionality to allow complex types using the new
approach or whatever
At least doing the first step eliminates a massive annoyance currently rampant in many libraries. I'd refer again to https://github.com/AlekSi/pointer/issues/8
Comment From: icholy
@justjoeyuk https://www.youtube.com/watch?v=rFejpH_tAHM
Comment From: alnr
Another option to solve the ambiguity of
i := &3 // *int or *int64?
Could be to allow the syntax only when the compiler can infer the type:
var p *int32 = &3 // OK
func f(p *int64) {}
f(&58123) // OK
type S struct { p *int }
s := S{ p: &999 } // OK
x := &4412 // error: cannot infer type of x
var tooSmall *int8 = &1024 // error: literal 1024 overflows int8
Comment From: JAicewizard
the ambiguity already existed before this:
var x = 5 //what type is x?
fmt.Println(reflect.TypeOf(x)) // its an int!!!
I think adding the requirement of such explicit type indicators should be a separate proposal covering more than just this. that way it stays consistent, instead of needing it for pointers to ints, and not for other ints.
Comment From: clausecker
@alnr @JAicewizard I don't really see a problem with fixing the type of &foo
where foo
is an untyped integer constant to int
. This is consistent with short variable declarations x := foo
and an explicit type can always be given if desired.
Comment From: deanveloper
If we assign &5
to always be *int
then you can’t do var x int64 = &5
. Also, it may not be good to have &5
mean different things in different situations.
Currently we don’t encounter this with x := 5
. 5
on its own is an untyped integer constant, and in x := 5
, because x
does not have a specified type, it is assigned an untyped integer constants default type, int
.
This becomes much more complicated for &5
, because we do not have a concept for “pointers to untyped integer constants”. So what would the &5
in x := &5
mean? How do we resolve it in a way that works for both x := &5
and var x *int64 = &5
?
The solution provided by @clausecker only works for x := &5
but would fail to compile for var x *int64 = &5
.
Comment From: clausecker
@deanveloper For a 64 bit int, you'd have to add a cast: x := &int64(5)
. This doesn't strike me as unreasonable.
Comment From: deanveloper
@clausecker I guess that works but it may be confusing, especially for a beginner. f(5)
works, why can’t fPtr(&5)
? (where the functions take an int64 and *int64 respectively)
Comment From: clausecker
@deanveloper While I acknowledge that this is less elegant than it could be, I don't think this is a problem that crops up too often and especially not for beginners. So no point in optimising for the rare case.
Btw, my proposal for this thing is to just allow building composite literals of scalars and have them be lvalues distinct from the initialiser. So you would have to write something like fPtr(&int64{5})
regardless of what type you want to use. One major advantage is that this syntax would not come with an implicit type cast and is thus safer against accidental type confusion.
Comment From: deanveloper
I’m personally not a fan of it because int64
isn’t a composite type, so composite literal syntax doesn’t exactly make sense for it imo. However I see the attraction to it and wouldn’t mind it per se. However then you get into semantics of what int64{…}
means because int64
is not a composite type. For instance, would I be able to do int64{dur}
where dur
is time.Duration
? It would add an entirely new concept that seems different from type conversion, but isn’t quite the same, which is what I would like to avoid in Go. I personally think that simply allowing pointers to typed constants (a la x := &int64(5)
) is the best solution.
Comment From: quenbyako
@justjoeyuk Can we not just literally bake some utilities
No, we can't. If you so want to solve all the problems in the world by creating separate libraries for each one, I suggest you use Lua, there even the standard library consists of 5 (if memory serves me) packages. And this despite the fact that on the basis of Lua you can do everything the same as on go (yes, absolutely everything).
Get the problem right: the point is not that "this makes it possible to write less", but literally every second package is faced with the problem of creating pointers to simple types. Therefore, no, telling that "this is not a problem, just use pointers
package" is not a solution to the problem.
@alnr
// *int or *int64?
Huh? Of course it will be *int, cause every number without explicit type assertion became int. Am i worng?
@deanveloper i understand what you want to say, but i think, that this is not too bad idea to upgrade language parser... idk honestly, looks like you are right if there will be no changes to language specification
Comment From: DmitriyMV
No, we can't.
Yes we can. Generic pointer.Of(t T) *T
solves this rather nicely.
cause every number without explicit type assertion became int
Not really - 10.0
becomes float64. Pointer expressions would also require to adjust language spec with something like pointer to untyped constants
which is another can of worms.
Comment From: quenbyako
@DmitriyMV you didn't get the point of my comment:
Generic pointer.Of(t T) *T solves this rather nicely.
Nuh-uh, the point of "why we can't" is not about it's now technically impossible (of course it is possible, there is a pointers
package, go on, use it). The point is to avoid one of the most frequent crutches in literally any go projects and just improve language specification. Yes, the use of pointers
package is a crutch, for some reason the structures can be initialized immediately, and simple types suddenly can not.
10.0
becomes float64.
True, but, again, you didn't get the point of my comment:
i := 123 // reflect.TypeOf(i) == "int"
var j = 123 // reflect.TypeOf(j) == "int"
var k int64 = 123 // reflect.TypeOf(k) == "int64"
ip := &123 // with this proposal: reflect.TypeOf(i) == "*int"
var jp = &123 // with this proposal: reflect.TypeOf(i) == "int"
var kp &int64 = &123 // with this proposal, guess what?: reflect.TypeOf(k) == "int64"
f := 10.0 // float64
var g float32 = 10 // float32
fip := &10 // *int
ffp := &10.0 // *float64
var gp *float32 = &10 // *float32
Comment From: deanveloper
Yes we can. Generic pointer.Of(t T) *T solves this rather nicely.
it’s not really a great solution, as (it i remember correctly) return types cannot be inferred. this means it never really solves the f(&x)
(where x is const and &x becomes *int64) problem. We’d need to specify the type (ie f(pointer.Of(int64(x)))
or f(pointer.Of[int64](x))
) which defeats the point of using generics to reduce verbosity.
either way, since the language allows pointers to composite literals, it seems to make sense to allow pointers to typed constants (ie &int64(x)
).
my only issue with allowing pointers to typed constants is something like:
const x int = 5; (…) &x
this makes it look like a pointer to x
, however it’s actually a new pointer. a bit confusing, but i think its a good idea.
Comment From: DmitriyMV
@quenbyako
Yes, the use of pointers package is a crutch
With something like this:
package main
import (
"fmt"
. "pointers"
)
func main() {
fmt.Println(PtrOf(10))
fmt.Println(PtrOf(10.0))
}
it's not. Or you could use an alias.
for some reason the structures can be initialized immediately, and simple types suddenly can not.
- Structures are composite types.
- Address of struct doesn't do full type inference. That is - you cannot write
var l = {Field1: 0}
. Either you have a fully specified type on the left side or on the right.
you didn't get the point of my comment
You didn't get mine either. Try to describe "type of pointer to untyped constant" in formal terms (this is spec change we are talking about).
@deanveloper
as (it i remember correctly) return types cannot be inferred
They can: https://go2goplay.golang.org/p/SDLKf5rzosf
it seems to make sense to allow pointers to typed constants (ie &int64(x)).
That's sounds reasonable. Just like new(int, 3)
, though it looks a lot like new([]int, 3)
. I'm however strongly against something like i := &3
.
Comment From: deanveloper
They can: https://go2goplay.golang.org/p/SDLKf5rzosf
That’s an incorrect use of what I meant. In that case, Pointer
is returning to an interface{}. A more accurate example is https://go2goplay.golang.org/p/bIGdW1JDioN where Pointer
returns to an int64
.
Comment From: DmitriyMV
A more accurate example is https://go2goplay.golang.org/p/bIGdW1JDioN where Pointer returns to an int64.
I think it falls down to return type inferring from usage
and that isn't planned ATM. Although maybe it's a good thing since in that case p := Pointer(3)
would essentially be p := &3
and not p := &int(3)
?
Comment From: deanveloper
I think it falls down to return type inferring from usage and that isn't planned ATM
yes, that is what i was trying to say
Although maybe it's a good thing since in that case p := Pointer(3) would essentially be p := &3 and not p := &int(3)?
my point essentially was that using a generic function is a bit clunky, and is a replacement for a feature that seems like it should be in the language in the first place (given that you can make inline pointers to composite literals, it seems strange you can’t do it for other expressions)
Comment From: quenbyako
@DmitriyMV
. "pointers"
Please, never ever do this. I'm begging you (and any gopher), do not.
Btw, something like new(int, 3)
doesn't look so scary as previous alternatives, so may it be a good design proposal
Comment From: benhoyt
Looks like this was last discussed in the proposal review meeting on May 5. While there's no clear consensus here, there are a number of good options. It seems like there's a fair bit of enthusiasm for Russ's simple new(1)
form, and a decent amount of support for a new builtin generic function like Roger Peppe's ptr(1)
suggestion. My vote would be for ptr(1)
as it just uses "ordinary" generics, but I like new(1)
too. Could this be discussed at the review meetings again?
Comment From: seebs
So, I was originally in favor of things like &expr
but on reflection I slightly dislike the general case of that being allocatey, especially because it creates an ambiguity: given m[k], is p := &m[k]
now equivalent to v := m[k]; p := &v
? Because that seems like it loses a thing that currently prevents a significant error.
On the other hand, I don't really object to allowing composite literals for simple types. So, int(3)
is an expression of type int
with the value 3, and not addressable, but &int{3}
would be declaring an object of type int, with the initializer 3, and taking its address. This seems consistent with the &foo.
And I think... if you accept the premise that automatically creating objects for expressions in general is Probably A Bad Idea, then I think there's a good case for a distinction between expressions and brace-form literals, where the latter are expressing the idea of "an object with these properties" rather than just "this value".
And I think this is an argument for not allowing, generically, taking the address of function results or map lookups or other things, because I think it's beneficial that &m[k]
does not work. Since it can't give you the actual current address of m[k], it's pretty important that it not, instead, give you the address of an object which happens to look like m[k] did when you requested it.
But if you had to write &vtype{m[k]}
, you'd be clearly indicating that you were not getting the address of m[k], you were getting the address of an anonymous literal.
I don't object to also the new(1)
thing, I just think that the braced-literal syntax is nicer, and being able to do it for a handful of additional types would be a nice change.
Comment From: earthboundkid
Anyone will be able to write their own func NewOf[T any](v T) *T
when Go 1.18 comes out. I say, let people play with that, and if it’s widely enjoyed and accepted, add a built in newof
in 1.19.
Comment From: moonchant12
If this proposal is to resolve inconsistency, shouldn't it allow taking the address of functions as well?
As in,
fp := &func(){}
Comment From: earthboundkid
Here’s new.Of() for whoever wants it: https://github.com/carlmjohnson/new
Comment From: rogpeppe
Here’s new.Of() for whoever wants it: https://github.com/carlmjohnson/new
Personally I'd say that that package is a great illustration of the proverb "a little copying is better than a little dependency".
FWIW I've been just writing out this code wherever I need it:
func ref[T any](x T) *T {
return &x
}
Comment From: earthboundkid
Personally I'd say that that package is a great illustration of the proverb "a little copying is better than a little dependency".
Well sure, but then what about my plan to replace it with a version that prints "Send carlmjohnson a fiver, will ya?" once it gets popular?
More seriously, ref
is also a good name for the function.
Comment From: acehow
already 7 years passed since #9097 .so maybe go team will need another 7 years to support this change.
Comment From: quenbyako
@acehow instead of scoffing, you could help with the implementation. this is not an easy task as it might seem, you can add syntax sugar, but it will become really slow.
It's not very wise to scoff maintainers, when you have a super-powerful language literally for free.
And also, there is already good solution for this issue, so you don't need to wait new go version to handle cases like this))))
Comment From: afa7789
package types
func Ptr[T any](v T) *T {
return &v
}
When I want to make a quick reference Pointer, I have to use types.Ptr( VARIABLE )
, I guess this could be a casting tool native to go itself. Is something simple, usefull etc.
Like we havelen( VARIABLE )
,print( stuff)
why not pointer( VARIABLE )
or even ptr( VARIABLE ).
Anyway, I to used &[]string{VARIABLE}[0]
to quick access pointer, and I guess it's something usefull.
It's really a great way to get it fast when you have to use it in another function
example.FunctionReceivePoitnerAndString(types.Ptr(Something),string1)
so you don't have to create the Something pointer out of the scope.
edit: "I am now seeing everyone is proposing the same, shoudl have read it all srry guys"
Comment From: afa7789
Anyone will be able to write their own
func NewOf[T any](v T) *T
when Go 1.18 comes out. I say, let people play with that, and if it’s widely enjoyed and accepted, add a built innewof
in 1.19.
awesome.
Comment From: earthboundkid
I think it's a useful datapoint that this is being independently recreated. :-)
Comment From: EraYaN
K8s has shipped pointer tooling for quite a while now in the k8s.io/utils/pointer
package because you need it pretty darn often. At some point it might be worth it to have a look at it for the language itself.
Comment From: DeedleFake
@EraYaN
Something like that in the standard library definitely feels like overkill post-generics. As mentioned in previous comments, all it takes now is just one definition of
func ptr[T any](v T) *T { return &v }
somewhere and it covers every single one of those to-pointer functions in a single function definition.
Comment From: EraYaN
But isn't something simple like that exactly something that ought to go into a stdlib? I get that golang is sort of weird about it's stdlib but still, seem like a core function to me.
Comment From: itroot
But isn't something simple like that exactly something that ought to go into a stdlib? I get that golang is sort of weird about it's stdlib but still, seem like a core function to me.
There are quite a few libs that have similar funcs; i.e.: https://github.com/samber/lo#toptr
Comment From: ivalue2333
But isn't something simple like that exactly something that ought to go into a stdlib? I get that golang is sort of weird about it's stdlib but still, seem like a core function to me.
add it to stdlib will be helpful, we have so many ptr usage in our project. And I hope this issue keep active and be solved
Comment From: ianlancetaylor
At this point the most viable options seem to be:
- A new generic function somewhere in the standard library
func PtrTo[T any](v T) *T { return &v }
&T(v)
to get the address of a newly allocated variable of typeT
with the valuev
new(v)
which acts likePtrTo(v)
(or perhapsnew(T, v)
, or perhaps permit both forms)
Several other ideas have been mentioned but seem strictly worse than these.
@griesemer. @bradfitz, and @ianlancetaylor prefer permitting both new(v)
and new(T, v)
. Permitting the latter form is similar to the way we permit both var x = v
and var x T = v
; this leaves it to the programmer to write clear code. (Either way, another possibility is var x = T(v)
and new(T(v))
, which works but is a bit more cluttered).
The disadvantage of PtrTo
is simply the name: where in the standard library would that function go? The disadvantage of &T(v)
is the similarity to composite literals and the possible confusion of when &
allocates and when it takes the address of an existing variable.
While there are no immediate plans for any change here, are there strong objections to the choice of new(v)
and new(T, V)
? Thanks.
Comment From: benhoyt
@ianlancetaylor Thanks for getting back to this! Can you clarify which of the three choices you're referring to in "are there strong objections to that choice?" It's a bit ambiguous at present.
I slightly prefer new(v)
over &T(v)
because it eliminates stuttering in cases like new(time.Now())
-- that would be &time.Time(time.Now())
with the other syntax. If new(T, v)
is supported in addition for clarity in certain cases, that's fine. new()
is also a bit clearer that it always creates a "new" thing.
The other disadvantage of ptr.PtrTo
is that it requires an import (and the package prefix on each use). Not a big deal, but just a slight irritation over new
being always available and very short.
Comment From: griesemer
@benhoyt Ian meant the last of the three most viable options: are there strong objections to new(V)
and new(T, v)
which would act like PtrTo(v)
(or PtrTo[T](v)
, respectively).
Comment From: ianlancetaylor
Edited my comment for clarity. Thanks.
Comment From: willfaught
p := &3
but that has the nasty problem that 3 does not have a type, so that just won't work.
3 can have a type: the default type for integer literals, int. 3.0 is float64, "3" is string, etc.
Instead, we define that conversions (and perhaps type assertions, but let's not worry about them here) are addressible.
Why not make any expression addressable if it's used like it's addressable?
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)
I don't see why the type argument is needed here. If it's a literal, use the default type. If not, use the actual type. new(3)
could mean new(int(3))
. new(f())
could use the result type of f.
Making type conversions addressable as a special case is inconsistent and magical. There's no rhyme or reason for it. It would be one of those magic incantations that new Go users have to memorize, with the help of the arcane knowledge of this GitHub issue explaining why it exists the way it does. If we wrap addressability into this, I think we should instead focus on making addressability more generalized/expressive so we can accomplish what this issue is about. Otherwise, we shouldn't involve addressability at all.
Comment From: DeedleFake
@benhoyt
new(time.Now())
reads really strangely to me. I'd much prefer &time.Now()
.
I wrote up a whole post about it but @willfaught beat me to it right above. My thought process is that
return &Struct{}
is conceptually equivalent to
s := Struct{}
return &s
so why not just expand that to every single-valued expression? It's always confused me that struct, array, slice, and map types are special in that regard. Just let me omit the intermediate variable for everything, be it &3
, &"example"
, or &f()
.
Side note: Huh, maps can be pointer initialized. &map[int]int{1: 3, 2: 2, 3: 5}
is completely valid. I had no idea before writing this.
Comment From: benhoyt
@willfaught It looks like you're responding to the original proposal at the top, rather than Ian's recent question. Which is fine, but several of your points have already been addressed. In particular, you said of new(int, 3)
"I don't see why the type argument is needed here" -- but Ian's proposal is to allow new(3)
(in addition to new(int, 3)
).
@DeedleFake I actually created issue 22647 a while back proposing to allow the &anyExpression
syntax, but I've been convinced away from that. As noted above and in Ian's comment on issue 9097, it's not quite "conceptually equivalent". When you do &Struct{}
Go creates a new value every time and returns its address, but when you do &s
Go returns the address of that same variable each time. Also, you could then write &myMap[k]
, which would make it look like map entries are addressable, but they're not (that construct is now an error). As such, I think allowing &anyExpression
is more confusing than it's worth.
Comment From: zephyrtronium
Does the proposed new(v)
infer the correct type when v
is an untyped literal and the expression is used in a context requiring a defined type? That is, does this type check?
type MyString string
func F(*MyString) {}
func main() {
F(new(""))
}
Comment From: willfaught
In particular, you said of new(int, 3) "I don't see why the type argument is needed here" -- but Ian's proposal is to allow new(3) (in addition to new(int, 3)).
@benhoyt I know, my objection was for including new(int, 3) because the type argument is redundant. new(3) is fine. Part of why var t T = x is allowed is because the assignment might involve conversion/assignability; another part is documentation. There's no such need in expressions for allocations.
Actually, his proposal was for new(v), not new(3), so as far as I can tell, my point about default types still stands.
Comment From: ianlancetaylor
In a case like new(3)
or new("")
the untyped constant will be given the default type, just as though you wrote var x1 = 3
or var x2 = ""
.
The question about F(new(""))
is an interesting one. That would not type-check today, as we don't carry inferred types into function calls, only out of them. It's similar to
type MyString string
func F(*MyString) {}
func PtrTo[T any](v T) *T { return &v }
func main() {
F(PtrTo(""))
}
Today that fails to compile with
foo.go:10:7: cannot use PtrTo("") (value of type *string) as *MyString value in argument to F
In principle we could infer that this must be PtrTo[MyString]
in order to satisfy the call to F
, and that therefore the untyped string should be treated as MyString
. But we don't make that kind of type inference today, and we have no plans to do so.
Comment From: rogpeppe
My main reservation about new(v)
is that, as far as I know, all built-in functions take either a type or a value for a given argument - that is, for a given primitive, we always know which of the two we're expecting.
new(v)
would blur that line.
Even though both type expressions and value expressions exist in the same namespace so there's no problem in principle with doing this, it makes me uneasy.
It also makes tooling a little less capable, because when I write new(
, my editor can no longer know that I'm going to write a type and make appropriate suggestions.
My personal vote would be for a new built-in function (ref
, ptr
, newof
... ?) which avoids the need for this argument kind ambiguity.
Comment From: robpike
That issue can be avoided if the type is required to be there, which was the original proposal. I believe this facility will be used relatively rarely, and the requirement to put the type there adds little pain overall while maintaining consistency.
Comment From: leighmcculloch
At this point the most viable options seem to be:
The three options don't seem viable to me, as described:
- A new generic function somewhere in the standard library
func PtrTo[T any](v T) *T { return &v }
Experimenting with this option, a generic function that copies, allocates, and returns the pointer, seems undesirable than any of the other options, because it causes the value to always be allocated on the heap, where-as other options discussed would result in the value appearing on either the stack or heap depending on context. (Assuming the other proposals follow the existing behavior of allocation for new
and &S{...}
.)
&T(v)
to get the address of a newly allocated variable of typeT
with the valuev
Breaking down &T(v)
and reading it right-to-left it reads like:
1. v
given some value
2. T(...)
convert the value into T
3. &
get the address of
Part 2 seems surprising. If v
is already of type T
it looks like the conversion shouldn't be necessary. &
already means get the address of. T(...)
already means convert a value to type T
. It's surprising, and unintuitive, that combining the two means something new: "create a new value of T
in a temporary and get the address of it." While the original proposal highlights that type conversion always creates a new value, this is not intuitive. When I see a type conversion, I don't see an intent to create a new value, I see only an intent to convert, so I don't think &T(v)
communicates intent clearly enough.
are there strong objections to the choice of
new(v)
andnew(T, V)
?
I am not fond of new(v)
because it will cause the new
function's first parameter to be either a value or a type. That seems surprising given overloading functions isn't supported by Go.
I am not fond of only adding new(T, v)
because it is verbose and introduces type stutter when used with structs. We already have verbose solutions to this problem (i.e. var v = 3; var p = &v;
) and this proposal is a bigger win if the result is clear and succinct.
I am also not found of only adding new(T, v)
because much application code I see in Go favor &S{...}
over new
. Creating a new way to do the same thing, when that new way is more verbose, will make it harder for new developers to learn Go. new(T, v)
won't address the confusion new developers experience when &S{...}
works, but &3
does not.
@robpike's original proposal included a shorthand where &v
would be equivalent to new(T, v)
. new(T, v)
seems pretty unattractive without that shorthand:
That is,
p := &expression
where
expr
is not an existing memory location is now just defined to be shorthand for
p := new(typeOfExpression, expression)
Even with the downsides of &expression
that have been stated in comments, it seems like the easiest to describe to a new Go developer. I think it will be easier to explain that &v
creates a temporary and gets that temporaries address in situations where the expression is not addressable, such as constants and function return values. The cases that the &
shorthand is allowed could be restricted to disallow the most confusing case, such as accessing a map with &myMap[k]
.
If new(T, v)
is to be added, it would be ideal to also add the shorthand &v
for the constant and function return value non-addressable cases of v
. It's pretty clear that &3
and &f()
are addresses of temporaries/new values.
Comment From: earthboundkid
Experimenting with this option, a generic function that copies, allocates, and returns the pointer, seems undesirable than any of the other options, because it causes the value to always be allocated on the heap, where-as other options discussed would result in the value appearing on either the stack or heap depending on context.
I would assume that this is just a temporary compiler hiccough as generics are integrated in. I wouldn't make a long term decision based on it.
Comment From: DmitriyMV
@leighmcculloch
The results (as of 1.20.5) for me:
Benchmark1
Benchmark1-10 1000000000 0.9498 ns/op 0 B/op 0 allocs/op
Benchmark2a
Benchmark2a-10 1000000000 0.9457 ns/op 0 B/op 0 allocs/op
Benchmark2b
Benchmark2b-10 1000000000 0.9585 ns/op 0 B/op 0 allocs/op
Benchmark3
Benchmark3-10 1000000000 0.9427 ns/op 0 B/op 0 allocs/op
Benchmark4
Benchmark4-10 1000000000 0.9438 ns/op 0 B/op 0 allocs/op
No allocations.
Comment From: ianlancetaylor
Personally I think the very different behavior between &2
and &v
(where v
is a variable) means that we should not add &2
to the language. (The composite literal syntax is sufficiently different from &v
that confusion doesn't seem likely.)
Comment From: leighmcculloch
the very different behavior between
&2
and&v
(wherev
is a variable) means that we should not add&2
to the language. (The composite literal syntax is sufficiently different from&v
that confusion doesn't seem likely.)
From a Go user perspective I don't think the underlying behavior difference becomes a behavior difference in the program for constants and functions in a way that a developer could observe the behavior in a surprising way.
@ianlancetaylor Do we have an example where &2
would create application behavior that is confusing?
For constants there is no address that a developer could expect to receive. &2
would always give a new address, which is different to &v
, but I haven't seen complaints of people being surprised by this for &S{...}
, so that seems like a non-issue. (eg)
For function return I think the same is true. The example shared up in the thread (https://github.com/golang/go/issues/45624#issuecomment-822426632) for how function returns could be confusing is itself a confusing code sample already requiring an understanding of non-trivial concepts.
I understand there is confusion with maps, but use with map lookups (&myMap[...]
) could be disallowed. (If maps ever got methods, &myMap.get(...)
would be usable and less confusing.)
Comment From: DeedleFake
Personally I think the very different behavior between
&2
and&v
(wherev
is a variable) means that we should not add&2
to the language. (The composite literal syntax is sufficiently different from&v
that confusion doesn't seem likely.)
What if, not including the existing composite literally syntax and possibly a new ability to address a return from a function, all allocating address usages require an extra set of parentheses, i.e. &(3)
or &(someMap["key"])
? ~~That's not legal currently and~~ seems sufficiently different to me to be obvious.
Edit: It is apparently currently legal. Huh. Well, either way, it may still work as a differentiator.
Comment From: zephyrtronium
@DeedleFake That is indeed legal currently: https://go.dev/play/p/m0YPHwq7CpR
Comment From: zephyrtronium
@leighmcculloch
From a Go user perspective I don't think the underlying behavior difference becomes a behavior difference in the program for constants and functions in a way that a developer could observe the behavior in a surprising way.
The difference between obtaining a pointer to an existing object (which necessarily aliases) and allocating a new object (which necessarily never aliases) is definitely a behavior difference and definitely easy to observe. Whether it's "in a surprising way" depends on what any given programmer expects. If a learning Go user is accustomed to &expr()
never aliasing, and then it does when they do &variable
or &slice[x]
, then it's different in a surprising way.
For constants there is no address that a developer could expect to receive.
&2
would always give a new address, which is different to&v
, but I haven't seen complaints of people being surprised by this for&S{...}
, so that seems like a non-issue. (eg)
FWIW, I am actually surprised that this program prints false. If you modify it slightly, it doesn't: https://go.dev/play/p/UcHpIBuuoge. The Go spec says, "Pointers to distinct zero-size variables may or may not be equal." I have explained this to people who were surprised by it before.
I understand there is confusion with maps, but use with map lookups (
&myMap[...]
) could be disallowed. (If maps ever got methods,&myMap.get(...)
would be usable and less confusing.)
I don't think that replacing one special case (the ability to take the address of composite literals) with another (only map index expressions cannot be the operand of unary &
) makes the language less confusing. Maybe it doesn't make it more confusing, but I don't think "not worse" is a sufficient bar for language changes.
It also isn't the only kind of expression that could be confusing. Type assertions have already been mentioned: &anyVar.(int)
wouldn't take the address of an int
in an any
, it would allocate a copy of the dynamic value of the int
. I have also explained to people, generally looking to call pointer methods on non-pointer dynamic values, that you can't take the address of the value in an interface; having it "work" but operate on a copy might have been surprising to them.
Comment From: ianlancetaylor
Do we have an example where &2 would create application behavior that is confusing?
How about &syscall.ImplementsGetwd
as compared to &syscall.ForkLock
?
Comment From: clausecker
Let's extend the semantics of the unary +
operator to apply to operands of any type, having the semantic of “turn lvalue into rvalue.” Then &+foo
unambiguously points to a copy of foo
.
Comment From: ianlancetaylor
Several people (@rogpeppe, @robpike and others) have commented that rather than both new(T, v)
and new(v)
, we should only have new(T, v)
. That means that the first argument to new
is always a type.
It's true that this makes the expression more verbose in some cases. However, it seems reasonable to guess that most cases where a long type is used are structs, and for which we have the &S{} literal notation. It seems less likely that people will want to write new(T, v)
with a simple v
and a complicated T
. At least, it would be interesting to see specific examples where that comes up.
new(T, v)
also matches make(T, length)
syntactically, although the meaning is different.
As discussed above, there are several reasons why using an &
syntax seems potentially confusing. The new(T, v)
syntax should be fairly clear and never confusing.
Comment From: rogpeppe
@ianlancetaylor That's a slight mischaracterisation of my comment. To repeat, my preference is still to choose a new spelling for a function takes a value only. I'm not keen on shoehorning the functionality into new
.
Comment From: Merovius
I don't particularly like new(T, v)
either. I think the similarity to make
detracts from its appeal, instead of adding to it: There already is some confusion of why make
is used for some types but new
(or &T{}
) used for others. I also don't think it really applies - in make(T, v)
, the v
means something completely different from what it'd mean in new(T, v)
. Lastly, in my opinion the extra overhead of typing out the T
is significant in many cases, where the value you want it initialized to can be inferred. new(int64(42))
isn't any more to type or read than new(int64, 42)
, but new(time.Second)
is significantly better than new(time.Duration, time.Second)
. I don't think having the type in there really adds anything. We are already kind of used to inferring the type from a constant literal.
That being said, new(T, v)
is still better than where we're at, so if we can't agree on new(v)
and if we can't agree on a better name for pointerTo(v)
, then I'd live with new(T, v)
.
Comment From: icholy
but
new(time.Second)
is significantly better thannew(time.Duration, time.Second)
.
It's better if I already know that time.Second
is value. But if you're reading new(pkg.Ident)
, there's no way to tell which overload you're using without checking the definition of pkg.Ident
.
Comment From: rogpeppe
Another point: the new(T, v)
form is also inconvenient in the not-uncommon case where we want to make a copy of a pointer type.
e.g.
func f(x Foo) {
x.field = new(int, *x.field) // where int is the type of Foo.field
// use x.field without worrying about shared pointers.
}
We have to mention the type where there's otherwise no need for it and it might not be obvious, making the code a little more brittle.
With a "pointerTo"-style function, it's nicer IMHO:
func f(x Foo) {
x.field = ref(x.field)
// use x.field without worrying about shared pointers.
}
// ... for some spelling of "ref"
func ref[T any](x T) *T { return &x }
Comment From: earthboundkid
I don’t like the new(v) form because it depends on the reader knowing if v is a value or a type. Between new(T, v) and ref(v), I could live with either, and it really just depends on if you think it’s better to not add another predeclared identifier or better to not add an overloaded form for an existing identifier.
Comment From: ianlancetaylor
@rogpeppe Apologies for the mischaracterization.
Comment From: Merovius
FWIW I agree that new(X)
is ambiguous because it requires to know if X
is a value or a type. I just used that to make the point that I don't like new(T, v)
. I think my favorite version would be ptr(v)
, but I assume ptr
is not an acceptable name. Someone might have a better one.
But as I said, if we can't agree on any color of bikeshed for a type-argument-less version of this builtin, new(T, v)
is still better than nothing.
Comment From: DeedleFake
If new(T, v)
is the form that gets adopted, I will probably just find myself writing
func ptr[T any](v T) *T { return &v }
in random places anyways to avoid the hassle, though admittedly less often as new(T, v)
will be useful sometimes. Overall I think overloading new()
is the wrong way to go, especially if it's going to force it into a significantly less helpful syntax.
Comment From: willfaught
We could lean into the syntax we already have:
&&value
&&time.Second
var p *int = &&123
Comment From: jimmyfrasche
One of the subproposals discussed in #34515 is to omit the type in make
/new
whenever you could omit the type in a call to a generic function. new(Type, value)
would likely take that option off the table as that brings it back to new(value)
.
Comment From: metux
Bonus challenge: do the same for
make
:) I think it would boil down to extending the struct literal initialization syntax in some way that could cover just these 4 things:
apropos male():
Still havent understood why map fields always need to be explicitly initialized by make(), instead of directly working off the zero value. This makes using maps in structs much more complicated.
Comment From: DeedleFake
Every type's zero value is literally the memory all set to zero. A map is a pointer to a struct internally, so its zero value is literally just a nil pointer. Trying to make the zero value behave differently would fail in the following situation, among others:
func addThing(m map[string]string) {
// This would allocate an hmap, but only this m would get set to its address.
m["example"] = "This is an example."
}
func main() {
// Remember, this is a *hmap.
var m map[string]string
// addThing() gets a copy of the address, currently nil.
addThing(m)
// No matter what addThing() does, the local m is still nil at this point.
}
Comment From: metux
Every type's zero value is literally the memory all set to zero. A map is a pointer to a struct internally, so its zero value is literally just a nil pointer. Trying to make the zero value behave differently would fail in the following situation, among others:
```go func addThing(m map[string]string) { // This would allocate an hmap, but only this m would get set to its address. m["example"] = "This is an example." }
Ah, so a map can be directly passed as value (instead of reference/pointer to it) while still using the same underlying hashtable. Good news to me, wasn't sure that's really the case :)
Indeed, now an on-demand allocation would cause this kind of trouble. If we'd ever go that way, this would need to be clearly documented and probably code checkers should look for bugs where the programmer might have forgotten it ... certainly not nice.
But what would happen (besides extra compiler complexity) if we'd let it implicitly emit an makemap() call, if it doesn't find another one (that probably asks for different initial size) ? IIRC, the worst that could happen (when there is an explicit make that goes unnoticed), we might have an wasted allocation or memory clear, but shouldn't hurt semantics at all.
Am I missing something ?
By the way, still haven't fully understood how code generation and runtime code really work together ... if a program doesn't use maps at all, does the deadcode elimination kick out all the map-related runtime code ?
thx.
Comment From: adonovan
Summarizing some comments:
- &v is unclear as to whether it allocates, or returns an existing variable's address.
- new(v) is concise but easily confused for new(T).
- new(T, v) is clear and similar to make, but the type is redundant.
- the function can be written as a one-liner: func addr[T any](v T) *T { return &v }
Given that it is trivial to write the helper function, a language change would add marginal value. @robpike, do you still think there's a need to do anything here?
Comment From: robpike
@adonovan There is certainly no need, but I still find the imbalance troubling: it's easier to build a pointer to a complex thing than to a simple one.
None of the bullet points in your list seem fatal to me. The first one is irrelevant to what I suggested, the middle two are true but not clearly problems, while the ease of writing that function doesn't touch the fundamental asymmetry.
Comment From: findleyr
Leaving open until someone brings this discussion to a consensus. — rfindley for the language proposal review group
Comment From: perj
the function can be written as a one-liner: func addrT any *T { return &v }
Judging from the past 1.5 years, I appear to be writing this function about once every second month, when I need it in a new package. The need especially arise with pointers to strings in unit test files, I've noticed.
Admittedly, I work a lot with code generated from API specifications. That code tend to use *string
a lot, since it can't be certain that nil and empty string are equivalent (and rightly so, they aren't).
It's not very annoying, but does feel a bit like I'm littering my packages with this function, so not having to write it would be welcome. I do realise I can put it in a package I import, but that also seems overkill for a one-liner.
Comment From: smasher164
As far as being explicit about allocation is concerned, I think Go is well past that point. Whether or not an object is on the stack or the heap is entirely predicated by escape analysis. If anything, &v
is more like ML's ref v
which says "Hey here's a mutable reference to this value-whether it's on the stack or the heap is implementation-defined."
Comment From: andig
Given that it is trivial to write the helper function, a language change would add marginal value.
The helper function can easily be called with a pointer value which would typically be a programming error. Having a language construct could prevent this, either by rule or by removing a layer of indirection.
Comment From: earthboundkid
- new(T, v) is clear and similar to make, but the type is redundant.
If the type inference worked well enough (perhaps by just hard coding this) that you could write new(T, { field1, field2 })
instead of new(T, T{ field1, field2 })
, it wouldn't be so bad. The comma is weird, but it exists so you don't confuse T with v, which is good.
Comment From: bbkane
Has there been any more updates to this?
Similar to @perj , I've written this function for multiple modules, and I've seen it in 3rd party modules many times as well, often with different names.
In addition to the "symmetry" argument from @robpike , I want a standard way to create a pointer to a primitive type simply to get some standardization.
This issue has been open since 2021, and most commenters welcome the idea, though I haven't seen super strong arguments on either side for a winning syntax. Could someone on the language team pick a winner so I can start using it when it's implemented?
Comment From: adonovan
@robpike's original option 1, new(T, v)
has a lot to recommend it. The type is explicit, and the allocation of a new variable is explicit, so it's almost impossible to misunderstand what it means. And it's a small language change, being merely the addition of a parameter to an existing function.
I'll bring it up again at the next review.
Comment From: robpike
Very happy to hear that. I care deeply about this wart (the asymmetry) in the language, although it's not a major flaw.
Comment From: mitar
My main use case is in JSON with optional fields where I have a struct with *string
or *int
types:
type Foo struct {
String *string `json:"string,omitempty"`
Int *int `json:"int,omitempty"`
}
Creating such a struct is currently really painful. But proposed syntax from option 1 is also very verbose:
Foo{
String: new(string, "x"),
Int: new(int, 0),
}
Could we maybe allow:
Foo{
String: new("x"),
Int: new(0),
}
Comment From: ianlancetaylor
@mitar There has been a lot of discussion on this issue. I believe that idea was first proposed at https://github.com/golang/go/issues/45624#issuecomment-822211103.
Comment From: ianlancetaylor
I believe my most recent summary of this issue was https://github.com/golang/go/issues/45624#issuecomment-1581546783 followed by https://github.com/golang/go/issues/45624#issuecomment-1642776089.
Comment From: ianlancetaylor
Moving to regular proposal committee.
Comment From: rogpeppe
@robpike's original option 1,
new(T, v)
has a lot to recommend it. The type is explicit, and the allocation of a new variable is explicit, so it's almost impossible to misunderstand what it means. And it's a small language change, being merely the addition of a parameter to an existing function.
FWIW I still write this tiny function wherever I need it:
func ref[T any](x T) *T {
return &x
}
Replacing, for example, ref(someMap[x])
with new(SomeType, someMap[x])
would be a net loss because it makes the code more verbose and a little bit more fragile, requiring update should the type of the map's values change.
Another idiom I commonly use involving the above function is to make a shallow copy of a pointer value:
x := ref(*y)
Again, the requirement to explicitly mention the type of y
there is unnecessarily annoying, particularly for types with large names such as inline-defined structs.
I still think that some equivalent of the ref
function above would be a nice addition to the language.
Comment From: t9t
I would like to add a point related to naming that I think has not been voiced in this thread (which I just fully read; I hope I didn't miss it).
There have been some proposals to add a new function, e.g. a built-in called ref
or ptr
, or in the standard library like pointer.Of
.
Imagine the following hypothetical snippet:
a := 10
p := ptr(a)
// or:
r := ref(a)
My first thought when seeing this, is that ptr(a)
or ref(a)
is equivalent to &a
, i.e. taking the address of (getting a pointer or reference to) the variable a
. But it's not: &a
will give the same address each time, while ptr(a)
/ref(a)
would make a copy, and return an address to the copy.
While I'm not a big fan of the new(T, v)
syntax because it can become verbose in certain cases, I do like the name new
here because it says exactly what it's doing: creating something new rather than pointing to (or referencing) something existing.
Comment From: earthboundkid
While I'm not a big fan of the
new(T, v)
syntax because it can become verbose in certain cases, I do like the namenew
here because it says exactly what it's doing: creating something new rather than pointing to (or referencing) something existing.
I like the name newof
for similar reasons.
Comment From: adonovan
While I'm not a big fan of the
new(T, v)
syntax because it can become verbose in certain cases, I do like the namenew
here because it says exactly what it's doing: creating something new rather than pointing to (or referencing) something existing.I like the name
newof
for similar reasons.
I wonder: would it be possible to change the interpretation of new
so that it accepts its current form new(T)
but also new forms such as new[T]()
, new[T](expr)
, and new(expr)
? Given that the call and conversion forms f(x)
and T(x)
are not distinguishable by the parser, which must parse f as an expression and leave it to the type checker to discriminate types from terms, it should be possible for the type checker to interpret the first argument of new in a similar way.
Then we could use the style new(T)
or new[T]()
for calls that construct a zero-valued variable, and new(x)
or new[T](x)
when we need to initialize the variable, the latter case for when x is not a T.
Comment From: apparentlymart
The idea of overloading new
so that its single argument can be either a type or a value was already discussed multiple times earlier in the thread, and already has some concerns raised that I'm linking here just in the hope that we don't need to repeat these same ideas again:
- https://github.com/golang/go/issues/45624#issuecomment-1582248350
- https://github.com/golang/go/issues/45624#issuecomment-1644290116
- https://github.com/golang/go/issues/45624#issuecomment-1642776089
I'm sharing these links only to help find earlier discussion. I don't have a strong opinion on this subject.
Comment From: aclements
One concern with accepting new[T]()
is that there would now be two different and completely equivalent ways of using new
. And it's already the case that you can often use &T{}
to do the equivalent of new
, so that's three equivalent ways of allocating a new thing.
Comment From: neild
I think that if we add new[T]()
, then we need to ask whether there should be make[T]()
as well for consistency. It seems confusing for one to have a type parameter but for the other not to.
If we did add of new[T]()
and/or make[T]()
, we'd need to ask what the preferred form is. Should you write x := new(int)
or x := new[int]()
? Is the new form only for cases when you supply a value, so you write x := new(int)
and x := new[int](42)
?
If we were designing the language from the start, then I think having new
and make
use the same type parameter syntax as user-defined functions would clearly be better than what we have today, just for general consistency. If we add new syntax now, however, we have even less consistency. I think there's value in adding syntax to create a pointer to a non-zero value of a simple type (new(int, 42)
, new(int(42))
, and &int(42)
all look fine to me), but I don't think we should add a replacement for new(int)
; it's too much language churn for too little value.
Comment From: mitar
If we add new syntax now, however, we have even less consistency.
But we could have a linter warning that old style is deprecated and slowly the codebase moves to the new style. While old style is kept around forever for backwards compatibility?
Comment From: mibk
If we did add of
new[T]()
and/ormake[T]()
, we'd need to ask what the preferred form is. Should you writex := new(int)
orx := new[int]()
? Is the new form only for cases when you supply a value, so you writex := new(int)
andx := new[int](42)
?
I was reminded of a small syntactic wart I’ve felt ever since generics landed.
When a generic factory takes no value arguments, we have to write
m := omap.Make[int, string]()
which feels noticeably heavier than the built-in form
m := make(map[int]string)
All the noise comes from the empty ()
after an explicit type-argument list.
Could we special-case this and let the first parenthesised list serve as type arguments when no []
is present?
m := omap.Make(int, string) // sugar for omap.Make[int, string]()
That would make user-defined generic factories look and feel much more like the built-ins, reducing the “specialness” of make
and new
. Type inference still works as today; you’d only spell out the types when you need them.
I had never dared to actually propose this, but I felt it was related to the discussion.
Comment From: aclements
This proposal has been added to the active column of the proposals project and will now be reviewed at the weekly proposal review meetings. — aclements for the proposal review group
Comment From: perj
It seems the discussion has settled on either new(T, v)
or a new builtin such as newof[T](v)
.
Of those two, I slightly prefer the latter. It fits better into the existing generics syntax and it's clear enough what's going on. It also optionally allows specifying the type if the default is not correct, but not making it mandatory.
I've previously named that functions as newval[T](v)
, just to throw another name in there (naming is hard, as we all know).
Comment From: rednafi
Not a fan of the new(T, v)
syntax. This also requires fiddling with the built in. Then for backward compat we need to keep new(v)
around.
This is confusing for newcomers. The proposed newof[T](v)
is better because people are already writing some form of this and are familiar with it.
Also, the name newof
indicate that we’re not pointing to any existing value, rather we take the address of a new value. So it's better than ptr
IMO.
Comment From: mcandre
2025 and still no progress. Unsubscribing.
Comment From: aclements
@adonovan would you mind writing a summary of the discussion, saying which syntaxes were considered and the reasoning for rejecting them? I'd like to avoid re-litigating.
Comment From: DeedleFake
@aclements
As an experiment, I used the GitHub CLI to convert this issue to JSON and then fed it into an LLM to produce a summary and list of syntaxes with arguments for and against them. It seems to have done a pretty good job, so I'm pasting its output here.
Overview of GitHub Issue #45624: Proposal for Pointers to Simple Types in Go
This issue, opened by Rob Pike in April 2021, proposes adding syntax to Go for creating pointers to simple types (e.g., int, string) more easily, addressing the asymmetry where pointers to composite literals (e.g., &S{a:3}
) are straightforward, but simple types require verbose workarounds like var a = 3; p = &a
. The core problem is that untyped constants (e.g., &3
) lack a type, and direct addressing of non-addressable expressions can lead to inconsistencies or bugs.
The discussion spans ~3.5 years (up to July 2025), with 100+ comments from contributors, core team members (e.g., Rob Pike, Russ Cox, Ian Lance Taylor), and users. Key themes include orthogonality, readability, backward compatibility, and avoiding confusion with existing &
(which takes addresses without allocation) or new
(which allocates zeroed values). Many users report writing helper functions like func ptr[T any](v T) *T { return &v }
as a workaround, especially for APIs like AWS SDK or Thrift that use *int
or *string
for optionals.
Generics (introduced in Go 1.18) mitigate some needs via helpers, but the proposal persists for language-level symmetry and ergonomics. No consensus yet; the issue is active in the proposal review process as of July 2025.
Proposed Syntaxes and Arguments
Below is a table summarizing all major proposed syntaxes, grouped by category. Each includes a brief description, examples, key proponents, arguments for (pros), and against (cons). Proposals are drawn from the original post and comments; some overlap or evolve (e.g., variations on new
).
Syntax Category | Specific Proposal | Description | Examples | Key Proponents | Pros | Cons |
---|---|---|---|---|---|---|
Extend new Builtin |
new(T, expr) (Option 1 from original post) |
Add optional second arg to new : allocates a T , initializes it to expr (with implicit conversion if needed). Redefines &composite as sugar for this. |
p := new(int, 3) p := new(T, f()) |
Rob Pike (original), Alan Donovan (recent revival), Ian Lance Taylor | - Explicit allocation and type. - Small change to existing builtin. - Orthogonal: fixes asymmetry without overloading & .- Backward compatible. - Matches make(T, n) syntax.- Clear for teaching (no hidden allocation). |
- Verbose/stuttery (repeats type if obvious, e.g., new(time.Duration, time.Second) ).- Requires type even when inferable. - Doesn't help copying pointers (e.g., new(int, *x.field) ).- Overloads new (already has one form).- Not as concise as generics helper. |
Extend new Builtin |
new(expr) |
Overload new to accept an expression instead of a type; infers type from expr . |
p := new(3) (infers *int )p := new(f()) |
Russ Cox, some commenters | - Concise, no type repetition. - Retroactively explains &T{...} as sugar.- Handles function returns easily. |
- Ambiguous: Reader must know if arg is type or value (e.g., new(pkg.Ident) could be type or constant).- Overloads new heavily.- No way to specify type if inference wrong (e.g., for untyped constants). |
Extend new Builtin |
new[T](expr) or new[T]() |
Generic form: Use type params for zero-init or value-init. | p := new[int](3) p := new[int]() (zeroed) |
Alan Donovan (recent), some generics fans | - Modern, consistent with generics. - Flexible: Optional type param for inference. - Could unify with make[T]() . |
- Adds equivalent forms (e.g., new(int) vs. new[int]() ), increasing churn.- Inconsistent with non-generic builtins. - Unnecessary for zero-init (duplicates existing new ).- More syntax noise for simple cases. |
Address Conversions (Option 2) | &T(expr) |
Make type conversions addressable: Allocates new storage for the converted value. | p := &int(3) p := &string("foo") |
Rob Pike (original), Ben Hoyt, Peter Bourgon | - Uses existing conversion syntax. - Concise for simple types. - Always creates new storage (consistent with conversions changing types). - Works for function returns if generalized. |
- Confusing: & now allocates copies sometimes (vs. direct address for vars).- Implicit casting can hide type mismatches (e.g., wrong enum). - Doesn't work for untyped constants without conversion. - Potential bugs if extended to arbitrary expr (e.g., &m[k] copies map entry). |
Generalized Addressing | &expr (for non-addressables) |
Allow & on non-addressable expr (e.g., constants, function returns); allocates implicitly. |
p := &3 (infers *int )p := &f() |
Some early commenters, Faiface | - Terse and intuitive. - Generalizes composite literals. - Handles functions naturally. |
- Major con: & behavior changes (allocates copy vs. existing address).- Untyped constants ambiguous (e.g., &3 type?).- Bug-prone: &m[k] copies instead of addressing map; surprises with interfaces or type assertions.- Rejected in prior issues (#9097). |
Generalized Addressing | &f(...) (function results only) |
Limit to function calls/conversions; fresh address for returns. | p := &time.Now() p := &int(3) |
Faiface, Ben Hoyt, Filippo Valsorda | - Covers common cases (e.g., returns). - Consistent: Functions create new objects. - Less special than composites. |
- Still overloads & (copy vs. direct address).- Doesn't cover all expr (e.g., map lookups). - Semantic issues with returns (e.g., copying vs. aliasing in complex funcs). |
Composite Literals for Scalars | &T{expr} |
Allow composite literal syntax for simple types (treat as implicit [1]T). | p := &int{3} |
Clausecker, some C-inspired commenters | - Extends existing composite syntax uniformly. - Familiar from C (compound literals). - No new builtins. |
- Redundant: int{3} == int(3) ; why two ways?- Ambiguous for types like []interface{} (slice vs. scalar?).- Begs question: What does bare T{expr} mean?- Not orthogonal; adds special case. |
New Builtin Function | ptr(expr) or ref(expr) (generic) |
Add predeclared generic like func ptr[T any](v T) *T { return &v } . |
p := ptr(3) p := ref(x.field) |
Rogpeppe, Merovius, many users (e.g., perj, nemith) | - No language change needed (post-generics). - Concise, inferable type. - Handles copies nicely (e.g., ref(*y) ).- Standardizes common helper funcs (seen in AWS, Thrift). |
- Adds predeclared identifier (bloat?). - Naming debate: ptr /ref misleading (implies reference to existing, not copy).- Can be misused with pointers (e.g., ptr(&v) ).- Not as "core" as builtin extension. |
New Builtin Function | newof(expr) or similar |
Variant of above, emphasizing "new of" for clarity. | p := newof(3) |
Earthboundkid, some recent | - Clear: Signals allocation/copy. - Avoids ref /ptr confusion. |
- New keyword; potential naming bikeshedding. - Same misuse risks as above. |
Other/Misc | &&expr |
Double & for "allocate and address". |
p := &&3 |
Willfaught | - Builds on existing & visually. |
- Ugly/verbose; confuses with logical AND. - Not seriously considered. |
Other/Misc | &+expr |
Unary + as "rvalue-izer", then address. |
p := &+foo |
Clausecker | - Forces copy explicitly. | - Obscure; adds unary + overload for all types.- Too clever/complex. |
Other/Misc | &(expr) (parenthesized) |
Require parens for allocating address. | p := &(3) |
DeedleFake | - Differentiates from direct &v . |
- Already legal (just groups); no semantic change. - Doesn't solve ambiguity. |
Overall Discussion Trends
- Pros Consensus: Improves ergonomics for optionals (e.g.,
*int
in structs/JSON/APIs); reduces boilerplate helpers; enhances language symmetry. - Cons Consensus: Risk of confusion with allocation vs. addressing; backward compatibility paramount (Go 1 guarantee); generics reduce urgency but don't eliminate asymmetry.
- Rejected Ideas: Broad
&expr
(too bug-prone); scalar composites (redundant/ambiguous). - Leading Contenders (as of July 2025):
new(T, expr)
(explicit, small change) vs. generic builtin likeref(expr)
(concise, inferable). - Status: Active; no implementation/prototype. Some suggest waiting/deprecating old forms via linters, but core team emphasizes minimal churn.
Comment From: adonovan
Thanks, that's not a bad summary. I would add:
The main points of later discussion are around the desire to be able to specify just a value, just a type, or both, as needed by the situation.
- new(T, expr)
always requires a type, which is often redundant.
- new(expr)
provides no way to specify the type as well.
- &T(expr)
and &T{expr}
also always require an explicit type, but &(expr)
, &{expr}
, and &expr
all have problems of ambiguity, readability, or referential non-transparency (though that's also true of &T{x}
).
- new[T](expr)
or new[T]()
supports all three cases (type, value, both), but creates a new way to say new(T)
. Also the new(T)
and new(expr)
forms may be hard for the reader to distinguish. Modernizing new(T)
to new[T}()
is churn.
The language proposal committee initially leaned toward new(T, expr)
, but since this is just a brevity hack--you can always write the 1-line wrapper function--I'm sympathetic to the desire to avoid redundantly stating the type. So I now favor the last option.
Comment From: earthboundkid
Reading the LLM summary inclines me towards new(expr)
. You could write new(myint(3))
if you needed to specify the type. The argument against it is that it's ambiguous, but we'd get used to it.
Comment From: neild
new[T](expr)
is two changes wrapped up in one:
- An expression to create a pointer to a simple type.
- A type-parameterized version of the built-in
new
function.
Of these, the second change is by far the more significant one.
One of the aspects of Go which struck me when I first started using it, somewhere around Go 1.1, was the for-me-but-not-for-thee attitude towards genericity. The language included a variety of built-in functions and types (new
, append
, maps...) that were parameterized by some type or types and couldn't be implemented in user code. Eventually the language acquired user-defined type-parameterized functions and types, and then iterators, and now we can write equivalents in user code.
However, user-defined type parameterized functions and types don't look like the built-in ones. You can write a function like New[int32]()
, but the built-in is new(int32)
. You can write a type Map[K,V]
, but the built-in is map[K]V
.
Perhaps if the language had contained generics from the start, we'd have designed the built-in functions to look like user-defined ones: new[T]()
, append[int32](nil, 1, 2, 3)
, map[K,V]
, chan[T]
, etc. But the language didn't have generics, and we didn't design the built-in functions that way, and now we have a bit of inconsistency. This inconsistency is pretty easy to explain to new users, however: "Go 1.0 didn't have user-defined generics, so the built-in functions use a different syntax." It's fine.
If we add new[T](expr)
, we start to chip away at that inconsistency. But if we chip away at it in only one place, we actually increase the amount of inconsistency: Now we have the original built-in generics, the new built-in generics, and the user-defined generics. How do we explain to users why new
comes in two forms, but make
, append
, and so on don't?
Maybe we should fix the inconsistency everywhere. We could add make[T](...)
and append[T](s, v...)
and so on. (I'd use append[int](nil, v...)
in preference to append([]int{}, v...)
, so that might be nice.) But if we do that, then we're implicitly saying that the more-consistent form is better (because otherwise why did we add it?), which will encourage people to migrate code to that form. This is a vastly more broad change than just adding an simple way to create a pointer to a value of a simple type!
So I don't like new[T](expr)
. If we want to add new[T]()
, then we should do that as part of a comprehensive modernization of the built-in generic functions. Perhaps that modernization is worthwhile (although it seems to me like a lot of churn for a dubious benefit) but if we do it we should do so on its own merits rather than as an incidental effect of this proposal.
Comment From: Merovius
@neild I'll add that you can not express the type of append
(or copy
) using current Go generics anyways, due to the exceptions for string
.
Comment From: ianlancetaylor
I currently think that if we do anything we should start with new(T, expr)
. That is a simple and small extension to the current language. For expressions like new(int32, 1)
, the type is not redundant. For struct types, the composite literal syntax remains a concise alternative.
It seems to me that the case where the type is both redundant and slightly annoying is when you want to take the address of a value returned by a function, and the function returns a complicated type. How often does that happen in practice?
If we use new[T](expr)
, then presumably we are suggesting that T
can be inferred in many cases. In other words, this is a different syntax for new(expr)
. Since people will often write simply new(name)
, this suffers from the ambiguity problem mentioned above: when reading the code it's not clear whether name
is an expression or a type.
Comment From: benhoyt
@ianlancetaylor That's certainly an obvious place to start, and not overly verbose for the common use cases (mine are normally int
or string
). It's a bit stuttery for things like time.Now()
(my original case years ago), but still not bad as a one-liner. I'd be okay with this:
s := myStruct{
intField: new(int, 5),
stringField: new(string, "Hello"),
timeField: new(time.Time, time.Now()),
}
Comment From: jakebailey
We have the ptrTo
thing many times in typescript-go thanks to generating LSP types that have to interact with JS/JSON null
and so on.
Ignoring generated test code, 74 out of 106 the instances of ptrTo
(so far) are inferrable and would be written redundantly with the above proposal, like new(string, "...")
, new(bool, true)
, new(int, 0)
.
I feel like that's not so good, and would probably opt to keep using ptrTo
.
Comment From: adonovan
A compromise: we could permit the most efficient notation without opening the Pandora's box of generic builtins, by allowing all three forms:
new(T)
-- existing form, zero valuenew(T, value)
-- explicit type, nonzero valuenew(value)
-- implicit type, nonzero value
The type checker should be able to distinguish all three cases well enough.
Comment From: ianlancetaylor
@adonovan My concern, which others have also expressed on this issue, is that new(value)
can be confusing to the person reading a Go program. I don't think there is any question that the type checker could handle it. The question is whether a reader can easily understand the type and meaning of new(otherpkg.X)
.
Comment From: Merovius
I don't really think that argument holds up. Currently, if you want to set e.g. a field and do v.F = otherpkg.X
, we are fine with that. I don't see why that would be more of a problem, if F
has a pointer type and you write v.F = new(otherpkg.X)
.
The type of new(expr)
is "pointer to the type of expr
". So it seems obvious if and only if the type of expr
is obvious.
[edit] oh egg on my face, the issue is whether otherpkg.X
is a type or an expression, fair enough.
Would a solution be, to accept new(v)
only if v
is a BasicLit
? That would distinguish these cases very obviously, while still addressing the main issue we are trying to solve.
Comment From: aclements
In today’s proposal review, we spent a while discussing the &expr
option. I’d polled several Go programmers I know about what they would expect the syntax for this to be and every single one said &expr
, by analogy to &T{...}
. One person went so far as to say "I avoid new
at all costs. I use &T{...}." &expr
seems like an intuitive and obvious answer, but it has some serious, non-obvious flaws.
@DeedleFake’s handy AI summary didn’t do this option justice, so I wanted to explore it more. It listed three cons: 1. “& behavior changes (allocates copy vs. existing address)” I don’t see as a problem because &T{}
already behaves this way and is extremely common. 2. “Untyped constants ambiguous (e.g., &3 type?)” is easily resolved by using Go's existing rules for the default types of untyped constants. 3. “Bug-prone: &m[k] copies instead of addressing map; surprises with interfaces or type assertions” gets closer to the serious issue.
I see three choices for the final con, all of which have unfortunate tradeoffs:
-
If
expr
can be an arbitrary expression, then&
behaves wildly differently depending on whetherexpr
is addressable or not, but this is often hard to predict. The summary gave the example of&m[k]
: here, ifm
is a slice, then this addresses the k’th element, but ifm
is a map, then this would copy the value and return a new allocation. -
We could limit
expr
to a constant expression.&x
is still ambiguous depending on whetherx
is a variable or a constant, but this resolves the issue with&m[k]
and it’s generally much easier to spot a constant expression than a non-addressable one. However, this is more limited than options likenew(expr)
, which can take an arbitrary expression and work the same way regardless of the expression’s addressability. -
We could limit
expr
to a BasicLit, like @Merovius’s suggestion. With this, the behavior of&
would always be syntactically clear, but this is also extremely limiting. For example, this doesn’t even permit&int64(42)
.
Comment From: DeedleFake
@aclements, is it possible to do option 3 and then add a special case for conversions?
Comment From: jakebailey
I should have gone more into detail in my ptrTo
cases from typescript-go
above, but about a third of them are of the form ptrTo(lsproto.CompletionItemKindClass)
, usually some predefined constant/enum value.
Things get even more biased if we include our generated test code, which brings the ptrTo
count up to >6000 calls, about 3000 of which are like ptrTo(lsproto.CompletionItemKindClass)
. Only about 300 or so are BasicLit
.
Comment From: Merovius
@aclements
With this, the behavior of & would always be syntactically clear, but this is also extremely limiting. For example, this doesn’t even permit
&int64(42)
.
But I believe we could make it so &42
is assignable to *int64
, which IMO is even better. It requires special treatment of new(expr)
as the RHS of assignments. We already do that for nil
. It's not super elegant, but it doesn't seem too hard to be made to work.
I think a more significant limit is that you can't do &pkg.EnumLikeConstant
(as @jakebailey brings up). For new(expr)
that would be an inherent limitation, to avoid the ambiguity of new(expr)
vs. new(type)
. For &expr
that wouldn't be the case and we could also allow expr
to be a qualified identifier.
Point is: if we want, we can limit expressions to whatever subset of constant expressions we want. It drives up the cost in complexity. But really, I don't see how we can both get something conveniently addressing everything we want, while not including anything that might look ambiguous. Also, arguably, we already have that situation, as &thing
is valid for very specific thing
s (struct literals and addressable expressions, where "addressable" is, as you say, hard to predict sometimes).
I'll also note that for #74472, I already considered the BasicLit | QualifiedIdent
restriction as well. I think having a very simple subset of constant expressions might be generally useful. General constant expressions are a very good decision for initializers. But maybe, even making them available for things like array sizes was more than we should have done.
(Personally, I don't like the aesthetic of &expr
, but whatever, maybe I will get used to it)
Comment From: adonovan
The main motive for this proposal is to avoid the need to call helpers such as ptrOf(i)
(or proto.Int64(i)
for users of protocol messsages) to create a new int variable whose value is i, so any design that only permits constant values of i is a non-solution. That eliminates @aclements' variants 2 and 3 for dealing with &expr
, leaving 1, which I think we all agree violates the principle of least astonishment. (&somePtrExpr.f
would work as it does today, but &someStructValueExpr.f
would allocate.) So I think &expr
is a non-starter.
Comment From: t9t
One person went so far as to say "I avoid
new
at all costs. I use &T{...}."
I must say that I find this argument against amending new
and favoring &
a little weak, since the fact that &T{}
is preferable is because you can take the address of a filled-in value directly, rather than taking the address of a (new) zero value. In my experience, new(T)
has limited use anyway. If new(value)
was allowed then the difference would be aesthetic only. And perhaps people would even prefer new
over &T{}
for consistency when &
couldn't be used with simple types and new
could (plus the name new
being explicit about creating a new value rather than taking the address of something existing, which is what &
is primarily used for).
Is there any particular reason to try very hard to fit it into existing operators and functions like &
and new
? I guess there must be strong reasons to not introduce a new builtin function for this?
Comment From: Merovius
@adonovan
The main motive for this proposal is to avoid the need to call helpers such as
ptrOf(i)
(orproto.Int64(i)
for users of protocol messsages) to create a new int variable whose value is i, so any design that only permits constant values of i is a non-solution.
I don't think that follows. If approximately every usage of such functions happens with a constant argument, then a solution that only permits constants seems fine.
Comment From: adonovan
I don't think that follows. If approximately every usage of such functions happens with a constant argument, then a solution that only permits constants seems fine.
That's fair. My tacit assumption (having written protobuf code) is that there are a great many uses with non-constant arguments.
Comment From: greg-dennis
One person went so far as to say "I avoid new at all costs. I use &T{...}."
One could view that as an argument for reusing/repurposing new
as the missing ptrOf
function. That is, the "solution" to the "new(x) is ambiguous" argument could be style guidance: "don't use new(T) and assume x is an expression unless obvious or proven otherwise." Since many developers are already exclusively using &T{}
over new(T)
, for them it would just be codifying existing practice. If this path were chosen, it might make sense for the two-argument overload to be new(expr, T)
, rather than new(T, expr)
, to emphasize the expectation that the first argument be an expression. Yeah, it's an uncomfortable solution that doesn't fully remove the ambiguity, but it sounds like we're in a space of only uncomfortable solutions.
Comment From: apparentlymart
Further to @greg-dennis's point above, if we did find consensus that new(value)
should be preferred over new(type)
moving forward, then presumably after a reasonable amount of time for the ecosystem to adjust to that the typical "lint"-type tools could start suggesting to replace new(type)
with new(value)
, similar to how it's now become common for tools to recommend using any
instead of interface{}
.
Though I concede that interface{}
to any
is a mechanical replacement, whereas new(type)
to new(value)
requires thinking about how to write out the zero value of type
. A generic function that returns the zero value of a given type would be useful here -- new(zero[type]())
or new(zero(type))
-- though I do note that ideas along that line were already declined a few times, like in https://github.com/golang/go/issues/61372, and that would also take us back to a situation where there's more than one way to write the same thing.
Comment From: Merovius
As someone who quite frequently uses new(type)
and have defended that use¹, I would be grumpy if I'm being told that I should avoid it in the future. I would adhere to it, if that is ultimately for the best, but under protest.
[1] ICYI: I use &Struct{}
if Struct
has exported fields that I might want to initialize (e.g. http.Server
), to make explicit that I want defaults, and I use new(Struct)
if it doesn't (e.g. bytes.Buffer
) to make explicit that the type is opaque.
Comment From: JAicewizard
A compromise: we could permit the most efficient notation without opening the Pandora's box of generic builtins, by allowing all three forms:
* `new(T)` -- existing form, zero value * `new(T, value)` -- explicit type, nonzero value * `new(value)` -- implicit type, nonzero value
The type checker should be able to distinguish all three cases well enough.
It would be a much more invasive change, but we could use _
as a "infered" type in this specific case. In languages with pattern matching _
is already often used as a "whatever fits" indicator, although usually for values not types. If we want consistency with the rest of the language we could also allow things like var x _ = ...
as a longhand for x := ...
and func[_](x)
as a longhand for fnuc(x)
. (I might actually use the latter, when I have a lot of calls to func where some can be auto-infered. I would rather they all have [...] but the LSP complains about inferable explicitly given type params, _
could be a "I know shut up" value for this)
Comment From: adonovan
I wrote a quick analyzer to find calls equivalent to new(value)
, and ran it over a sample of 25K modules in corpus. It turned up 5K functions equivalent to new(value)
, and 700K calls. The analyzer source is at the end of this note.
A random 25 of each are listed below. The attached files contain a complete lists of functions and a 10K random subset of calls (due to GitHub upload limits).
funcs funcs.txt
https://go-mod-viewer.appspot.com/git.zd.zone/hrpc/hrpc@v0.0.12/types/basic.go#L11: Int64 is a new(value) func https://go-mod-viewer.appspot.com/gitee.com/larksuite/oapi-sdk-go/v3@v3.0.3/core/utils.go#L37: StringPtr is a new(value) func https://go-mod-viewer.appspot.com/github.com/agrea/ptr@v0.2.0/ptr.go#L16: Float32 is a new(value) func https://go-mod-viewer.appspot.com/github.com/akamai/AkamaiOPEN-edgegrid-golang/v2@v2.17.0/pkg/imaging/policy.gen.go#L1992: IfDimensionDimensionPtr is a new(value) func https://go-mod-viewer.appspot.com/github.com/alibabacloud-go/tea@v1.3.10/tea/trans.go#L14: Int is a new(value) func https://go-mod-viewer.appspot.com/github.com/chanxuehong/wechat@v0.0.0-20230222024006-36f0325263cd/util/helper.go#L29: Float32 is a new(value) func https://go-mod-viewer.appspot.com/github.com/clerkinc/clerk-sdk-go@v1.49.1/clerk/http_utils_test.go#L62: int64ToPtr is a new(value) func https://go-mod-viewer.appspot.com/github.com/cznic/mathutil@v0.0.0-20181122101859-297441e03548/all_test.go#L61: bytePtr is a new(value) func https://go-mod-viewer.appspot.com/github.com/google/go-github/v69@v69.2.0/github/github.go#L1754: Int is a new(value) func https://go-mod-viewer.appspot.com/github.com/hashicorp/hcp-sdk-go@v0.154.0/clients/cloud-billing/stable/2020-11-05/models/billing_account_on_demand_status.go#L23: NewBillingAccountOnDemandStatus is a new(value) func https://go-mod-viewer.appspot.com/github.com/hupe1980/go-huggingface@v0.0.15/huggingface.go#L207: PTR is a new(value) func https://go-mod-viewer.appspot.com/github.com/Ingenico-ePayments/connect-sdk-go@v0.0.0-20240318153750-1f8cd329b9c9/examples/merchant/productgroups/Helper.go#L25: newInt32 is a new(value) func https://go-mod-viewer.appspot.com/github.com/intel-go/fastjson@v0.0.0-20170329170629-f846ae58a1ab/decode_test.go#L225: sliceAddr is a new(value) func https://go-mod-viewer.appspot.com/github.com/kaptinlin/jsonschema@v0.4.6/struct_validation_test.go#L97: boolPtr is a new(value) func https://go-mod-viewer.appspot.com/github.com/livekit/protocol@v1.39.3/utils/pointer/to.go#L17: To is a new(value) func https://go-mod-viewer.appspot.com/github.com/lmittmann/w3@v0.20.0/internal/abi/copy_test.go#L313: ptr is a new(value) func https://go-mod-viewer.appspot.com/github.com/openshift-online/ocm-sdk-go@v0.1.473/helpers/json_helpers.go#L70: NewInteger is a new(value) func https://go-mod-viewer.appspot.com/github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/deploy/assets/assets.go#L319: replicas is a new(value) func https://go-mod-viewer.appspot.com/github.com/sacloud/libsacloud/v2@v2.32.3/sacloud/pointer/primitive.go#L39: NewUint is a new(value) func https://go-mod-viewer.appspot.com/github.com/stripe/stripe-go/v76@v76.25.0/stripe.go#L1259: String is a new(value) func https://go-mod-viewer.appspot.com/github.com/TykTechnologies/newrelic-go-agent@v0.0.0-20230823062834-3c80ff5627f6/internal/connect_reply.go#L130: uintPtr is a new(value) func https://go-mod-viewer.appspot.com/github.com/Uptycs/basequery-go@v0.8.0/gen/osquery/osquery.go#L103: ExtensionRouteTablePtr is a new(value) func https://go-mod-viewer.appspot.com/github.com/vmware/govmomi@v0.51.0/vim25/json/discriminator_test.go#L763: addrOfUint32Noop is a new(value) func https://go-mod-viewer.appspot.com/gitlab.com/ignitionrobotics/web/ign-go@v1.0.0-rc4/types.go#L14: Float64 is a new(value) func https://go-mod-viewer.appspot.com/google.golang.org/grpc@v1.74.2/xds/internal/xdsclient/xdsresource/unmarshal_rds_test.go#L1607: newBoolP is a new(value) func
calls calls.txt
https://go-mod-viewer.appspot.com/github.com/aliyun/credentials-go@v1.4.7/integration/proxy/proxy_test.go#L34: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6@v6.2.0/fake/adminrules_server.go#L323: call to new(value)-like func Ptr https://go-mod-viewer.appspot.com/github.com/biogo/biogo@v1.0.4/io/featio/gff/gff_test.go#L58: call to new(value)-like func floatPtr https://go-mod-viewer.appspot.com/github.com/google/go-github/v33@v33.0.0/github/github-stringify_test.go#L608: call to new(value)-like func Int https://go-mod-viewer.appspot.com/github.com/google/go-github/v42@v42.0.0/github/event_types_test.go#L10643: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v42@v42.0.0/github/event_types_test.go#L11130: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v42@v42.0.0/github/repos_hooks_deliveries_test.go#L288: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v48@v48.2.0/github/event_types_test.go#L1509: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v49@v49.1.0/github/enterprise_actions_runners_test.go#L142: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v50@v50.2.0/github/activity_test.go#L167: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v52@v52.0.0/github/event_types_test.go#L7953: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v53@v53.2.0/github/event_types_test.go#L9960: call to new(value)-like func Int https://go-mod-viewer.appspot.com/github.com/google/go-github/v57@v57.0.0/github/event_types_test.go#L6144: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v60@v60.0.0/github/event_types_test.go#L1201: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v64@v64.0.0/github/security_advisories_test.go#L1518: call to new(value)-like func Bool https://go-mod-viewer.appspot.com/github.com/google/go-github/v65@v65.0.0/github/teams_discussions_test.go#L562: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v66@v66.0.0/github/github-stringify_test.go#L869: call to new(value)-like func String https://go-mod-viewer.appspot.com/github.com/google/go-github/v68@v68.0.0/github/github-stringify_test.go#L635: call to new(value)-like func Ptr https://go-mod-viewer.appspot.com/github.com/google/go-github/v69@v69.2.0/github/repos_statuses_test.go#L208: call to new(value)-like func Ptr https://go-mod-viewer.appspot.com/github.com/google/go-github/v70@v70.0.0/github/gists_test.go#L124: call to new(value)-like func Ptr https://go-mod-viewer.appspot.com/github.com/google/go-github/v71@v71.0.0/github/enterprise_actions_runner_groups_test.go#L305: call to new(value)-like func Ptr https://go-mod-viewer.appspot.com/github.com/google/go-github/v71@v71.0.0/github/event_types_test.go#L7201: call to new(value)-like func Ptr https://go-mod-viewer.appspot.com/github.com/google/go-github/v74@v74.0.0/github/git_refs_test.go#L311: call to new(value)-like func Ptr https://go-mod-viewer.appspot.com/github.com/mattermost/mattermost-server/v5@v5.39.3/store/storetest/group_store.go#L4018: call to new(value)-like func NewString https://go-mod-viewer.appspot.com/github.com/tsuna/gohbase@v0.0.0-20250731002811-4ffcadfba63e/hrpc/hrpc_test.go#L546: call to new(value)-like func Bool
var Analyzer = &analysis.Analyzer{
Name: "newvalue",
Doc: "xxx",
URL: "yyy",
Run: run,
FactTypes: []analysis.Fact{&newLike{}},
}
func run(pass *analysis.Pass) (any, error) {
// pass 1. export facts
for _, file := range pass.Files {
ast.Inspect(file, func(n ast.Node) bool {
switch n := n.(type) {
case *ast.FuncDecl:
fn := pass.TypesInfo.Defs[n.Name].(*types.Func)
if n.Body != nil && len(n.Body.List) == 1 {
if ret, ok := n.Body.List[0].(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
if unary, ok := ret.Results[0].(*ast.UnaryExpr); ok && unary.Op == token.AND {
if id, ok := unary.X.(*ast.Ident); ok {
if v, ok := pass.TypesInfo.Uses[id].(*types.Var); ok {
sig := fn.Signature()
if sig.Results().Len() == 1 &&
is[*types.Pointer](sig.Results().At(0).Type()) && // => no iface conversion
sig.Params().Len() == 1 &&
sig.Params().At(0) == v {
pass.ReportRangef(n, "%s is a new(value) func", n.Name)
pass.ExportObjectFact(fn, &newLike{})
}
}
}
}
}
}
}
return true
})
}
// 2. report calls
for _, file := range pass.Files {
ast.Inspect(file, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
var fact newLike
if fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func); ok &&
pass.ImportObjectFact(fn, &fact) {
pass.ReportRangef(call, "call to new(value)-like func %s", fn.Name())
}
}
return true
})
}
return nil, nil
}
func is[T any](x any) bool {
_, ok := x.(T)
return ok
}
type newLike struct{}
func (newLike) AFact() {}
Comment From: andig
...and there's of course lo.ToPtr
from samber/lo
Comment From: alnr
Like many others in this thread, I've been subscribed for a while and read most or all of the discussion.
Initially, I liked a syntax like &"literal"
the best, but that has a bunch of bad consequences.
Like many others (I suspect), I'm also a fan of not adding new keywords/builtins if it can be avoided. Hence new(value)
seems not so bad. But that turns out to also have a bunch of bad consequences, specifically distinguishing new(otherpkg.Type)
from new(otherpkg.Variable)
.
So it seems to me we should just bite the bullet:
* add newOf
builtin function with definition func newOf[T any](t T) *T { return &t }
.
* for string, ints, floats (and imaginary?), allow var i *int64 = newOf(123)
, i.e. magic conversion from int*
to int64*
.
Not too long ago, the builtins min
and max
where added and they are quite useful. It wasn't a big deal from a language POV but a good quality of life improvement.
Beside newOf
being slightly more ugly than new
or &
, it solves the issue cleanly from what I understand. And newOf
also makes quite clear a value is being copied, i.e., a copy/allocation happens and the pointer is to a new memory location.
Comment From: josharian
@alnr I might paint the shed a different color (e.g. ptr
or ptrTo
), but the analogy with min
and max
is pretty compelling.
Comment From: adonovan
Hence new(value) seems not so bad. But that turns out to also have a bunch of bad consequences, specifically distinguishing new(otherpkg.Type) from new(otherpkg.Variable).
We still haven't established whether this is a real problem: perhaps in ~all cases, it's clear in context whether otherpkg.X is a type or a value.
for string, ints, floats (and imaginary?), allow var i int64 = newOf(123), i.e. magic conversion from int to int64*.
This would mean inferring a type from the context of an expression rather than from the expression itself. To date, the only places in the entire language where we do this are
(a) nested composite literals (but the outermost literal always has an explicit type, making things simple), and
(b) for x << y
shift expressions, which are one of the most complex aspects of the spec and implementation.
Although there may be open proposals for generalizing the notion of top-down inference (#61712) they all involved major changes to the theory of Go's types. So let's not casually open that door here.
Not too long ago, the builtins min and max where added and they are quite useful.
A new builtin would be easy. But we haven't yet exhausted whether this can be safely fitted into the existing builtin.
Also:
new(T, value)
-- explicit type, nonzero value
@aclements pointed out yesterday that this 2-argument form of new is unnecessary since you can always say new(T(value))
, which is just about maximally clear and succinct.
Comment From: Merovius
This would mean inferring a type from the context of an expression rather than from the expression itself. To date, the only places in the entire language where we do this are
We also, importantly here, do it for (untyped) constants. And for nil
. ISTM that once we are at "the only four places where we do this", the door is open.
Comment From: Merovius
That's fair. My tacit assumption (having written protobuf code) is that there are a great many uses with non-constant arguments.
By the way, it might be worth noting that the opaque protobuf API eliminates the need to construct pointers to primitive types, as I understand it. That doesn't make the general usefulness of this proposal go away completely, but I do believe protobuf was one of the main pain points.
Comment From: adonovan
We also, importantly here, do it for (untyped) constants. And for
nil
. ISTM that once we are at "the only four places where we do this", the door is open.
I think that's a property of the implementation, but not something inherent to the spec. That is, the types of 0 and nil are "untyped zero" and "untyped nil", and there are implicit conversions when they are used in var ( _ int = 0; _ *int = nil )
. Unfortunately go/types pushes the LHS type down in to TypeOf(RHS) in these cases. I regret having requested this behavior instead of asking for it to record all implicit conversions, which would have been cleaner and much more useful.
And the composite literal case truly is simple because it always destructures an explicit type. So that leaves just shifts, and we should definitely not use the way shifts work as justification for further top-down type inference.
Comment From: alnr
This would mean inferring a type from the context of an expression rather than from the expression itself. To date, the only places in the entire language where we do this are
I would allow var i *int64 = newOf(123)
but not var j int; var i *int64 = newOf(j);
Basically if the argument is an untyped constant.
But it's also not really necessary if the implementation or rules would get to complicated.
It can be achieved simply by var j int; var i *int64 = newOf(int64(j));
.
We still haven't established whether this is a real problem: perhaps in ~all cases, it's clear in context whether otherpkg.X is a type or a value.
Consider
package foo
type Enum int
var (
Enum1 Enum = iota
Enum2 Enum
)
...
package bar
var e1 = new(foo.Enum) // ok
var e2 = new(foo.Enum1) // also OK, very different semantics, looks extremely similar.
new(T, value)
seems cool at first, but will be worse than newOf
(or a custom helper like everyone has right now) in this pretty common scenario:
package complicatedname
type ComplicatedEnumChooseMethod int
var (
ComplicatedEnumChooseMethodSomething ComplicatedEnumChooseMethod = iota
ComplicatedEnumChooseMethodAnything ComplicatedEnumChooseMethod
)
type Option struct {
Method *ComplicatedEnumChooseMethod
}
func Fun(o Option) { ... }
...
package main
import "complicatedname"
complicatedname.Fun(complicatedName.Option{
Method: new(complicatedName.ComplicatedEnumChooseMethod, complicatedname.ComplicatedEnumChooseMethodSomething),
})
// versus
complicatedname.Fun(complicatedName.Option{
Method: newOf(complicatedname.ComplicatedEnumChooseMethodSomething),
})
Comment From: adonovan
By the way, it might be worth noting that the opaque protobuf API eliminates the need to construct pointers to primitive types, as I understand it. That doesn't make the general usefulness of this proposal go away completely, but I do believe protobuf was one of the main pain points.
True, but the problem still crops up in a variety of related situations in which a pointer is used in a schema to represent optionality, so it's common in other encodings such as JSON as well.
Comment From: alnr
By the way: why is this OK
var _ = &([]string{"foo"})
but this isn't?
var _ = &(time.Now())
Comment From: adonovan
By the way: why is this OK
var _ = &([]string{"foo"})
but this isn't?
var _ = &(time.Now())
The answer to your question is because that's what the spec says. The bigger question raised by your question is exactly what this proposal is trying to answer.
Comment From: alnr
What is the issue with allowing &(int(123))
or &(123)
or &(time.Now())
then?
I get this is potentially error prone when doing &otherpkg.Variable
because it doesn't point to a new memory location. But other than that?
Apologies if this was discussed prior. It's quite difficult to keep every aspect in one's head after so many replies in this thread.
Comment From: artificial-aidan
for string, ints, floats (and imaginary?), allow var i int64 = newOf(123), i.e. magic conversion from int to int64*.
This feels unnecessary when var i := newOf(int64(123))
is the same amount of typing and more explicit.
I am in favor of just a new construct. Even a ptr
stdlib package with a To
method would be fine. I've been using the azure SDK one lately, because I usually have it handy, but any of them work. Just standardizing on a simpler reusable expression is the intention of this issue right?
Comment From: t9t
@alnr I think this is the most comprehensive comment discussing the &
variant: https://github.com/golang/go/issues/45624#issuecomment-3138414707
To me it feels like it will introduce a couple new footguns that could be avoided with a different syntax (e.g. a new builtin function, or a re-use of new
).
@josharian
I might paint the shed a different color (e.g. ptr or ptrTo), but the analogy with min and max is pretty compelling.
ptr
and ptrTo
are misleading names if the function doesn't return a pointer to the value, but creates a new value and returns a pointer to that new value (see also https://github.com/golang/go/issues/45624#issuecomment-3046301760).
Comment From: aclements
In my experience, new(T) has limited use anyway. If new(value) was allowed then the difference would be aesthetic only. (https://github.com/golang/go/issues/45624#issuecomment-3139948536)
I'm not sure if this is quite what you're saying, but there's an argument that people not using new(T)
today makes new
ripe for reclaiming. To me, and I think a lot of people, new
has always felt like a wart, so why not turn it into something much more useful?
Comment From: aclements
It turned up 5K functions equivalent to new(value), and 700K calls. (https://github.com/golang/go/issues/45624#issuecomment-3161481726)
Thanks for getting these numbers, @adonovan! I find these stats quite compelling in support of this feature.
I'll note that there's some clear data issue where github.com/google/go-github is wildly overrepresented. (We chatted about this offline.) Could you get just the number of unique modules for the calls? go-github would presumably still represent ~74 of those, but it may be more informative. For the function declarations, sed 's/@.*//' funcs.txt | sort | uniq | wc -l
reports 715 unique modules.
Comment From: ianlancetaylor
I find these stats quite compelling in support of this feature.
Yes, thanks @adonovan for getting them. That said I don't think anybody is opposed to the feature. This discussion is about the name or syntax to use.
I remain concerned that new(v)
is confusing with the existing new(T)
. As various people have pointed out (for example, https://github.com/golang/go/issues/45624#issuecomment-1582248350), we don't otherwise confuse values and types. (It's perhaps worth noting that in C++ and Java new
takes a type, not a value.)
I agree that if we have somefunction(v)
then we don't need new(T, v)
.
I agree that none of the proposed non-function syntaxes are persuasive.
I would prefer a function in the standard library to a new builtin function (perhaps unique.Ptr
?). But I lost that argument for min
and max
, and I expect I would lose it here.
So to me that suggests either new(T, v)
or FN(v)
, where FN
could be ptr
, ref
, addr
, something more clever.
Comment From: rogpeppe
So to me that suggests either
new(T, v)
orFN(v)
, whereFN
could beptr
,ref
,addr
, something more clever.
For the record, I'll just say (again?) that I dislike the new(T, v)
form because the T
argument is almost always
redundant, is laborious to type and adds to code clutter.
I generally copy and paste this function everywhere I need it currently:
func ref[T any](x T) *T { return &x }
I don't have strong feelings about the name; I suspect my own preference for ref
is rooted
in long term familiarity.
Comment From: chai2010
It has been 10 years since I initially proposed the new(T, value)
idea (issue9097). Although everyone agrees it's a problem, the debate remains the same as it was 5 years ago. Many thanks to @ianlancetaylor for his support and understanding of the original proposal.
I couldn't wait any longer, so I implemented the new(T, value)
syntax in my Wa language, which is based on the evolution of Go:
func main {
p := new(int, 123)
println(*p)
}
Now we can understand the differences between the two sides without emotional baggage.
Hopefully, we'll see this officially supported one day.
Comment From: adonovan
@chai2010, we move slowly to avoid mistakes we regret. The cost of not adding this is very low given that it's a trivial one-liner. I'm not sure who is bringing the excess baggage here.
@rogpeppe, @ianlancetaylor: I see the concern with new(v)
. I'm still not certain that it would actually be a problem, but I admit it does blur a clear line.
Assuming we add a new function, it's obvious that it could be an ordinary library function (since it has been to date), but it would need to be something with a short enough name that pkg.Func(v)
isn't so verbose that it becomes a disincentive to use (or worse, a temptation to dot-import pkg). unique.Ptr falls into that category; more importantly, new
is unrelated to the unique package. Also, new(v) is so fundamental and low-level a concept that I think there's an even stronger case for a built-in than there was for min and max.
If we add a built-in, I would call it newOf(v) or varOf(v), since it creates a new variable. I dislike the various other names (ref, ptr, addr), since they all fail to express that.
But I would still prefer new(v) if it turns out that the ambiguity isn't a real problem.
Comment From: Merovius
@adonovan
I think that's a property of the implementation, but not something inherent to the spec. That is, the types of 0 and nil are "untyped zero" and "untyped nil" and there are implicit conversions when they are used in var ( _ int = 0; _ *int = nil ).
I disagree. For example, when we look at assignability (which is the part we most care about here) we can see that both for nil
and for untyped constants, there are explicit special cases made for them - and especially the first is quite special. The spec does not assign a type to constants or nil
and then just handles that as an assignment of a variable of type X
to one of type Y
. It specifically explains how untyped constants or the predeclared identifier nil
behave under assignment.
It does not seem a prohibitive complication, to add a "x is a call to the builtin function new
with an expression assignable to T" case to that.
(The same, of course, applies to any other way to spell new(expr)
, like e.g. &expr
)
Unfortunately go/types pushes the LHS type down in to TypeOf(RHS) in these cases.
I think that's a property of the implementation, but not something inherent to the spec (sorry, could not resist).
Comment From: Merovius
@artificial-aidan
for string, ints, floats (and imaginary?), allow
var i *int64 = newOf(123)
, i.e. magic conversion from*int
to*int64
.This feels unnecessary when
var i := newOf(int64(123))
is the same amount of typing and more explicit.
Consider the case where
package otherpkg
type MyEnumType int
const (
MyEnumValue1 MyEnumType = iota
)
type MyStruct struct {
Field *MyEnumType
}
Then otherpkg.MyStruct{ Field: new(otherpkg.MyEnumType(otherpkg.MyEnumValue1)) }
is significantly more to type (and to read) than otherpkg.MyStruct{ Field: new(otherpkg.MyEnumValue1) }
.
Comment From: atdiar
@adonovan I think that "in theory" (in its largest sense), new(v)
and new(T)
could be explained without too many issues.
If we consider types as sets of values, a typed value is a subtype of its type where the subtype only contains a single value. A singleton. (that's a mouthful).
So if new
returns a pointer to an allocation of the zero value of a type, new(v)
and new(T)
stem from the same definition.
Of course, how to spec it in Go is left as an exercise for you ;D.
Comment From: josharian
I would call it newOf(v) or varOf(v), since it creates a new variable.
I hear you. But in practice are there reasons to use this with anything other than constants?
Comment From: fabiopicchi
Adding a new built-in like newOf
in post-generics Go doesn't add much to the language. As @rogpeppe mentioned, his ref
function conveniently solves the problem. Maybe it could be even added to the standard library, but I think that could be discussed in another proposal.
Regardless of the existence of a ref
or ptr
in the standard library, I believe new(T, value)
should be added. As stated in the original proposal, the issue is that &S{}
was added for convenience and that makes it more convenient to allocate a pointer to a struct than one to a basic type. new(S, S{a: 3})
is kinda annoying, but &S{a: 3}
already exists so it is completely optional for the programmer to use the more verbose form.
Personally, I quite like new(int, 4)
. It is orthogonal, the allocation is explicit and even though people don't use new
that much, I use it and like the fact that I don't have to think too much to understand that there is a heap allocation happening.
The reason I believe new(int, 4)
and func ref[T any](x T) *T { return &x }
can coexist is because, even though generics exist in Go, a programmer can still choose not to use it because they consider it too complicated. I see this as maintaining and improving the legacy "pre-generics" Go. Someone mentioned in this issue that if Go had generics from the beginning, new
, make
, append
and other built-ins maybe wouldn't exist (in the sense that they would be generic functions). However, since these built-ins and generics exist in the language, I feel that both could be added.
Comment From: zephyrtronium
I hear you. But in practice are there reasons to use this with anything other than constants?
If I have a pointer-typed variable p
and I want to make a new pointer to a copy of its contents, ref(*p)
is the simplest way. In the kind of code I've been writing recently – which is to say, not using protobuf or the AWS SDK – this is even the more common usage for me than creating pointers to constant values.
(It's nice to know I'm not the only one who still names this function ref
.)
Comment From: adonovan
[@Merovius] The spec does not assign a type to constants or
nil
and then just handles that as an assignment of a variable of typeX
to one of typeY
. It specifically explains how untyped constants or the predeclared identifiernil
behave under assignment.
As you say, untyped nil is special because there is only one expression the entire language that has that type: the identifier spelled nil
. But so-called "untyped" constants really are types, and there are many different forms of expression that can have those types (e.g. 3.14, math.Pi, pi, 3 + 0.14, etc). To create a new category like this but for non-constant expressions such as new(3) would be a major change to the spec and the type system. So would treating new(3) as narrowly and specially as untyped nil.
Unfortunately go/types pushes the LHS type down in to TypeOf(RHS) in these cases. I think that's a property of the implementation, but not something inherent to the spec (sorry, could not resist).
Yeah, no disagreement there.
Comment From: Merovius
@adonovan It seems a fairly small change to me, consisting of a few lines in the spec. But I guess we'll just going to have to disagree.
To be clear, no one has to like this idea. It just seems disingenuous to me, to claim that it is a dramatic deviation from what the spec is doing currently. It is full of such special cases. Most of them, we just never notice in day-to-day coding. Because it turns out that the result is quite intuitive. If we don't think this would be, fair enough. But it's IMO not because it's such a big change from how things work right now.
Comment From: Merovius
I guess the reason why I'm kind of insistent on this point is that twice now, otherwise fine syntax suggestions have been dismissed with the argument, that e.g. new(1)
would not be assignable to *int64
.
I agree that special-casing new
this way would be a cost. It would be something kind of weird people had to learn¹. But IMO that cost is lower than having to use the full name of the type, every time you use this. Because the name never seems useful, to me and can often get quite long. Like, we talk about proto being a common use case for this (modulo opaque API). If I had to type (or read) new(controlpb.IntelligenceConfig_EditionConfig, IntelligenceConfig_EDITION_CONFIG_UNSPECIFIED)
, I'd be pretty annoyed.
I just don't want us to throw away the option of otherwise good syntax suggestions, just because we are hesitant of having to do an extra step to solve the assignability issue.
[1] Though I'd argue in practice not any weirder than the existing constant rules.
Comment From: adonovan
IMO that cost is lower than having to use the full name of the type, every time you use this.
FWIW, Rog Peppe's Mar 7 comment already convinced me that it is important not to have to redundantly state the type and the value, making new(T, v)
a non-solution. Hence my interest in new(value)
, spelled newOf or varOf if we must, with new(T(v))
for when the value has the wrong type.
BTW, In your enum example, the value does have the right type, so you could use just new(IntelligenceConfig_EDITION_CONFIG_UNSPECIFIED)
, which makes me wonder exactly what it is we are disagreeing about. Perhaps you want to say just x.f = new()
, with no value, and have the type inferred from context?
I really do believe that top-down type inference rules are a significant conceptual complication. Even if we've already crossed that Rubicon I don't think this is an important enough feature to justify taking it further.
Comment From: jonathansharman
I'm not a Go compiler maintainer, just a user, but for my part I don't see a problem with special-casing the typing for new(value)
(or equivalent) in the same way as constants or nil
. I think for almost all users, it would be a transparent feature that just works, not something they have to understand from an implementation standpoint. I'm curious if anyone can produce a code snippet where this feature could plausibly produce confusing behavior.
I do feel a little strange about reusing new
for values. I'm generally not too worried about confusing a variable name for a type name, but one place where I think that could realistically come up is where a local variable shadows a type name. (Apologies if that's already been mentioned. The thread is long.) I'm weakly in favor of choosing another name for the builtin, but I think either option is workable!
Comment From: atdiar
@jonathansharman I believe that if a local variable name reuses a type name, that may only be an issue at best if it is defined as such:
func(int int){...}
or var int int
. But not if we have var int someothertype
.
And then, we need to want to assign (in the local scope) new(int) to a variable defined in the outerscope such that it is of type *int
. instead of the 0 value, we would point to something that would store whatever was in int
.
Old code wouldn't suffer from this since trying this would be a compilation error (the redefined int is not a type)
So at best for new code? Unlikely.
For the rest,
var i *int64 = new(1)
vs. var i *int64 = new(int64(1))
The latter doesn't strike me as being particularly unpleasant.
Might even be clearer in the case x.f = new(int64(3))
There are helpful cases of redundant information when reading code.
I also can't see carving a hole in the type inference algorithm for new
, when its argument is a const, as being that straightforward.