-
Notifications
You must be signed in to change notification settings - Fork 17.6k
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: allow conversion between return types and structs #33080
Comments
As example we can have:
And in another function:
Another proposal is: #32941 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? |
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 |
I was proposing to @griesemer that maybe we also use 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 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 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:
|
@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." (#33080 (comment)) |
Regarding #33080 (comment), to clarify: Like for function invocations |
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 Thanks! |
Experienced
C, Java, Kotlin, Rust, Python, Perl to name a few
It won't influence the learning curve
Not to my knowledge. Though an idea to turn the return tuple into a proper type was.
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.
Yes
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: 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
}
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.
I assume the compile time cost to be negligible, and equal to any other conversion operation.
I assume it shouldn't have any run time cost.
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).
No
The Expressions -> Conversions block would need to be extended to describe the functionality
It's an additional rule to the already existing conversion expression.
No
Possibly, as the main drive behind it is passing errors as well as other values through a channel
Its a completely different part of error handling, not usually discussed.
No |
@gopherbot please remove label WaitingForInfo |
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 #33080 (comment) 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 {
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 Because it's a function, the usual rules apply so, for With those and 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 |
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 |
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:
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
you won't be able to add a field to that because clients might be using it to convert calls to The proposal for Allowing 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
The text was updated successfully, but these errors were encountered: