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: various changes to := #377

Open
agl opened this Issue Dec 3, 2009 · 66 comments

Comments

Projects
None yet
@agl
Contributor

agl commented Dec 3, 2009

This code is subtly wrong:

func f() (err os.Error) {
  v, err := g();
  if err != nil {
    return;
  }
  if v {
    v, err := h();
    if err != nil {
      return;
    }
  }
}

The := in the if statement causes a new err variable that shadows the 
return parameter.

Maybe doing this should be an error. Maybe return parameters should be 
special so that := doesn't ever shadow them. (I like the latter.)
@peterGo

This comment has been minimized.

Contributor

peterGo commented Dec 3, 2009

Comment 1:

I noticed this situation a while ago. I argued that it conforms to the scope rules,
which are usual and customary.
The first err is declared under rule 4. The second err is declared under rule 5. The
second declaration is the inner declaration, so the inner redeclaration rule applies,
thereby hiding, within its own scope, the first err.
This is the usual and customary behaviour for many languages. Some languages have a
construct which allows a reference in the inner scope to the variable in the outer scope.
The Go Programming Language Specification
Declarations and scope
The scope of a declared identifier is the extent of source text in which the
identifier denotes the specified constant, type, variable, function, or package.
Go is lexically scoped using blocks:
   1. The scope of a predeclared identifier is the universe block.
   2. The scope of an identifier denoting a constant, type, variable, or function
declared at top level (outside any function) is the package block.
   3. The scope of an imported package identifier is the file block of the file
containing the import declaration.
   4. The scope of an identifier denoting a function parameter or result variable is
the function body.
   5. The scope of a constant or variable identifier declared inside a function
begins at the end of the ConstSpec or VarSpec and ends at the end of the innermost
containing block.
   6. The scope of a type identifier declared inside a function begins at the
identifier in the TypeSpec and ends at the end of the innermost containing block.
An identifier declared in a block may be redeclared in an inner block. While the
identifier of the inner declaration is in scope, it denotes the entity declared by
the inner declaration.
@gopherbot

This comment has been minimized.

gopherbot commented Dec 9, 2009

Comment 3 by pshah.foss:

Is there anyway to access the variable in outer scope ?
@rsc

This comment has been minimized.

Contributor

rsc commented Jan 11, 2010

Comment 4:

Issue #514 has been merged into this issue.

@rsc

This comment has been minimized.

Contributor

rsc commented Jan 19, 2010

Comment 5:

This issue now tracks various proposals that have been made, among them:
  * disallow shadowing outer variables
  * allow arbitary expressions on the lhs
  * don't require something new on the lhs
@rsc

This comment has been minimized.

Contributor

rsc commented Jan 19, 2010

Comment 6:

Issue #505 has been merged into this issue.

@rsc

This comment has been minimized.

Contributor

rsc commented Jan 19, 2010

Comment 7:

Issue #469 has been merged into this issue.

@gopherbot

This comment has been minimized.

gopherbot commented Feb 5, 2010

Comment 8 by jesse.dailey:

The go spec for short variable declaration specifically addresses redeclaration, and  
explicitly states that this should not happen.
From the go spec:
"a short variable declaration may redeclare variables provided they were originally 
declared in the same block with the same type"
Right now, you can shadow global variables, and redeclare their type.
"Redeclaration does not introduce a new variable; it just assigns a new value to the 
original."
var someGlobal = "foo";
func someFunc() (int, os.Error) {
  return 1, nil
}
func TestThree(t *testing.T) {
  if someGlobal, err := someFunc(); err == nil {
    // rather than throwing an error, someGlobal will now silently be an int == 1
  }
  // now it will be a string == "foo" again
}
@rsc

This comment has been minimized.

Contributor

rsc commented Feb 5, 2010

Comment 9:

@jesse.dailey: The implementation is in line with the spec.  The proposal is a change 
to the spec.
x := 1
{ 
    x := 2
}
The two x are in different blocks so the sentence you quoted does not apply.
@rogpeppe

This comment has been minimized.

Contributor

rogpeppe commented Mar 26, 2010

Comment 10:

another possibility that i think would be useful:
allow non-variables on the l.h.s. of a := as long
as there's one new variable there.
e.g.
   x := new(SomeStruct)
   x.Field, err := os.Open(...)
