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.)
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.
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 }
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.
Comment 11 by [email protected]:
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 :-)
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
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?
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.
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
Comment 20 by [email protected]:
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 ;) )
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" } }
Comment 22 by [email protected]:
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.
Comment 23 by [email protected]:
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. ;)
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.
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
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.
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.
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._
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.
I don't think magic syntax, such as an extra : on the left hand side, is a good idea. I think it might be feasible to disallow using := to shadow a variable in an enclosing block in the same function. This would have a significant disadvantage: blocks would no longer stand by themselves. However, it would remain possible in most cases to write a block which does stand by itself, by using an explicit "var" instead of :=. It's just a thought, I don't know if it really works.
It's only magic before it's defined and specified. You can think of this: v, err := f() as exactly the same as this: :v, :err = f() Which looks like "distributing the operator". Some time after we introduce this logic, we can then prevent from *reusing* variables entirely, and force calls like this: v, err := f() to be made instead as the following when the intention is to reuse a previous definition of "err": :v, err = f() This, in turn, enables us to consistently use the former/current version of := to always mean "shadow/define everything, and error out if the variable isn't used". If I'm not missing any details (quite possible) I believe these steps can be made gradually and in a compatible way, and would address good part of the reasons why people reach this issue.
As a more novice user, I enjoy the := because it just does what you want it to do. Except in this case. The case where I ran into this involved a global variable not being set, instead a new local variable was made. I would like to see an opt out feature of :=, so you can do v, :err := f() in this way you explicitly say you don't want a redeclaration of the second variable. This way you only have to put in a special character for the special cases. Again I'm a novice user. I would just like to see a way to use outside scopes, while still keeping the power of :=
I agree with Russ: I think ':=' works well. However, I also once forgot the rules for this operator and got a bug. Maybe an optional compiler flag could help, by warning the user when such case happens ? And with an optional compiler flag, gofmt could automatically rename such variables ? // --------- example --------------- package main import( "os" "fmt" ) var i float32 func main() { i, err := bar(0) // warning: 'i' hides var defined at ... (global) if err != nil { i, _ := bar(1) // warning: 'i' hides var defined at ... (local) fmt.Println(i) } fmt.Println(i) } func bar(i int) (int, os.Error) { if i == 0 { return i, fmt.Errorf("blah") } return i, nil }
An optional flag that enables a convention which is entirely unused in the language and largely not followed by the community doesn't have a place in a standard tool, IMO. If you follow that convention yourself, though, you can easily build something to verify your code based on the "go" package.
Can someone give an example where shadowing a variable name is the right thing to do -- either for clarity, correctness or performance, preferably, all three? Please include the counter example showing how confusing, broken and burdensome such code would be in a world without shadowing. n13m3y3r seems to say in Comment 39 that he has such an example, but my imagination fails me.
Moving priority-someday to the Unplanned milestone.
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:
:=
, treating them as though they appeared on the left hand side of =
.:=
.=
, but require at least one of the variables to already exist.:=
to indicate which variables are being declared.:
before each variable being declared.(var v)
for each variable being declared.:=
declarations in if
, for
, switch
, since the lack of a {
makes them seem to be in the outer block.FWIW, my suggestion:
var
but not with :=
.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.
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.)
Somewhat related to #20802.
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.
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.
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 { ... })
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.
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.
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
The most common case in which :=
is used to redeclare an existing variable, while also defining a new variable, is for the variable err
. If we adopt the error handling design draft linked from https://go.googlesource.com/proposal/+/master/design/go2draft.md then it is possible that it will no longer be important to be able to use :=
to redeclare err
. In that case, perhaps we could consider dropping the redeclaration aspect of :=
. Instead, all variables on the left hand side of :=
would be defined for the first time, and if there were already a variable of that name it would be an error. That would be a simple change that might address many people's concerns.
I think that concerns about variable shadowing should be considered separately from :=
redefinitions, perhaps in a different issue, though I don't know if there is one open for that right now.
I've branched my comment above re variable shadowing to #30321
@ianlancetaylor, there is an old proposal above, which is equivalent* to #30318 plus dropping the redeclaration aspect of :=
(in fact, #30318 would eliminate the need for redeclarations, regardless of the error handling design).
I'm highly in favor of it: if both of those things were to happen I think all the problems I listed could be solved.
Unfortunately dropping the redeclaration aspect of :=
is not backward compatible (but #30318 is). On the contrary the colon-prefix syntax is as effective but also backward compatible.
(* actually nsf's proposal differs in its point #2
, but it's just a matter of symmetry and doesn't change its effectiveness)
I just got an idea which acts like an inverse of @niemeyer's idea. (_edit: It is more like @nsf's idea_)
The idea removes redeclaration :=
syntax (which is the different point to the ideas from @niemeyer and @nsf) and tries to solve both the problems mentioned in the current proposal and this one.
The following code shows the logic of the idea:
package bar
func foo() {
var x, err = f()
...
// Here ":err" means the "err" declared above.
var y, z, :err = g()
...
{
// In fact, ":id" means the innermost already declared "id"
var w, :err = h()
...
// "::id" means the package-level declared "id".
var u, v, ::err = j()
...
// This "err" is a new declared one.
var m, n, err = k()
...
}
}
(_edit: the ::id
syntax is not essential in this idea._)
Selectors, dereferences, element indexing, and the above mentioned :id
/::id
forms are allowed to show up in a variable declaration as long as there is at least one pure identifier in the declaration. The pure identifiers represent new declared variables for sure.
var err error
type T struct {
i *int
s []int
}
var t T
var p = new(int)
func bar() {
var *p, t.i, t.s[1], err = 1, p, 2, ::err
// Yes, the "::id" can be used as sources.
// ":id" can also but it is a non-sense.
...
}
The var
keyword can be omitted in the short statements in all kinds of control flows.
For example,
if a, b = c, d; a {
}
//<=>
if var a, b = c, d; a { // some ugly
}
The idea should be Go 1 compatible.
Sorry, the two :=
in the last code snippet should be both =
.
Sorry, the description for control flow short statements is not correct. In fact, the :=
redeclaration syntax should be kept, but ONLY in the short statements in control flows, just for aesthetics reason. In other words,
if a, b := c, d; a {
}
//<=>
if var a, b = c, d; a { // some ugly
}
The whole idea still keeps Go 1 compatible with this change.
Another simple fix is to prefix *&
to re-declared identifiers to indicate the prefixed identifiers are redeclared.
var a, err = A()
var b, *&err = B()
The benefit here is that no new expression notations are invented (same as (err)
).
Is *&
a nop at runtime, or are you suggesting it should be? It feels like the compiler should be smart enough to figure it out today.
Yes, it has already been a nop now.
Could we consider a clearer (but more verbose) solution to the problem in the same spirit as global
keyword in Python which is widely used?
outerscp
is introduced to the language.outerScp var1, var2, var3
var1
, var2
etc. are variables declared in the outer scopeouterscp
statement, it is not shadowedpackage main
import (
"fmt"
)
var a int
func printGlobal() {
fmt.Printf("Print Global: %d\n", a)
}
func getNum() (int, error) {
return 4, nil
}
func main() {
a, err := getNum()
if err != nil{
fmt.Printf("error")
return
}
printGlobal()
fmt.Printf("Printing in main: %d\n", a)
}
Print Global: 0
Printing in main: 4
outerscp
keywordpackage main
import (
"fmt"
)
var a int
func printGlobal() {
fmt.Printf("Print Global: %d\n", a)
}
func getNum() (int, error) {
return 4, nil
}
func main() {
outerscp a
a, err := getNum()
if err != nil{
fmt.Printf("error")
return
}
printGlobal()
fmt.Printf("Printing in main: %d\n", a)
}
Print Global: 4
Printing in main: 4
outerscp
keyword looks ugly. HoweverouterScope
keyword is used in several public repositoriesouter
, global
etc. are of course used in many repos as identifiers@srinathh That problem with a keyword like outerscp
is that it only helps if you know that you have a problem. If you know that you have a problem, there are other things you can do, like not use :=
.
@ianlancetaylor Yes you're right... but that criticism is equally applicable to any other proposal short of completely removing support for the :=
operator.
I think the key to reducing possibility of subtle bugs is reduce cognitive load so programmers can better maintain contextual awareness. A keyword approach I think makes things a lot more obvious vs. introducing cryptic notations into the :=
assignment statement and should be familiar at least to Python programmers who form the biggest chunk of Go users who use multiple languages as per the survey.
@srinathh Fair point. I guess what I'm mean is that while cryptic notations are definitely cryptic, and perhaps not a good idea, at least they are short. If I have to write out outerscp v
, then I might as well just write var v
as needed and stop using :=
.
Most helpful comment
Comment 16: