Skip to content
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 · 38 comments

Comments

@chai2010
Copy link
Contributor

@chai2010 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
Copy link
Contributor

@cznic 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
Copy link
Contributor Author

@chai2010 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
Copy link
Contributor

@cznic 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
Copy link
Contributor Author

@chai2010 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
Copy link
Contributor

@cznic 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
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Nov 13, 2014

Comment 6:

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

@chai2010
Copy link
Contributor Author

@chai2010 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
Copy link

@mikespook 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
Copy link
Contributor

@rsc 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
Copy link
Contributor

@ianlancetaylor 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
@cznic
Copy link
Contributor

@cznic 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
Copy link

@creker 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
Copy link
Contributor

@cznic 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
Copy link

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

@jaeyeom
Copy link

@jaeyeom jaeyeom commented Jun 25, 2019

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

It's not silly at all. It's painful enough, so protobuf package does have those helper functions, too. I hope &v or &T(v) can be added to the spec (and possibly remove new keyword.)

https://godoc.org/github.com/golang/protobuf/proto

@icholy
Copy link

@icholy icholy commented Jun 25, 2019

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

@bcmills can you elaborate? I didn't think there was any "address of copy" syntax.

@bcmills
Copy link
Member

@bcmills bcmills commented Jun 25, 2019

@icholy, the syntax

&(*x)

today evaluates to the same value as x (https://play.golang.org/p/4GS5_Z9B3HN).

It would be confusing for

&(*x+1)

to suddenly have a dramatically different aliasing behavior — changing from allocating a new value to aliasing an existing one — simply because the +1 is added or removed from the expression.

@preciselytom
Copy link

@preciselytom preciselytom commented Jan 24, 2020

This would make my life easier and my code cleaner. There are libraries that use *string, *uint64 etc extensively as "optional values" in structs and function arguments, and I always end up writing "helper functions" like this to be able to specify a literal:

func stringPtr(s string) *string {
    return &s
}
@beoran
Copy link

@beoran beoran commented Feb 21, 2020

I came here from #37302, and this really an annoyance in Go. Everyone is writing these small helper functions to transform a constant into a pointer, such as intPtr(v int) *int { return &v}, etc.

These functions get copied around everywhere, they are even in the Go standard library! Mostly they are used for used in testing: e.g.: src/encoding/asn1/asn1_test.go, src/encoding/json/decode_test.go, etc, and the definition is often redundant, and even named differently.

In stead of these functions, we could really use this &type() syntax to get rid of these little helper functions everywhere, even in the standard library.

@benhoyt
Copy link
Contributor

@benhoyt benhoyt commented Feb 21, 2020

How do we move this proposal forward, either to decide for or against it? Specifically I'm referring to the &T(v) syntax -- I agree with @ianlancetaylor that new(T, v) is unnecessary. It seems to me that phrases like &int(42) are clear, unambiguous, and a lot of people would find this syntax useful to avoid an awkward multiline thing with a named temporary variable. This is evidenced by all the libraries (serialization libraries particularly) that have some form of IntPtr style functions in them.

As Ian summarized earlier:

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.

(Separately we could consider &function(...), e.g., &time.Now(), but that seems less generally useful and could be considered separately -- see also #22647.)

What's the best way to follow through with the &T(v) discussion, and come to agreement -- either to include this in a subsequent version of Go, or decide that it's not worth it? A formal proposal, with references to use cases / experience reports? A discussion on golang-dev?

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Feb 22, 2020

I don't see any clear consensus in the discussion above. Although the emoji voting on &T(v) is good, there are quite a few comments suggesting other approachs.

It's also worth considering that the generics design draft permits writing

package addr

func P(type T)(v T) *T {
    return &v
}

which can then be used as

    p1 := addr.P(1) // p1 has type *int
    p2 := addr.P(iint64(2)) // p2 has type *int64
    p3 := addr.P("hi") // p3 has type *string
    p4 := addr.P(time.Now()) // p4 has type *time.Time

This has some advantages, in that it doesn't require a new language feature (well, doesn't require a language feature other than generics), and it doesn't require writing the type when that is not needed.

So personally I would be inclined to wait until we have generics to see if an approach like that seems sufficient.

@benhoyt
Copy link
Contributor

@benhoyt benhoyt commented Feb 22, 2020

That's reasonable thanks. I'm content to wait on that, assuming that generics design is going somewhere in the next couple of years. :-)

