-
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: let := support any l-value that = supports #30318
Comments
I think instead we should aim to eliminate redeclaration, which becomes much less compelling if we can get to a smoother error handling model. Won't happen soon though. Remove features rather than add them. |
I think that eliminating redeclaration is a good path, but I'm not sure it affects this proposal. This basically says that you can write // Declare err, assign to s.f and err.
s.f, err := F() |
Why only struct fields?
Removing restrictions can be a net increase in simplicity, if they result in a more uniform application of rules. This proposal would reduce some differences between what can be on the LHS/RHS of |
Yeah, sorry, I oversimplified. Any L-value that works with |
And If redeclaration is removed, then a if var x, y = f(); x == y {
...
} good? Personally, I can accept it. |
@go101 This is not about removing ":=" (which we definitively want to keep), but about the ability to redeclare a previously declared variable (which one can only do using ":="). |
@robpike, how would you mitigate the millions (?) of lines of code that would be broken by eliminating assignment redeclaration? |
@networkimprov, slightly off topic, but when we do remove language features, the plan is outlined in https://github.com/golang/proposal/blob/master/design/28221-go2-transitions.md ... language features can be removed (for a user declaring a certain language version), but we can't change language semantics and silently alter programs. But this isn't a bug about removing language features. This is strictly about increasing the number of programs that are accepted and making |
I think In my mind 'x :=' should be semantic sugar for 'var x ='. Allowing more complex expressions on the LHS only adds to the confusion |
Possible duplicate of #6842 (but #6842 only talks about fields). I don't see this as a feature, but rather something about allowing a more general behavior. The But if this proposal is accepted, we really don't need redeclarations any more, because we can just turn the variables we want to reuse into expressions by enclosing them in parentheses. |
One interesting comment from #6842 worth duplicating here, by @ascheglov:
There is indeed a bit of a scope problem there. |
@josharian, this proposal doesn't change what would happen to What's the scope problem? That it permits assigning to |
Yeah. That’s a significant behavior change from before. (The alternative is to have the assignment to x.f be temporary to that scope, and reverted afterwards, which would just be weird. But would also be analogous in some ways what would happen with a shadowed variable: https://play.golang.org/p/BtdQSLQGh-e.) |
I guess I don't see that as an important or significant behavior change. That's behaving exactly as this bug is about. |
@josharian, you are probably making some assumption about programmer expectations that I can't see (also I don't see any compatibility issue). Obviously in the code: The principle for var x int
x, y := f() // "var x" not allowed here -> just assign to x |
Yes, I was thinking about programmer expectations. I find the |
To a dev used to javascript, these would seem to add an element to a container (EDIT: and declare a variable). I think @beoran has a point.
|
@networkimprov, I don't follow. |
I think he meant that there's a difference between a struct field, a slice index, and a map index on the left-hand side of a I don't think it makes a whole lot of difference, however. The three cases would, I assume, work exactly the same way with The JavaScript reference is probably in reference to the fact that, in JavaScript, objects are actually maps with a few extra features and you can assign to an array element that doesn't exist yet, which automatically fills in the rest of the array. If you tell someone who's used to JavaScript that |
@bradfitz, mixing assignment and declaration can be confusing. The assignments in the stmts I listed could appear to be declarations affecting a container. It's allowed for |
With this proposal, it's obvious from visual inspection that the proposed new LHS forms are not declarations if they have any punctuation at all. |
Personally, I love this idea and think it makes the code more readable.
now becomes
I think this behavior is predictable, readable, and expected. Given that we can already redeclare/reassign in other more limited circumstances. It would be clear to the reader that something on the left is being declared as we are using It would also be clear that s.x (or any other more complicated expression) is being reassigned. IMO, this allows more code to be written using an expected go idiom. |
This problem has a nice solution somebody proposed in 2010, in the previously mentioned issue: #377 (comment) func foo() {
var t struct { i int }
t.i, :x = 1, 2
...
} |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as resolved.
This comment was marked as resolved.
There's just one thing that I dislike, here the example: type A struct {
x int
}
func myErrorFunc() (int, error) {
return 0, fmt.Errorf("oups")
}
func doSomething(a *A) error {
a.x, err := myErrorFunc()
if err != nil {
return err
}
return nil
}
func main() {
a := A{x: 42}
err := doSomething(&a)
if err != nil {
// do something with a.x but... it was changed and we lost our data...
}
} To fix this issue, we could do: if tempX, err := myErrorFunc(); err != nil {
return err
} else {
a.x = tempX
} We'll finally fallback to the old syntax or create temporary structs/slices/maps to avoid altering data in case of errors... |
@scorsi If you assign a value to a variable, you will lose the old value. I don't see how that's specific to this proposal. x := 42
var err error
if x, err = strconv.Atoi("bad"); err != nil {
// do something with x but... it was changed and we lost our data...
} |
This seems like a popular issue. Do folks have examples of real world code that this would benefit? I frequently need to use My experience is most multi-valued functions include a return value that provides information about the other returned values; e.g., an I'm curious to hear about others' experiences here, and any common use cases I'm not aware of. -- From an implementation point of view, this seems easy enough. I haven't seen any mention of what should happen if a redeclared variable appears in an LHS expression. For example:
Does this program compile? If so, what does it print? My assumption would be LHS expressions should be evaluated in the same scope as RHS expressions (i.e., based on identifier bindings before they're re-bound). That is, it would be equivalent to:
and thus print |
@mdempsky It comes up often when parsing something into a struct. If an error is encountered, the whole thing is thrown away anyway. func readThing(r *bufio.Reader) (*Thing, err) {
var t Thing
if t.A, err := readA(r); err != nil {
return nil, err
}
if t.B, err := readB(r); err != nil {
return nil, err
}
return &t, nil
} |
IMHO, that's the most important problem this proposal would solve (more details above; if I misunderstood what you mean, perhaps you could provide an example). file := os.Stdin
if arg != "-" {
(file), err := os.Open(arg)
if err != nil {
//...
}
defer file.Close()
} It is also a better alternative to the infamous "redeclaration" mechanism of a, ok := f()
if !ok {
//...
}
b, (ok) := g() (Again, there is nothing special about parenthesized identifiers, they already work on the LHS of an
It happens to me from time to time. @icholy use case is a good example. Sometimes assigning to a field/element before checking the error is not a problem. And there are multi-valued functions that always succeed.
I don't see a reason for compiling to fail, and I agree with your assumption. It seems consistent with existing evaluation rules. |
@pam4 does that syntax introduce any parsing ambiguities? |
Thanks for the example use cases.
No, sorry, I just missed that detail. So to restate this: This proposal is about extending the LHS of Assignment statements already allow LHS expressions to be parenthesized; e.g., I think that makes sense. I also agree it would apply to the use case I mentioned. So instead of writing something like:
I could write:
That looks pretty funny to me at the moment, but maybe it would grow on me. (I've always preferred the --
I'd have to try actually changing one of the parsers to be sure, but I don't immediately see any reason this proposal would cause problems. |
Change https://golang.org/cl/250037 mentions this issue: |
Yeah, no parser ambiguity from this proposal. The Go parsers already handle I uploaded a super rough proof-of-concept CL of the current proposal in case anyone wants to play with the idea and maybe demonstrate more real world use cases. It has cmd/compile and go/parser support, so "go fmt" and "go build" should work. It doesn't have go/types support yet, so "go vet" (and thus "go test") will fail. |
Having to do
instead of
breaks my heart. for anyone wanting another use case |
@Hetrafi In your first example you have a type assertion to I don't think those two programs would be equivalent under this proposal. I'm also inclined to say they should not be equivalent. |
The problem is that that shorter one modifies the On a side note, I usually do email, ok := info["email"]
if !ok {
return ...
}
user.Email = email for that situation. It puts Edit: I still think that the best solution to this is to separate the declaration from the assignment. Right now you have two options, only assign via |
(Pulling this specifically out of #377, the general
:=
bug)This proposal is about permitting a struct field (and other such l-values) on the left side of
:=
, as long as there's a new variable being created (the usual:=
rule).That is, permit the
t.i
here:This should be backwards compatible with Go 1.
Edit: clarification: any l-value that
=
supports, not just struct fields./cc @griesemer @ianlancetaylor
The text was updated successfully, but these errors were encountered: