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: add &T(v) to allocate variable of type T, set to v, and return address #9097

Open
chai2010 opened this Issue Nov 13, 2014 · 26 comments

Comments

Projects
None yet
@chai2010
Copy link
Contributor

chai2010 commented Nov 13, 2014

1. improve new func (by Albert Liu @ jpush)

func new(Type, value ...Type) *Type

2. support &Type(value) grammar

Examples:

px := new(int, 9527)
px := &int(9527)

Some discuss:
https://groups.google.com/d/msg/golang-nuts/I_nxdFuwAmE/jNObXNDy5bEJ
@cznic

This comment has been minimized.

Copy link
Contributor

cznic commented Nov 13, 2014

Comment 1:

- What is the ... in the proposed signature of new good for? new returns a pointer to
only one value.
- Having both &T{} and &T() do the same thing would be surprising at minimum.
- Allocating structs is common, allocating non-struct types is not.
@chai2010

This comment has been minimized.

Copy link
Contributor

chai2010 commented Nov 13, 2014

Comment 2:

#1
1. value are optional, so we need ... type:
px := new(int)
px := new(int, 123)
px := new([]int, 1, 2, 3)
px := new([]int, x...)
2. &T{} donot support &int{}
3. Please see the discuss.
@cznic

This comment has been minimized.

Copy link
Contributor

cznic commented Nov 13, 2014

Comment 3:

"px := new([]int, 1, 2, 3)"
Ah, so it's meant to support even slices? But using a different syntax than a slice
literal has ({[key:] value, ...})? But without the possibility to set len and cap? How
it's supposed to handle maps? `pm := new(map[t]u, 1, 2, 3)`? What is the key and what is
the value? Or map types, as an exception, do not qualify as a 'Type'? Etc.
I think this all shows how much of a bad idea this proposal is.
@chai2010

This comment has been minimized.

Copy link
Contributor

chai2010 commented Nov 13, 2014

Comment 4:

For map:
pmap := new(map[string]int, map[string]int{
    "A": 1,
    "B": 2,
})
For map slice:
pmaps := new([]map[string]int,
    map[string]int{
        "A": 1,
        "B": 2,
    },
    map[string]int{
        "A": 1,
        "B": 2,
    },
)
@cznic

This comment has been minimized.

Copy link
Contributor

cznic commented Nov 13, 2014

Comment 5:

