Description
This notion was addressed in #9097, which was shut down rather summarily. Rather than reopen it, let me take another approach.
When &S{}
was added to the language as a way to construct a pointer to a composite literal, it didn't quite feel right to me. The allocation was semi-hidden, magical. But I have gotten used to it, and of course now use it often.
But it still bothers me some, because it is a special case. Why is it only valid for composite literals? There are reasons for this, which we'll get back to, but it still feels wrong that it's easier to create a pointer to a struct:
p := &S{a:3}
than to create a pointer to a simple type:
a := 3
p := &a
I would like to propose two different solutions to this inconsistency.
Now it has been repeatedly suggested that we allow pointers to constants, as in
p := &3
but that has the nasty problem that 3 does not have a type, so that just won't work.
There are two ways forward that could work, though.
Option 1: new
We can add an optional argument to new
. If you think about it,
p := &S{a:3}
can be considered to be shorthand for
p := new(S)
*p = S{a:3}
or
var _v = S{a:3}
p := &_v
That's two steps either way. If we focus first on the new
version, we could reduce it to one line by allowing a second, optional argument to the builtin:
p := new(S, S{a:3})
That of course doesn't add much, and the stuttering is annoying, but it enables this form, making a number of previously clumsy pointer builds easy:
p1 := new(int, 3)
p2 := new(rune, 10)
p3 := new(Weekday, Tuesday)
p4 := new(Name, "unspecified")
... and so on
Seen in this light, this construct redresses the fact that it's harder to build a pointer to a simple type than to a compound one.
This construct creates an addressible form from a non-addressible one by explicitly allocating the storage for the expression.
It could be applied to lots of places, including function returns:
p := new(T, f())
Moreover, although we could leave out this step (but see Option 2) we could now redefine the &
operator applied to a non-addressible typed expression to be,
new(typeOfExpression, Expression)
That is,
p := &expression
where expr
is not an existing memory location is now just defined to be shorthand for
p := new(typeOfExpression, expression)
Option 2
I am more of a fan of the new
builtin than most. It's regular and easy to use, just a little verbose.
But a lot of people don't like it, for some reason.
So here's an approach that doesn't change new.
Instead, we define that conversions (and perhaps type assertions, but let's not worry about them here) are addressible.
This gives us another mechanism to define the type of that constant 3:
p := &int(3)
This works because a conversion must always create new storage.
By definition, a conversion changes the type of the result, so it must create a location of that type to hold the value.
We cannot say &3
because there is no type there, but by making the operation apply to a conversion, there is always a defined type.
Here are the examples above, rewritten in this form:
p1 := &int(3)
p2 := &rune(10)
p3 := &Weekday(Tuesday)
p4 := &Name("unspecified")
Discussion
Personally, I find both of these mechanisms attractive, although either one would scratch the itch.
I propose therefore that we do both, but of course the discussion may end up selecting only one.
Template
Would you consider yourself a novice, intermediate, or experienced Go programmer?
I have some experience.
What other languages do you have experience with?
Fortran, C, Forth, Basic, C, C++, Java, Python, and probably more. Just not JavaScript
Would this change make Go easier or harder to learn, and why?
Perhaps a little easier, but it's a niche problem.
Has this idea, or one like it, been proposed before?
Yes, in issue #9097 and probably elsewhere.
If so, how does this proposal differ?
A different justification and a new approach, with an extension of new
.
Who does this proposal help, and why?
People annoyed by the difficulty of allocating pointers to simple values.
What is the proposed change?
See above.
Please describe as precisely as possible the change to the language.
See above.
What would change in the language spec?
The new
operator would get an optional second argument, and/or conversions would become addressible.
Please also describe the change informally, as in a class teaching Go.
See above.
Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit.
Yes. Don't worry.
Show example code before and after the change.
See above.
What is the cost of this proposal? (Every language change has a cost).
Fairly small compiler update compared to some others underway. Will need to touch documentation, spec, perhaps some examples.
How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
Perhaps none? Not sure.
What is the compile time cost?
Nothing measurable.
What is the run time cost?
Nothing measurable.
Can you describe a possible implementation?
Yes.
Do you have a prototype? (This is not required.)
No.
How would the language spec change?
Answered above. Why is this question here twice?
Orthogonality: how does this change interact or overlap with existing features?
It is orthogonal.
Is the goal of this change a performance improvement?
No.
If so, what quantifiable improvement should we expect?
More regularity for this case, removing a restriction and making some (not terribly common, but irritating) constructs shorter.
How would we measure it?
Eyeballing.
Does this affect error handling?
No.
If so, how does this differ from previous error handling proposals?
N/A
Is this about generics?
No.
If so, how does this differ from the the current design draft and the previous generics proposals?
N/A
Metadata
Metadata
Assignees
Type
Projects
Status