i actually think this is less controversial than the original
rule allowing non-new variables - at least it's obvious
at a glance which variables have been declared.
@gopherbot

This comment has been minimized.

gopherbot commented Apr 21, 2010

Comment 11 by ravenstone13@cox.net:

I think the original poster was making a case for special treatment of return
parameters.  In principle I agree with his argument that it would reduce the
potential for a certain class of subtle errors.  The question is whether this
potential benefit is worth introducing a 'special case' into the spec and eventually
into all go compiler implementations.  Since much is being made by go promoters about
it being a 'safe' language I'm leaning towards agreement with OP, ie. no shadows of
return parameters.  I realize this isn't a democracy, it's just my opinion FWIW  :-)
@rsc

This comment has been minimized.

Contributor

rsc commented Apr 26, 2010

Comment 12:

Issue #739 has been merged into this issue.

@gopherbot

This comment has been minimized.

gopherbot commented Nov 20, 2010

Comment 13 by snake.scaly:

I think the OP highlights a more general problem: redeclaring variables from outer
scopes can create subtle, hard to track down errors.
Possible solution: make it an error to redeclare a variable declared in the same
function.
Rationale for this language change:
* A whole class of hard to fix errors is eliminated
* Probably it won't hurt most of existing, correct Go code
* Probably it will highlight bugs or hard-to-maintain spots in the existing code
* Redeclaration of global names is still allowed so that a new version of `import .
"Foo"` package won't hijack your code
* Does not complicate specification
* Does not seem to complicate implementation, at least not much
@mark-summerfield

This comment has been minimized.

mark-summerfield commented Nov 20, 2010

Comment 14:

One thing that could presumably done right now without changing the language is to
provide a warning when shadowing occurs. (Of course then it would be nice to have a
warning level option to give to the compiler so that the warning could be switched off
by people who don't like it.)
@niemeyer

This comment has been minimized.

Contributor

niemeyer commented Nov 23, 2010

Comment 15:

I'd like to introduce one additional proposal for consideration,
which I believe addresses the original problem brought up by the OP,
and which hasn't been covered yet.
What if "=" was allowed to be used to declare variables as well,
but only if at least one of the variables has *been* previously
declared?
In other words, this would be valid:
   a, err := f()
   if err == nil {
       b, err = g()
       if err == nil { ... }
   }
   return err
This would be the exact counterpart behavior to :=, which may only be
used when at least one of the variables has *not* been declared
previously.  It feels like I'd appreciate using this in practice, and
would avoid the errors I've personally found with the shadowing.
How do you all feel about this?
@niemeyer

This comment has been minimized.

Contributor

niemeyer commented Nov 30, 2010

Comment 16:

Another alternative based on the conversation in the mailing list would be to use a
per-variable declaration syntax.
For instance, this:
   a, b := f()
Would be fully equivalent to:
   :a, :b = f()
and a construction in an internal block such as:
   err = f()
might be extended to the following, which is completely clear and unambiguous:
   :a, err = f()
When one doesn't want to redefine err.
One of the things which feels interesting about this proposal is that
it would enable forbidding entirely partial declarations via := if
that's decided to be a good option, without compromising on other
aspects of the language.
@gopherbot

This comment has been minimized.

gopherbot commented Dec 10, 2010

Comment 17 by czapkofan:

Alternative proposals in spirit similar to comment 16, based on ideas expressed in
http://groups.google.com/group/golang-nuts/browse_thread/thread/5f070b3c5f60dbc1 :
Ideas, Variant 1:
  a, (b) := f1()             // redefines b, reuses a
  (a, b), c, (d, e) := f2()  // redefines c, reuses a, b, d, e
  // Flaw example: redundant with "a = f3()":
  (a) := f3()                // reuses a
Ideas, Variant 2:
  (var a), b = f1()           // redefines a, reuses b
  a, b, (var c), d, e = f2()  // redefines c, reuses a, b, d, e
  // Flaw example: redundant with "var a = f4":
  (var a) = f4()              // redefines a
@gopherbot

This comment has been minimized.

gopherbot commented Dec 10, 2010

Comment 18 by daveroundy:

I like this approach:
(var a), b = f1()           // redefines a, reuses b
precisely because it is so close to the already-existing equivalence between
a := f1()
and
var a = f1()
@rogpeppe

This comment has been minimized.

Contributor

rogpeppe commented Dec 10, 2010

Comment 19:

i'm not keen on that, because it's so heavyweight (5 extra characters).
you might as well do
a, nb := f1()
b = nb
@gopherbot

This comment has been minimized.

gopherbot commented Dec 11, 2010

Comment 20 by james@abneptis.com:

I won't re-raise this on the list, but after thinking a few more days, I think my
biggest disagreement with the current implementation allowing (the above given):
func TestThree(t *testing.T) {
  if someGlobal, err := someFunc(); err == nil {
    // rather than throwing an error, someGlobal will now silently be an int == 1
  }
  // now it will be a string == "foo" again
}
Is that the part that is creating the issue "if someGlobal, err := someFunc(); err ==
nil" doesn't /really/ seem to be part of the inner block scope to the reader;
Yes, it's completely expected that loop setup variables would be available within the
scope of the loop, and perhaps even, by default, not available outside of the loop
scope.  BUT, since the "clause" is outside of the braces, I think it's reasonable for a
coder to assume that it has a "middle" scope, that would by default inherit from the
global scope if available, otherwise creating variable solely available to the inner
loop scope.
I realize that's a complex description of the change, but I think if /clauses/ are
solely targeted with the change, we'd minimize the chance for both confusion and bugs
unintentionally introduced.
(And if unchanged, I'd love a compiler warning, but hey, I know that's not in the plans
;) )
@peterGo