So for slices a list o values is used (#2)
        px := new([]int, 1, 2, 3)
But for map types it uses a composite literal (#4)
        pmap := new(map[string]int, map[string]int{
            "A": 1,
            "B": 2,
        })
Which case is the norm and which is the exception? Why not in the slice case write
analogically
        px := new([]int, {1, 2, 3}) // ?
It also supports the existing key: val thing
        px := new([]int, {1, 42: 2, 3})
IOW, we're back to the "why the ... "?
If the proposal would be accepted, which I hope is not going to happen, I think that it
would have to be
        new(T, optExpr) // 1 is a literal as is {1, 2, 3}, etc.
Where optExpr is optional, similarly to
        make(T, optExpr1, optExpr2) // [0]
BTW, please let's not forget - the best feature of Go is its lack of "features".
  [0]: http://golang.org/ref/spec#Making_slices_maps_and_channels
@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Nov 13, 2014

Comment 6:

Labels changed: added repo-main, release-none, languagechange, go2.

@chai2010

This comment has been minimized.

Copy link
Contributor

chai2010 commented Nov 14, 2014

Comment 7:

#5
Sorry, i made a misstake. I only hope these two `new` type:
    func new(Type) *Type
    func new(Type, value Type) *Type
Not include this `new` type:
    func new([]Type, values ...Type) *[]Type
Beause it will cause this confused code:
    px := new([]int, []int{1})
    px := new([]int, 1) // like new([]int, 1, 2, 3)
Some examples:
    px := new(int)
    px := new(int, 123)
    px := new([]int)
    px := new([]int, []int{1, 2, 3})
    px := new(map[string]int)
    px := new(map[string]int, map[string]int{
        "A": 1,
        "B": 2,
        "C": 3,
    })
@mikespook

This comment has been minimized.

Copy link

mikespook commented Nov 14, 2014

Comment 8:

Here's a proposal witch should be related with this issue.
I'd like you to review and make some comments.
https://docs.google.com/document/d/111YaXFZeJbJ9DhOF69CvvFV49YTkUKpIRKiS42woMak/edit?usp=sharing

@bradfitz bradfitz removed the new label Dec 18, 2014

@rsc rsc added this to the Unplanned milestone Apr 10, 2015

@rsc rsc removed release-none labels Apr 10, 2015

@rsc rsc changed the title spec: suggest improve new function and add &T(x) grammar proposal: spec: add new(int, 5) and &int(5) to allocate and initialize 5 Jun 16, 2017

@rsc

This comment has been minimized.

Copy link
Contributor

rsc commented Jun 16, 2017

See also #19966.

@rsc rsc changed the title proposal: spec: add new(int, 5) and &int(5) to allocate and initialize 5 proposal: spec: add new(int, 5) and &int(5) to allocate and initialize int 5 Jun 16, 2017

@ianlancetaylor ianlancetaylor modified the milestones: Unplanned, Proposal Jan 3, 2018

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Jan 3, 2018

I don't see why we need both new(int, 5) and &int(5). It's true that today, if T is a composite type, we permit both new(T) and &T{}. The fact that we permit both means that essentially nobody ever writes new(T) for a composite type T. If we permit &int(5), then nobody will ever write new(int, 5). So, if anything, if we adopt &int(5), we should consider removing new entirely.

For this kind of thing it's interesting to consider the type []interface{}. With the syntax proposed here, &[]interface{}{nil} would return a slice of one element whose value is nil, and &[]interface{}(nil) would return a nil slice of type []interface{}. That in itself is a reason to prefer () here, while reserving {} for composite types.

I think the proposal here should be to add to the language the expression &T(v), for any type T, for any value v assignable to T. This expression will allocate a new variable of type T, set it to v, and return its address.

@ianlancetaylor ianlancetaylor changed the title proposal: spec: add new(int, 5) and &int(5) to allocate and initialize int 5 proposal: spec: add &T(v) to allocate variable of type T, set to v, and return address Jan 3, 2018

@alercah

This comment has been minimized.

Copy link

alercah commented Feb 13, 2018

I like that.

@tv42

This comment has been minimized.

Copy link

tv42 commented Feb 21, 2018

If &T(v) goes in, perhaps func foo() T should allow &foo() and not just &T(foo()), to get a *T.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Feb 21, 2018

@tv42 If I understand you correctly, that is not this proposal, it is #22647.

@tv42

This comment has been minimized.

Copy link

tv42 commented Feb 21, 2018

@ianlancetaylor That issues seems to contain &foo(), yes. I was brought here mostly by the similarity in syntax between &T(v) and &foo(v), &"bar" is a bit more out there.

@benhoyt

This comment has been minimized.

Copy link
Contributor

benhoyt commented Mar 28, 2018

I think this is a good proposal (see an "experience report" for something similar at #22647), but I'd vote for the simpler &"foo" or &1234 syntax. That to me seems more obvious than the &T(v) syntax, which looks like type coercion or a function call.

The &"foo" style syntax also seems like a natural extension of the existing &T{...} syntax: you construct a thing, then you take the address of it. And my proposal is that it doesn't matter whether that thing is a struct (like now) or an int or string or something else.

This syntax is what I tried when learning Go, as I just presumed you could prefix an expression with & to take the address and the compiler would figure it out (Go's big on "let the compiler figure out whether something needs to be on the heap or the stack"). This is not just me: other people expect this to work too, because of the &T{...} precedent: see one, two, three, four.

The simpler syntax would also work for expressions, like (single-valued) function calls such as &time.Now(), as well as more general expressions like &(x + 1234) -- the latter would just have to be in parentheses for operator precedence reasons. That said, I think such general expressions would be rare, and in practice it would mostly be people taking the address of a constant or function return value.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Mar 28, 2018

&1234 would presumably have the type *int. Sometimes you need, say, int64. So &1234 is not sufficient; there needs to be a way to say: create a variable of type int64 and set it to 1234 and return the address. The proposed &T(v) syntax permits &int64(1234). So it seems to me that we need something like &T(v) regardless.

If we want to permit &v for any expression v, then we can do &int64(v), using a type conversion.

But &v for any expression has some difficulties. Logically it should be possible to take the address of an address expression, which gives us &&v. But that doesn't work because && is an operator with a different meaning.

More importantly, if v is a variable, than &var is quite different from &v where v is an expression other than a variable. &var takes the address of the singular variable var. If called in a loop, it resolves to the same value each time it is executed. &v for a non-variable v allocates a new instance each time, and as such if called in a loop resolves to a different value each time it is executed. That is a rather subtle distinction that seems likely to lead to confusion.

You say above that &"foo" is an extension of &T{...}, but I'm not sure it is. &T{...} is a special case where the type is always required, and, more importantly, which is explicitly defined to allocate a new value each time.

@benhoyt

This comment has been minimized.

Copy link
Contributor

benhoyt commented Mar 28, 2018

Thanks -- that's reasonable, and I concur that &T(v) solves some of those subtle issues. Though I don't think the &&v issue is really a problem, because it'd be really rare, and if you actually needed that you'd just use parens like &(&v).

Still, the &T(v) approach would mean that my original use case, &time.Now(), would be quite clunky: &time.Time(time.Now()). Does it matter that &var returns the same value each time, and &expr does not? We already have that distinction with &var and &T{}, right?

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Mar 28, 2018

Yes, &var and &T{} act differently. This is clearly documented, and they also look different. (There was actually sentiment for a while to change the address-of-composite-literal syntax to be (*T){}, which would be more logical, but in the end we stuck with &T{}.) &var and &1 look a lot more similar, so it's more important to be aware of the fact that they behave quite differently.

I agree that &time.Time(time.Now()) looks clunky. That may be a good reason for us to not change anything here. All of this is just syntactic sugar. It has to be useful and it has to be clear.

@creker

This comment has been minimized.

Copy link

creker commented Mar 28, 2018

It's reasonable that &1 is not enough because we want it to be of specific type and numeric constants are untyped in Go. But why not give the compiler more freedom to derive the meaning from the context?

  1. &"foo" - *string
  2. &time.Now() - *Time
  3. &1 - ambiguous. Compiler could throw an error and you would have to use &int64(1) or something like that. But even in this case compiler could use context to determine the exact type. And if you pass it to a function with interface{} argument or create a variable with := you would still have to use &T(v) syntax.

It's seems to me that there's enough context to implement it properly. Just looking at the code you can easily tell which is which. Nothing is magical or surprising.

@benhoyt

This comment has been minimized.

Copy link
Contributor

benhoyt commented Mar 29, 2018

@creker Numeric constants may be untyped in Go, but when you assign an integer to a variable, it's always type int, like myInt := 1234. So to me it seems obvious that &1234 would mean, unambiguously, &int(1234).

@bcmills

This comment has been minimized.

Copy link
Member

bcmills commented Mar 29, 2018

As much as I loathe C++ “uniform initialization”, it might actually be a good example here.

We could allow &{x} (with or without a type after the &) as a general shorthand for taking the address of an anonymous variable. It's visually distinct from taking the address of an ordinary variable or expression, but visually similar to taking the address of a struct literal.

Examples:

px := &{1234}        // px := new(int);       *px = 1234
px64 := &int64{1234} // px64 = new(int64);    *px64 = 1234
pt := &{time.Now()}  // pt := new(time.Time); *pt = time.Now()
pfoo := &{"foo"}     // etc.

In conjunction with #21496, the only special thing about struct literals would be that we do not duplicate the curly braces:

ps := &SomeStructType{"foo", "bar"}  // ps := new(SomeStructType); *ps = {"foo", "bar"}
@bcmills

This comment has been minimized.

Copy link
Member

bcmills commented Mar 29, 2018

Ironically, there was a golang-nuts thread about &(*x) just today.

I think that supports Ian's argument that the “address of copy” syntax needs to be visually distinct from “address of an arbitrary expression”.

@cznic

This comment has been minimized.

Copy link
Contributor

cznic commented Mar 31, 2018

I'm against this proposal, but should it be accepted, I'd be inclined to just relax the restriction disallowing T in &T{...} to be a simple type like int, string etc. It also solves the untyped constant resulting type problem. &int32{42} or &int64{42} or even &myString{"foo"} are pretty clear about it.

@creker

This comment has been minimized.

Copy link

creker commented Mar 31, 2018

@bcmills I see now why we need distinct syntax but these {} examples look too much like struct initialization in C. It's just confusing why it looks like taking the address of a struct literal when, in fact, it's not struct literal at all.

@cznic

This comment has been minimized.

Copy link
Contributor

cznic commented Mar 31, 2018

It's just confusing why it looks like taking the address of a struct literal when, in fact, it's not struct literal at all.

From a certain point of view, it is. &int{42} can be seen as [a shortcut of] &(&struct{ i int }{42}).i, which works today: https://play.golang.org/p/dsaYvDmfGAH just fine. Analogically for other types, ofc.

@mwielbut

This comment has been minimized.

Copy link

mwielbut commented Dec 24, 2018

I did this enough to create a really simple (and maybe silly) package of helper functions:
https://godoc.org/github.com/mwielbut/pointy

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