Skip to content

spec: expression to create pointer to simple types #45624

Closed

Description

@robpike

(Latest proposal at #45624 (comment); --adonovan)

This notion was addressed in #9097, which was shut down rather summarily. Rather than reopen it, let me take another approach.

When &S{} was added to the language as a way to construct a pointer to a composite literal, it didn't quite feel right to me. The allocation was semi-hidden, magical. But I have gotten used to it, and of course now use it often.

But it still bothers me some, because it is a special case. Why is it only valid for composite literals? There are reasons for this, which we'll get back to, but it still feels wrong that it's easier to create a pointer to a struct:

p := &S{a:3}

than to create a pointer to a simple type:

a := 3
p := &a

I would like to propose two different solutions to this inconsistency.
Now it has been repeatedly suggested that we allow pointers to constants, as in

p := &3

but that has the nasty problem that 3 does not have a type, so that just won't work.

There are two ways forward that could work, though.

Option 1: new

We can add an optional argument to new. If you think about it,

p := &S{a:3}

can be considered to be shorthand for

p := new(S)
*p = S{a:3}

or

var _v = S{a:3}
p := &_v

That's two steps either way. If we focus first on the new version, we could reduce it to one line by allowing a second, optional argument to the builtin:

p := new(S, S{a:3})

That of course doesn't add much, and the stuttering is annoying, but it enables this form, making a number of previously clumsy pointer builds easy:

p1 := new(int, 3)
p2 := new(rune, 10)
p3 := new(Weekday, Tuesday)
p4 := new(Name, "unspecified")
... and so on

Seen in this light, this construct redresses the fact that it's harder to build a pointer to a simple type than to a compound one.

This construct creates an addressible form from a non-addressible one by explicitly allocating the storage for the expression.
It could be applied to lots of places, including function returns:

p := new(T, f())

Moreover, although we could leave out this step (but see Option 2) we could now redefine the & operator applied to a non-addressible typed expression to be,

new(typeOfExpression, Expression)

That is,

p := &expression 

where expr is not an existing memory location is now just defined to be shorthand for

p := new(typeOfExpression, expression)

Option 2

I am more of a fan of the new builtin than most. It's regular and easy to use, just a little verbose.
But a lot of people don't like it, for some reason.
So here's an approach that doesn't change new.
Instead, we define that conversions (and perhaps type assertions, but let's not worry about them here) are addressible.
This gives us another mechanism to define the type of that constant 3:

p := &int(3)

This works because a conversion must always create new storage.
By definition, a conversion changes the type of the result, so it must create a location of that type to hold the value.
We cannot say &3 because there is no type there, but by making the operation apply to a conversion, there is always a defined type.

Here are the examples above, rewritten in this form:

p1 := &int(3)
p2 := &rune(10)
p3 := &Weekday(Tuesday)
p4 := &Name("unspecified")

Discussion

Personally, I find both of these mechanisms attractive, although either one would scratch the itch.
I propose therefore that we do both, but of course the discussion may end up selecting only one.

Template

Would you consider yourself a novice, intermediate, or experienced Go programmer?

I have some experience.

What other languages do you have experience with?

Fortran, C, Forth, Basic, C, C++, Java, Python, and probably more. Just not JavaScript

Would this change make Go easier or harder to learn, and why?

Perhaps a little easier, but it's a niche problem.

Has this idea, or one like it, been proposed before?

Yes, in issue #9097 and probably elsewhere.

If so, how does this proposal differ?

A different justification and a new approach, with an extension of new.

Who does this proposal help, and why?

People annoyed by the difficulty of allocating pointers to simple values.

What is the proposed change?

See above.

Please describe as precisely as possible the change to the language.

See above.

What would change in the language spec?

The new operator would get an optional second argument, and/or conversions would become addressible.

Please also describe the change informally, as in a class teaching Go.

See above.

Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit.

Yes. Don't worry.

Show example code before and after the change.

See above.

What is the cost of this proposal? (Every language change has a cost).

Fairly small compiler update compared to some others underway. Will need to touch documentation, spec, perhaps some examples.

How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?

Perhaps none? Not sure.

What is the compile time cost?

Nothing measurable.

What is the run time cost?

Nothing measurable.

Can you describe a possible implementation?

Yes.

Do you have a prototype? (This is not required.)

No.

How would the language spec change?

Answered above. Why is this question here twice?

Orthogonality: how does this change interact or overlap with existing features?

It is orthogonal.

Is the goal of this change a performance improvement?

No.

If so, what quantifiable improvement should we expect?

More regularity for this case, removing a restriction and making some (not terribly common, but irritating) constructs shorter.

How would we measure it?

Eyeballing.

Does this affect error handling?

No.

If so, how does this differ from previous error handling proposals?

N/A

Is this about generics?

No.

If so, how does this differ from the the current design draft and the previous generics proposals?

N/A

Activity

  1. added this to the Proposal milestone on Apr 19, 2021
  2. seebs commented on Apr 19, 2021

    @seebs
    Contributor

    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.

  3. added
    v2An incompatible library change
    LanguageChangeSuggested changes to the Go language
    on Apr 19, 2021
  4. clausecker commented on Apr 19, 2021

    @clausecker

    Alternatively, what's the problem with adding composite literals of simple types, like int{3}?

  5. JAicewizard commented on Apr 19, 2021

    @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.

  6. faiface commented on Apr 19, 2021

    @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.

  7. eaglebush commented on Apr 19, 2021

    @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()
    }())
  8. peterbourgon commented on Apr 19, 2021

    @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)
    
  9. benhoyt commented on Apr 19, 2021

    @benhoyt
    Contributor

    @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 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.

  10. faiface commented on Apr 19, 2021

    @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.

  11. benhoyt commented on Apr 19, 2021

    @benhoyt
    Contributor

    @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.

  12. clausecker commented on Apr 19, 2021

    @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).

  13. clausecker commented on Apr 19, 2021

    @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.

  14. 281 remaining items

  15. adonovan commented on Oct 27, 2025

    @adonovan
    Member

    All done, in only eleven years since #9097. ;-)

  16. gopherbot commented on Oct 30, 2025

    @gopherbot
    Contributor

    Change https://go.dev/cl/716561 mentions this issue: go/analysis/passes/modernize: newexpr: add //go:fix inline directives

  17. arvenil commented on Feb 11, 2026

    @arvenil

    Hello,

    Change go.dev/cl/704737 mentions this issue: doc/go_spec.html: document new(expr)

    This feature is particularly useful when working with serialization packages such as encoding/json or protocol buffers that use a pointer to represent an optional value,

    Curiosity, why the example insist on json serialization package using pointer to represent an optional value when optional value can now be also achieved with omitzero which lets us get rid of pointer all together?

  18. swtch1 commented on Feb 11, 2026

    @swtch1

    Curiosity, why the example insist on json serialization package using pointer to represent an optional value when optional value can now be also achieved with omitzero which lets us get rid of pointer all together?

    Because 0 != not specified. I can think of a trivial example. Let's say a program says "specify a value to set the gauge".

    • 1 sets the value to 1
    • 0 sets the value to 0
    • Nil does not change the value
  19. adonovan commented on Feb 11, 2026

    @adonovan
    Member

    Curiosity, why the example insist on json serialization package using pointer to represent an optional value when optional value can now be also achieved with omitzero which lets us get rid of pointer all together?

    The answer is in the example:

    type Person struct {
    	Name string   `json:"name"`
    	Age  *int     `json:"age"` // age if known; nil otherwise
    }

    A newborn baby has an Age of zero; an Age of nil means the Person declined to reveal their age. The omitzero tag would cause the baby's known age of zero to be lost during marshaling and unmarshaling. What we want is that the nilness or otherwise of the field is preserved, and that we can distinguish nil from a non-nil pointer to an age of zero.

  20. Merovius commented on Feb 11, 2026

    @Merovius
    Contributor

    FWIW I assumed @arvenil was referring to something like

    type Optional[T any] struct {
        Value T
        Valid bool
    }
    
    func (o *Optional[T]) UnmarshalJSON(b []byte) error {
        if err := json.Unmarshal(&o.Value); err != nil {
            return err
        }
        o.Valid = true
        return nil
    }

    and then adding json:"omitzero" to any Optional[T] field. Which, previously, didn't work, because there was no way to make a field marshal into an omitted field.

    That being said

    […] why the example insist on json serialization package using pointer to represent an optional value […]

    I don't think it insists on anything. If you don't want to use pointers for optionals, you don't have to. But empirically, a lot of packages do and in those cases, new(expr) is useful. It's just an example, no need to read more into it than that.

  21. siutsin commented on Feb 18, 2026

    @siutsin
    Contributor

    In https://cs.opensource.google/go/go/+/refs/tags/go1.26.0:src/builtin/builtin.go;l=223-226, it still mentions:

    // The new built-in function allocates memory. The first argument is a type,
    // not a value, and the value returned is a pointer to a newly
    // allocated zero value of that type.
    func new(Type) *Type
    
  22. gopherbot commented on Feb 18, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/746561 mentions this issue: builtin: update new function comment

  23. gopherbot commented on Feb 18, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/746481 mentions this issue: builtin: document new(T)

  24. gopherbot commented on Feb 18, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/746481 mentions this issue: builtin: document new(V)

  25. aclements commented on Feb 20, 2026

    @aclements
    Member

    Just to close the loop: #77584 tracks the incorrect documentation in the builtin package. Thanks for pointing that out, @siutsin !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

  • Status
    Accepted

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions