Open urandom opened 5 years ago
As example we can have:
type T struct {
i string,
i2 int,
}
type T2 tuple (string,int)
func A() (string,int) {...}
And in another function:
func main() {
var a T
var b T2
a = A() //valid, with this proposal
b = A() //valid, with another proposal
d,c := A() //valid, currently
}
Another proposal is: https://github.com/golang/go/issues/32941 We can add the tuple type, all this conversions, return type will still be multiple return,but tuple will be constructed in place, then converted to the struct(or just saved).
However this is related features, i don't see any valuable benefits of adding these features aside.
There is an interesting duality for function arguments: today it is possible to pass the results of a multi-result function directly to another function that accepts the same argument list (https://play.golang.org/p/f3kNTppbVMc).
Should it be possible to make such a call by unpacking a struct?
If so, arguably that should require explicit syntax — what would it look like?
There is an interesting duality for function arguments: today it is possible to pass the results of a multi-result function directly to another function that accepts the same argument list (https://play.golang.org/p/f3kNTppbVMc).
Should it be possible to make such a call by unpacking a struct?
If so, arguably that should require explicit syntax — what would it look like?
Technically, yes, but I don't think that it's good idea. Readability will be almost lost(due to required declaration reading).
Personally I can't imagine syntax.
This proposal does not discuss assignability, only convertibility. The example @tema3210 would actually be invalid, as the proposal currently stands.
That being said, I don't see anything preventing a future discussion on assignability. Nevertheless, as for a hypothetical expansion syntax, why not just use ...
?
Should it be possible to make such a call by unpacking a struct?
If so, arguably that should require explicit syntax — what would it look like?
I was proposing to @griesemer that maybe we also use ...
to unpack a struct in a call:
type Pair { x, y int }
func f(a, b int) {}
func main() {
var p Pair
f(p...)
}
Or even:
s := struct{_ int; _ int; _ string}{1, 2, "foo"}
x, y, z := s...
And maybe using struct(...)
to convert an arbitrary function call return (or literals too?) into an unnamed struct:
func f() (int, int, string) { return 1, 2, "foo"}
func g() (x int, y int, N string) { return 1, 2, "foo"}
func main() {
fmt.Printf("%T\n", struct(f())) // struct { _ int; _ int; _ string }
fmt.Printf("%T\n", struct(g())) // struct { x int; y int; N string }
y := struct(1, 1.0, "foo")
fmt.Printf("%T\n", y) // struct { _ int; _ float64; _ string }
}
But then you have issues of naming the struct fields. If you converted an interface method call into a struct, you'd need to use the name of the interface method's named return values (which often aren't named, or are named all lowercase, so the struct field would have unexported fields).
(Braindump from me + @griesemer)
Suppose that instead of Pair(F())
we change the rules of composite literals so that a function call with multiple results can be used, and treated as though the results appeared in the composite literal as separate values.
That is, in the original example, instead of
go func() { c <- Pair(F()) }()
we permit
go func( { c <- Pair{F()} }()
This would be permitted for slice/array composite literals as well. (It wouldn't work for maps as there would be no way to specify both the key and the value.)
This seems like a relatively simple extension to the way that functions with multiple results can be used today.
Any reason not use:
func NewPair(i int, s string) Pair {
return Pair{i, s}
}
go func() { c <- NewPair(F()) }()
@egonelbre Of course this is an option. But I think the point the proposal author is trying to make is that this extra work shouldn't be required: "The goal of this proposal is to allow easy conversion from a function return to a struct with the same field types and order." (https://github.com/golang/go/issues/33080#issue-467483332)
Regarding https://github.com/golang/go/issues/33080#issuecomment-539713498, to clarify: Like for function invocations f(g()
where g
returns multiple results and f
accepts multiple results, if S
is a composite literal type (struct, array, or slice) we would only allow S{g()}
and not S{a, b, g(), c, d}
or S{g(), g()}
.
Why not specify the struct field that function assigns:
Pair{ i, s: F() }
type Point2D struct { x int, y int }
// ignore z
Point2D{ x, y, _: Point3D() }
Hi, we're sorry this issue has been open for so long. In an attempt to reduce backlog of language change proposals, we're trying out a new template describing them. Would you mind filling out the template at https://go.googlesource.com/proposal/+/bd3ac287ccbebb2d12a386f1f1447876dd74b54d/go2-language-changes.md .
When you are done, please reply to the issue with @gopherbot
please
remove
label
WaitingForInfo
.
Thanks!
Would you consider yourself a novice, intermediate, or experienced Go programmer? Experienced
What other languages do you have experience with? C, Java, Kotlin, Rust, Python, Perl to name a few
Would this change make Go easier or harder to learn, and why? It won't influence the learning curve
Has this idea, or one like it, been proposed before? Not to my knowledge. Though an idea to turn the return tuple into a proper type was.
Who does this proposal help, and why? This proposal mainly targets people working with channels, and goroutines that may produce errors (or other tuples), and readers of such code. It will help reduce the boilerplate around passing the valid data and the error across the channel. The proposal is generic enough that there may be other benefactors.
Is this change backward compatible? Yes
Show example code before and after the change. Before:
type Pair struct {
i int
s string
}
func F() (int, string) { ... } func G() chan { Pair c := make(chan Pair) go func() { count, id := F() c <- Pair{count: count, id: id} }() return c }
After:
```go
type Pair struct {
i int
s string
}
func F() (int, string) { ... }
func G() chan {
Pair c := make(chan Pair)
go func() { c <- Pair(F()) }()
return c
}
What is the cost of this proposal? (Every language change has a cost).
How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected? I assume gopls would need the most change, as it would need to suggest the function call during completion. I'm not sure how it affects other tools.
What is the compile time cost? I assume the compile time cost to be negligible, and equal to any other conversion operation.
What is the run time cost? I assume it shouldn't have any run time cost.
Can you describe a possible implementation? This is all an assumption: Parsing would not need to be changed. During type checking, the call operation will to have the correct ctxMultiOK if being converted to a struct with more than one field. The convertop function will need to be extended so that if the dst.IsStruct is true, the src type is transformed to a TSTRUCT Etype with the fields being the list of return types, before calling IdenticalIgnoreTags. At some place after that, the ast needs to be transformed to insert a statement that assigns the call returns to variables, and the the conversion expression will need to be transformed into a struct literal expression using the temporary variables (there's probably a more intelligent way of doing this).
Do you have a prototype? (This is not required.) No
How would the language spec change? The Expressions -> Conversions block would need to be extended to describe the functionality
Orthogonality: how does this change interact or overlap with existing features? It's an additional rule to the already existing conversion expression.
Is the goal of this change a performance improvement? No
Does this affect error handling? Possibly, as the main drive behind it is passing errors as well as other values through a channel
If so, how does this differ from previous error handling proposals? Its a completely different part of error handling, not usually discussed.
Is this about generics? No
@gopherbot please remove label WaitingForInfo
Would this change make Go easier or harder to learn, and why?
It won't influence the learning curve
Adding anything to the language means there's more to learn. Maybe new users won't write that code initially, but they'll eventually want to read & understand other people's code that uses such new features.
Would this change make Go easier or harder to learn, and why?
It won't influence the learning curve
Adding anything to the language means there's more to learn. Maybe new users won't write that code initially, but they'll eventually want to read & understand other people's code that uses such new features.
My understanding of that question is that since this builds on an existing feature, it won't make learning Go harder. It adds more, but doesn't make things harder to learn, a new user, knowing that you can convert between types, will know what the new code does without knowing the new rule. Though it might be possible I've misunderstood the question.
This is essentially @bradfitz's https://github.com/golang/go/issues/33080#issuecomment-534759183 but split into two parts and with a solution for field naming:
Introduce a new way for writing struct types without specifying field names. In struct (int, string)
, each field is automatically named Fn where n is its position in the definition. So struct (int, string)
is 100% sugar for/completely identical to
struct {
F0 int
F1 string
}
This variant of struct declaration is a tuple type without introducing a new kind of type, just a convention and a shorthand. More important here, it gives a way to talk about structs with unspecified field names.
Introduce a new builtin tuple
. It creates and populates a struct without specified field names based on its arguments: tuple(false, 3.14)
has type struct (bool, float64)
.
Because it's a function, the usual rules apply so, for func f() (T, error)
, tuple(f())
has type struct (T, error)
.
With those and ...
for struct unpacking, you can write:
ch := make(chan struct (T, error), 1)
ch <- tuple(f())
v, err := <-ch...
This could also be solved by generics.
package tuple
type Len2(type T0, T1) struct {
F0 T0
F1 T1
}
func Pack2(type T0, T1)(f0 T0, f1 T1) Len2(T0, T1) {
return Len2{f0, f1}
}
func (t Len2(T0, T1)) Unpack() (T0, T1) {
return t.F0, t.F1
}
This can be easily generated from 2 to some n large enough for most reasonable cases.
With that the previous code example is
ch := make(chan tuple.Len2(T, error), 1)
ch <- tuple.Pack2(f())
v, err := (<-ch).Unpack()
That's almost as good as native syntax. It's more verbose and the arity gets brought up a lot, which is annoying but probably fine.
The only language change required is generics but ...
for structs would obviate the Unpack method and allow aStructType{aTupleValue...}
.
There's another language improvement that would help out this use case a little bit. Rust, TypeScript, Haskell, and other languages allow for specifying just the field name and then automatically using an in scope variable with the same name as the value. So a snipped from the example would become:
count, id := F()
c <- Pair{count, id}
Not a huge improvement for this example, but when these patterns are allowed for both structuring and de-structuring it can add up in a program in a lot of places like this and the nice thing is it doesn't negatively affect readability.
For that to work there'd need to be some way to disambiguate it from sequentially filling the fields. In some other thread I used Pair{:count, :id}
.
As far as I can tell from a first reading, none of the suggestions here consider backwards compatibility.
In the current language, it is always backwards-compatible to add a comparable field to a struct, in the sense that all existing code will continue to compile. There are two exceptions to this:
Untagged struct literals. These are explicitly exempted by the Go 1 compatibility promise. There is also a vet check for them, so people generally don't use them. It's always easy to switch to tagged literals.
If the struct has only exported fields, clients can define their own identical struct type and convert between the two. But nobody ever does this, because why would you?
Some proposals here change the calculus. The one that lets you write a struct conversion that takes multiple return values will make struct conversion much more common, so if your package defines
type S struct { Int int; Err error }
you won't be able to add a field to that because clients might be using it to convert calls to strconv.Atoi
.
The proposal for S{F()}
puts untagged literals back in play, in a way where there is no easy rewrite to a tagged form.
Allowing f(s...)
where s
is a struct has the same issue.
Any syntax that takes all struct fields together and translates them to another form—args, return values, whatever—will affect backwards compatibility.
I believe we should seriously consider the ideas suggested here. I filed #64613 which is a restatement of the best ideas in this proposal.
This comes from a conversation in #32941
The goal of this proposal is to allow easy conversion from a function return to a struct with the same field types and order.
This would allow to more easily pass a function result to a channel, without having to manually create temporary variables and filing them up in a struct before it is sent thorough a channel.
Example :
Like converting between structs, tags should be ignored