@networkimprov
Copy link

@networkimprov networkimprov commented Feb 22, 2020

@ianlancetaylor, you prefer a third syntax to create pointers?

p := &v        // for any variable
p := &T{...}   // for composite types; consistent with &v
p := new(T)    // for any type, but mostly primitives
p := addr.P(v) // for any value, but mostly constants

That adds to the education/cognitive burden which is often raised re language proposals.

&T(v) is consistent with current syntax; that should count for a lot.

@griesemer any thoughts?

@griesemer
Copy link
Contributor

@griesemer griesemer commented Feb 22, 2020

@networkimprov As @ianlancetaylor also mentioned earlier, no clear consensus has emerged yet. I agree that it would be nice to resolve this but there's no urgency. I'd be happy to wait for a truly compelling solution or a strong reason to move forward with one of the existing suggestions. As far as I can tell, this is not blocking anything.

And, just to be clear, adding a third syntax seems not a good plan. We want to make things simpler and clearer, not more complicated.

@ConradIrwin
Copy link
Contributor

@ConradIrwin ConradIrwin commented May 22, 2020

I want to float another potential approach here.

Given that taking the address of arbitrary expressions seems confusing based on the existing semantics, why don’t we reduce the scope and instead make two specific changes:

  1. Add function return values (when the function returns a single value) to the list of addressable things, which would allow for: &a(), or &time.Now()
  2. Add typecast results to the list of addressable things, which would allow &int(1), &string(“a”).

I think this covers most of the use-cases while requiring minimal language spec changes, and without introducing new potentially ambiguous cases. I also think the meaning of the syntax is easy to understand for a reader as it re-uses the & operator to create a pointer. The main disadvantage is that it may lead people to assume that &1 should work without the typecast, this can be solved by updating the compilation error you get to say “cannot take the address of 1. To take the address use an explicit cast: &int(1)”

The main time that I want these operators to work is when I’m constructing an object literal and the object has pointer-valued fields (this happens mostly today when modeling SQL tables with NULL-able columns, but also in a a variety of APIs that distinguish between the absence and presence of a variable). As a new go programmer I used to create temporary variables, but I have since changed and have written a set of helper functions (similar to #38298)

I decided against the following, because it seemed more complicated, but we could instead of 2. above, do: 2. extend the constant behavior so that & on a constant literal gives you a constant that has a default type of a pointer to the literal’s default type and which gets its definite type from the context in the same way constants do. That would allow i := &1 to set i to a *int; but also allow var x *float64 = &1. The main advantage of this in my mind is that &1 is shorter than &int(1), but the disadvantage is that you might begin to expect &(2*math.PI) to work, which it wouldn’t. &float64(2*math.PI) would work in the proposal above.

I also decided against adding a pointers package (like the other proposal) because this change would work for any type (importantly for me time.Time) and also help beginners who are confused by why they can’t take the address of a return value (me included). And against trying to add another kind of optional syntax to go, pointer values are a good conceptual match for optionals and I don’t think we need more syntax for similar things. (It also seems like a go2 concern!)

@Thor-x86
Copy link

@Thor-x86 Thor-x86 commented Nov 18, 2020

I came here from #42690 also same reason of @beoran problem. I wrote library that helps handle nil and non-nil value for a variable with homogenous type. However, it doesn't add significant benefit due to prohibition of implicit conversion. Thus, I should workaround code like this:

myString := "Hello Gophers!"
myNullableString := nullable.NewString(&myString)

instead of directly assign pointer as parameter like this

myNullableString := nullable.NewString(&string("Hello Gophers!"))

and speaking of simplicity, this approach is relatively simple than declaring "home variable" first. It's less line count and seems understandable for newbies. Moreover, I didn't see any syntax collision here with AND operand (&).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.