This comment has been minimized.

Contributor

peterGo commented Dec 11, 2010

Comment 21:

James,
"A block is a sequence of declarations and statements within matching brace brackets.
Block = "{" { Statement ";" } "}" . In addition to explicit blocks in the source code,
there are implicit blocks:
   1. The universe block encompasses all Go source text.
   2. Each package has a package block containing all Go source text for that package.
   3. Each file has a file block containing all Go source text in that file.
   4. Each if, for, and switch statement is considered to be in its own implicit block.
   5. Each clause in a switch or select statement acts as an implicit block."
Blocks, The Go Programming Language Specification.
http://golang.org/doc/go_spec.html#Blocks
"In some contexts such as the initializers for if, for, or switch statements, [short
variable declarations] can be used to declare local temporary variables."
Short variable declarations, The Go Programming Language Specification.
http://golang.org/doc/go_spec.html#Short_variable_declarations
Therefore, until you can do it automatically in your head, you can simply explicitly
insert the implicit blocks. For example,
var x = "unum"
func implicit() {
    fmt.Println(x) // x = "unum"
    x := "one"
    fmt.Println(x) // x = "one"
    if x, err := 1, (*int)(nil); err == nil {
        fmt.Println(x) // x = 1
    }
    fmt.Println(x) // x = "one"
}
func explicit() {
    fmt.Println(x) // x = "unum"
    {
        x := "one"
        fmt.Println(x) // x = "one"
        {
            if x, err := 1, (*int)(nil); err == nil {
                fmt.Println(x) // x = 1
            }
        }
        fmt.Println(x) // x = "one"
    }
}
@gopherbot

This comment has been minimized.

gopherbot commented Dec 11, 2010

Comment 22 by james@abneptis.com:

Thanks;  It's not so much that I don't understand with it, or even disagree with it; 
It's that it's a frequent source of errors that are hard to physically see (differing
only in colon can have a dramatically different result).
(snip much longer ramble)
I have no problem with
var v; 
func(){ v := 3 }
It's 
foo()(err os.Error){
  for err := bar(); err != nil; err = bar() {
  }
}
being substantially different than
foo()(err os.Error){
  for err = bar(); err != nil; err = bar() {
  }
}
and both being semantically correct.
Essentially, my argument is w/r/t ONLY: "In some contexts such as the initializers for
if, for, or switch statements, [short variable declarations] can be used to declare
local temporary variables";  I would argue that since these are special cases to begin
with, that in multi-variable := usage, resolving those local temporary variables should
be handled via the same scope as the containing block, but stored in the inner scope if
creation is necessary;
I've got no problem with how it works, just been bitten by this more times than I'd care
to admit, and surprised when I'd realized how many others had been as well.
@gopherbot

This comment has been minimized.

gopherbot commented Jan 21, 2011

Comment 23 by ziutek@Lnet.pl:

I think that Go should be explicit language.
I prefer Go:
    ui = uint(si)
than C:
    ui = si
