-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: spec: expression to create pointer to simple types #45624
Comments
How much would it break things to let 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. |
Alternatively, what's the problem with adding composite literals of simple types, like |
If we have this new I think I like the Adding |
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. |
I like the 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()
}()) |
I prefer As far as I can tell, this would make it possible to express all valid Go programs without using the
|
@peterbourgon Probably we shouldn't derail this to try to get rid of
I used to be in favor of |
@benhoyt If you only restrict |
When supporting taking the address of return values, the question on whether returning makes a copy of the return value obtains. For example, consider code like this:
@benhoy Not really in favour of the |
I also kinda wonder why the obvious |
Rob, don't tell me about such a gap. I was implementing Bliss interface for so long. |
@clausecker Because why add a new construct ( |
@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 |
The As far as I understand, only numeric literals have a problem with unambiguous type inference. With the above in mind, Examples: _ = &int(42)
_ = &true
_ = &"brains"
type Name string
_ = &Name("what's my name?")
type Count int64
_ =&Count(100500) |
The Go 2 playground permits the function:
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 I'd suggest just waiting for generics to drop and writing/providing that function. |
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 But either way, having the Option 2 would be very cool IMO. |
While it's true that generics would allow you to write the |
If I can add voice here, I would much prefer option 2 than 1. |
Do we have any data on I'm definitely preferential to option 2 ( |
Why not |
Indeed, I understand the difference between conversion and function calls, but I feel like people learning Go will be confused by 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 |
I like that Jerf's |
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 (#45624 (comment)) 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 |
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.... |
One of the subproposals discussed in #34515 is to omit the type in |
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. |
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.
} |
Ah, so a map can be directly passed as value (instead of reference/pointer to it) while still Indeed, now an on-demand allocation would cause this kind of trouble. If we'd ever go that But what would happen (besides extra compiler complexity) if we'd let it implicitly emit an Am I missing something ? By the way, still haven't fully understood how code generation and runtime code really work thx. |
Summarizing some comments:
Given that it is trivial to write the helper function, a language change would add marginal value. |
@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. |
Leaving open until someone brings this discussion to a consensus. |
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 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. |
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, |
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. |
If the type inference worked well enough (perhaps by just hard coding this) that you could write |
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? |
@robpike's original option 1, I'll bring it up again at the next review. |
Very happy to hear that. I care deeply about this wart (the asymmetry) in the language, although it's not a major flaw. |
My main use case is in JSON with optional fields where I have a struct with 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),
} |
@mitar There has been a lot of discussion on this issue. I believe that idea was first proposed at #45624 (comment). |
I believe my most recent summary of this issue was #45624 (comment) followed by #45624 (comment). |
Moving to regular proposal committee. |
FWIW I still write this tiny function wherever I need it:
Replacing, for example, Another idiom I commonly use involving the above function is to make a shallow copy of a pointer value:
Again, the requirement to explicitly mention the type of I still think that some equivalent of the |
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:
than to create a pointer to a simple type:
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
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,can be considered to be shorthand for
or
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: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:
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:
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,That is,
where
expr
is not an existing memory location is now just defined to be shorthand forOption 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:
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:
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
The text was updated successfully, but these errors were encountered: