Description
Before Go 1 :=
would only work if every name on the lhs had not been declared in the current scope. This made it hard to write common code like
x, err := f()
//...
y, err := g()
Either y
would require an explicit declaration with var
or there would need to be an ugly workaround such as
y, err2 := g()
err = err2
Among other peccadilloes this meant copying a bit of valid code from one place to another could make it invalid and it would need to be rewritten.
This made :=
much less useful than it could be so the current behavior of implicitly reusing existing names defined in the current scope was introduced and erroring out if none of the names were new.
This made :=
much more useful, but has made it too easy to shadow variables in outer scopes.
When it was introduced, there was much discussion in the mailing list thread announcing the change (apologies but I cannot find a link) for a way to explicitly annotate the names to reuse such as
y, ^err := g() // mnemonic for "reusing variable defined above"
This was dismissed—if I recall correctly (again apologizes for not being able to track down the original thread)—because it would mean that copying a bit of valid code from one place to another could make it invalid, as annotations would need to be added or removed depending on the new context.
However, this is subtly true with the current behavior :=
depending on the names involved and what is or is not in the current scope, leading to bugs caused by accidental shadowing that are hard to track down due to the implicit nature of what is shadowed when. This is particularly vexing with named returns.
With explicit annotation it would still be possible to accidentally shadow a variable, but it would be easier to see what was happening at a glance and possibly easier to detect mechanically.
I think the idea of explicit annotation should be revisited for Go 2. I propose the following semantics
x, y, z := f()
follows the pre-Go 1 semantics—all names must be unbound in the current scopex, ^y, ^z := f()
reuses y and z as defined in the current scope or an outer but non-global scope†- annotating a name that does not exist is an error
- at least one name on the lhs must not be annotated
† making it incompatible with reusing variables in package global scope is a bit uneven but seems a wise safety precaution
This should be entirely go fix
-able as the correct annotations to mirror the Go 1 semantics could be applied mechanically.