if ui is unsigned and si is signed. Why do we need an implicit behavior of :=?
So if := is the declaration operator it should work exactly like var for all its lhs.
If some of lhs are previously declared in this scope, it should fail - I believe we
should have a separate explicit construction for this case. Proposal from comment 16 is
nice for me:
    :a, b = f()
In above case it doesn't introduce any additional character. In:
    :a, b, :c = f()
it adds only one.
This notation looks good. I can easily determine what's going on.
    a, b, c := f()
should be an abbreviation for:
    :a, :b, :c = f()
With current := behavior I fill like this:
    :=?
I vote for change this emoticon to:
    :=
in the future.
;)
@nsf

This comment has been minimized.

nsf commented Apr 6, 2011

Comment 24:

In fact I think there is a perfect solution in one of the proposals. So, I'll sum up
what I think:
1. Allow arbitrary addressable expressions on the left side of ':='.
2. Allow no new variables on the left side of ':=' (a matter of consistency in the code,
see examples).
3. Use the following rule to distinguish between a need of "declare and initialize" and
"reuse":
If the LHS looks like an identifier, then the meaning is: "declare and initialize".
Trying to redeclare a variable in the current block that way will issue an error.
Otherwise LHS must be an addressable expression and the meaning is: "reuse". Rule allows
one to use paren expression to trick the compiler into thinking that an identifier is an
addressable expression.
Examples:
a, err := A()   // 'a' and 'err' are identifiers - declare and initialize
b, (err) := B() // 'b' - declare and initialize, '(err)' looks like an addressable
expression - reuse
type MyStruct struct {
    a, b int
}
var x MyStruct
x.a, err := A()   // 'x.a' is an addressable expression - reuse, 'err' is an identifier
- declare and initialize
x.b, (err) := B() // 'x.b' and '(err)' are addressable expressions - reuse (special case
without any new variables)
Of course it could be:
x.b, err = B()    // and that's just a matter of preferrence and consistency
Note: My idea is a bit different from proposal above, the following syntax is invalid: 
(a, b), c := Foo()
The right way to do this is:
(a), (b), c := Foo()
Yes, it's a bit longer. But keep in mind that the alternative is typing 'var a Type',
'var b Type'. Using parens is perfectly fine to me for such a complex case.
Also this approach has one very cool property - it almost doesn't alter syntax (allowing
arbitrary addressable expressions on the left side of ':=' is the only change), only
special semantic meaning.
@niemeyer

This comment has been minimized.

Contributor

niemeyer commented Apr 6, 2011

Comment 25:

I'd still prefer
  :a, :b, c = Foo()
But at this point it's really just syntax.  I'd be happy with either approach.
@gopherbot

This comment has been minimized.

gopherbot commented Apr 6, 2011

Comment 26 by ckrueger:

I am in favor of doing away with := entirely because of the desire to control what is
done per-value on multiple returns.  
The :val syntax described above seems nice and short and would seem like valid syntactic
sugar for a keyword driven solution:
:x = f(), declare(shadow) and initialize x, infer type
x  = f(), assign x, infer type
would be the same as
auto var x = f(), declare(shadow) and initialize x, infer type
auto x = f(), assign x, infer type
to revisit the implicit/explicit example shown above in comment 21:
var x = "unum"
func implicit() {
    fmt.Println(x) // x = "unum"
    :x = "one" //<- potentially make this an error, redeclaration after use in same scope.
    //:x = "two" <- would not compile, can only declare once in scope
    fmt.Println(x) // x = "one", global x still = "unum"
    if :x, :err = 1, (*int)(nil); err == nil {
        fmt.Println(x) // x = 1
    }
    fmt.Println(x) // x = "one"
}
func explicit() {
    fmt.Println(x) // x = "unum"
    {
        :x = "one"
        fmt.Println(x) // x = "one"
        {
            if :x, :err = 1, (*int)(nil); err == nil {
                fmt.Println(x) // x = 1
            }
        }
        fmt.Println(x) // x = "one"
    }
    fmt.Println(x) // x = "unum"
}
to revisit the example in the original post:
func f() (err os.Error) {
  :v, err = g(); <-- reusing err for return
  if err != nil {
    return;
  }
  if v {
    :v, err = h(); <-- shadowing v, but reusing err for return
    if err != nil {
      return;
    }
  }
}
in addition, if one wants to enforce typing per-value, specifying type removes the need
for :val as you cannot re-specify a type on an existing value and thus initialisation is
inferred.
int :x, os.Error err = f(); initialize and assign x/error, don't compile if return value
2 is not os.Error
@rsc

This comment has been minimized.

Contributor

rsc commented Jul 25, 2011

Comment 27:

I think it's safe to say we're not going to change :=.

Status changed to WorkingAsIntended.

@gopherbot

This comment has been minimized.

gopherbot commented Jul 26, 2011

Comment 28 by czapkofan:

Could you possibly elaborate a bit why? Especially with regards to the alternative
"explicit" syntax proposals?
I don't plan to argue, the right to any final decision is obviously yours as always; but
I'd be highly interested to know if there are some problems expected to be introduced by
those proposals, or otherwise what is the common reasoning behind this decision.
Thanks.
@niemeyer

This comment has been minimized.

Contributor

niemeyer commented Jul 26, 2011

Comment 29:

Agreed.  Besides _changing_ :=, there are other proposals, and this problem was brought
up repeatedly in the mailing list by completely different people, with this thread being
referenced as the future answer (how many issues are starred by 46+ people?).
It'd be nice to have some more careful consideration and feedback before dismissal.
@rsc

This comment has been minimized.

Contributor

rsc commented Jul 26, 2011

Comment 30:

The decision about := is not mine, at least not mine alone.
I am just trying to clean up the bug tracker, so that it reflects
things we need to work on.
1. The bug entry is 1.5 years old at this point.  If it were
going to have an effect, it would have by now.
2. This comes up occasionally on its own.  A bug entry is
not necessary to remind us about it.
I'll change the status back to long term but I remain
skeptical that anything will change.

Status changed to LongTerm.

@niemeyer

This comment has been minimized.

Contributor

niemeyer commented Jul 26, 2011

Comment 31:

Thanks for the work on cleaning up, it's appreciated. It's also certainly fine for this
to be closed if it reflects a decision made.
The point was mostly that it'd be nice to have some feedback on the features proposed
for solving the problem, given that there's so much interest on the problem and a bit of
love towards a few of the proposed solutions.
E.g. allowing this:
    :v, err = f()
as equivalent to
    var v T
    v, err = f()
If you have internally decided this is off the table, it'd be nice to know it, and if
possible what was the reasoning.
@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented May 14, 2018

Summarizing the discussion.

The original problem report was fixed before the Go 1 release by https://golang.org/cl/5245056. That code (if updated to current syntax) now gets an error at compilation time: err is shadowed during return.

Suggestions made in this issue and its duplicates:

  • Don't permit shadowing variables in general.
  • Don't permit shadowing variables declared in the same function (but do permit shadowing global variables).
  • Allow arbitrary expressions on the left hand side of :=, treating them as though they appeared on the left hand side of =.
  • Don't require a new variable declaration on the left hand side of :=.
  • Permit declaring new variables using =, but require at least one of the variables to already exist.
  • Use a per-variable syntax with := to indicate which variables are being declared.
    • Put a : before each variable being declared.
    • Use (var v) for each variable being declared.
  • Don't use an implicit block for := declarations in if, for, switch, since the lack of a { makes them seem to be in the outer block.

@golang golang unlocked this conversation May 15, 2018

@carlmjohnson

This comment has been minimized.

Contributor

carlmjohnson commented May 15, 2018

FWIW, my suggestion:

  • Permit shadowing with var but not with :=.
@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented May 15, 2018

Another possibility that may have been mentioned elsewhere:

Currently := permits redeclaring variables that were declared earlier in the same block, as long as at least one of the variables is new. We could change that so that := redeclares variables declared earlier in the same block or in any surrounding block within the same function, as long as at least one of the variables is new.

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented May 15, 2018

One thing that seems to be missing from this issue is a clear statement of the problem. For example, is this a problem only for programmers new to Go, or are there cases that are confusing for experienced Go programmers. (The test case that started this issue did seem to be confusing for experienced Go programmers, and that test case no longer compiles.)

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented May 15, 2018

Somewhat related to #20802.

@nassibnassar

This comment has been minimized.

nassibnassar commented May 15, 2018

I am new to Go; please disregard this feedback if it isn't helpful. The problem, as I see it, is that the behavior of := is overly subtle. Maybe when := was first invented, that was in a simpler context, and it might have been very elegant. But with redeclaration, multiple assignment, and scoping/shadowing effects, it can be unclear what is going on. Again, I am a Go-newbie and am still learning the language, but this is my memory of the problem from several months ago when I looked at it. Also, in reading the proposals above, I hope that changes to := (if any) will make the language simpler rather than more complicated.

@pam4

This comment has been minimized.

pam4 commented May 24, 2018

I'm a supporter of the colon-prefix syntax proposed above, for example :n, err = f()

@ianlancetaylor has mentioned a per-variable syntax in his summary, but the one I'm talking about is to be used with =, not :=. That is to say, this kind of short declaration would be just a special L-value on the left side of a normal assignment.
I think it could be also fully backward compatible.

Some time ago I wrote a hackish code rewriter for this syntax, and I would be glad if anyone wants to try it out to get a feel of the syntax.
If you do, please read the caveats in the README file that I just jotted down (sorry, I wish I had more time), and keep in mind that I'm publishing this only as a demonstration for this issue.
https://github.com/pam4/gooey

I've used this syntax for some time now, and I find it easier to read and to write, specially in cases similar to the examples reported here and in related issues.

@lpar

This comment has been minimized.

lpar commented May 24, 2018

Just got caught by variable shadowing while using filepath.Walk with an anonymous function, because Walk returns an error and the anonymous function has to accept an error, and they're not the same error.

err := filepath.Walk(basedir, func(path string, f os.FileInfo, err error) error { ... })
@pam4

This comment has been minimized.

pam4 commented May 24, 2018

EDIT: some of the suggestions/solutions summarized above are trying to solve very different problems, and by solving one problem they may make another worse.
I think we also need a list of problems:

  1. shadowing is confusing/error-prone (in combination with multi-variable :=, or in general). (suggestions 1, 2, 6)
  2. multi-variable := is not informative enough for readers: by just looking at it you can't tell which variables are new and which are being reused; you need to eyeball all the preceding part of the block to get that information (taking into account function arguments and named return values), and you may get confused if you miss something. (suggestion 6)
  3. it is not always possible to use a short declaration in a multi-variable context, which partially defeats the convenience of short declarations:
    1. when you have at least one variable to declare and at least one arbitrary expression on the left hand side (suggestions 3, 6)
    2. when you have at least one variable to declare and at least one variable to reuse from an outer scope (suggestions 5, 6)

(not sure what suggestion 4 in Ian's list is supposed to solve)

I believe that a per-variable short declaration syntax is the only solution that have a chance to solve all the problems I listed (unless you are against shadowing in general).

Proposed per-variable syntax alternatives:
:n, err = f()
n, (err) := f() (details here)
(var n), err = f()
All equivalent to: var n T; n, err = f()

A per-variable syntax could be backward compatible and nearly as terse as a := declaration but would allow finer control, would be more informative, and the compiler would be able to catch more errors.

By introducing special cases about shadowing we would only address problem #1, at the cost of violating the encapsulation notion of a block (as noted here and here): one block could cease to work when its context is changed, even if the block is independent of such context.

Problem #2 cannot be addressed with a per-statement switch (= / :=), no matter what magic you put into it, and problem #3 can be addressed only partially.

Suggestions addressing only problem #3 have generally bad consequences for the other problems, for example allowing bare new variables on the left side of = would just make the lack of explicitness problem worse.

@networkimprov

This comment has been minimized.

networkimprov commented Aug 11, 2018

A backwards-compatible approach to shadowing: let var name override shadows within its scope. Although this would not prevent unintended shadows, maybe go vet can catch those...

x, err := fa()
if err == nil {      // OR: if var err; err == nil 
   var err           // override shadowing in if-scope
   y, z, err := fb() // preferable to separate declarations of y & z
}
if err != nil { ... }

To silence go vet re intended shadows, use var name type in the new scope.

x, err := fa()
if err == nil {      // NOT: if var err error; err == nil (valid but wrong)
   var err error     // explicit shadowing; not flagged by go vet
   y, z, err := fb()

Et voila, the scope of err is clear in both cases.

@gertcuykens

This comment has been minimized.

gertcuykens commented Sep 23, 2018

Would like to add a gopher talk illustrating that many people struggle with shadowing including me and hope desperately this will make it in Go2

https://www.youtube.com/watch?v=zPd0Cxzsslk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment