strange scoping rules

504 views
Skip to first unread message

Ryanne Dolan

unread,
Nov 23, 2010, 1:44:50 AM11/23/10
to golang-nuts
Why does Go let you redefine an identifier in a child block?

foo := true
{
   foo := false
   _ = foo
}
// foo is true

More alarmingly (and the source of several bugs in my code):

var foo interface{}

if foo, err := getFoo(); err != nil {
   // trouble getting foo
}
// foo is still nil

I understand what is going on here, of course.  But it isn't consistent with the following:

var a int
a, ok := 1, true

...in which 'a' is _not_ redefined, but only 'ok' is newly defined.  Knowing this, you'd expect "a,b := c,d" to only define a or b if they aren't defined in the current scope.  But in my previous examples, 'foo' _is_ defined in the current scope (inherited from the parent block).  The default semantics are to redefine and mask foo.

I can't think of a legitimate reason to allow masking of variables.  I especially can't fathom why the default semantics of ":=" is to silently mask variables when possible.  Perhaps it makes sense within nested closures, but it is always possible to come up with new variable names rather than mask ones in the parent block.

In a lot of my recent code, I've had bugs due to a the presence of ":=" instead of "=".  If the compiler didn't let me mask variables, I would have caught my mistake right away.

Thanks.
Ryanne

--
www.ryannedolan.info

Jessta

unread,
Nov 23, 2010, 2:07:03 AM11/23/10
to Ryanne Dolan, golang-nuts
On Tue, Nov 23, 2010 at 5:44 PM, Ryanne Dolan <ryann...@gmail.com> wrote:
> Why does Go let you redefine an identifier in a child block?
> foo := true
> {
>    foo := false
>    _ = foo
> }

Other languages allow the same behaviour.
The advantage of this is that blocks of code don't have to worry about
the surrounding scope they are in.
The := operator makes this a bit less obvious.

> // foo is true
> More alarmingly (and the source of several bugs in my code):
> var foo interface{}
> if foo, err := getFoo(); err != nil {
>    // trouble getting foo
> }
> // foo is still nil
> I understand what is going on here, of course.  But it isn't consistent with
> the following:
> var a int
> a, ok := 1, true
> ...in which 'a' is _not_ redefined, but only 'ok' is newly defined.  Knowing
> this, you'd expect "a,b := c,d" to only define a or b if they aren't defined
> in the current scope.  But in my previous examples, 'foo' _is_ defined in
> the current scope (inherited from the parent block).  The default semantics
> are to redefine and mask foo.

The semantics are exactly the same.
var a int
{
a, ok := 1, true // a is redefined here.
}
//the outer a is accessible here.

The 'SimpleStmt' in the if is part of the scope of the if block.
http://golang.org/doc/go_spec.html#If_statements

> I can't think of a legitimate reason to allow masking of variables.  I
> especially can't fathom why the default semantics of ":=" is to silently
> mask variables when possible.  Perhaps it makes sense within nested
> closures, but it is always possible to come up with new variable names
> rather than mask ones in the parent block.
> In a lot of my recent code, I've had bugs due to a the presence of ":="
> instead of "=".  If the compiler didn't let me mask variables, I would have
> caught my mistake right away.

I've had these problems too.
The := is a bit too close to the = and it's easy to make that typo and
not realise.
Syntax highlighting might help in this instance. But I do tend to
avoid := for this very reason.


- jessta


--
=====================
http://jessta.id.au

Ryanne Dolan

unread,
Nov 23, 2010, 2:25:50 AM11/23/10
to Jessta, golang-nuts
Jessta,

"The advantage of this is that blocks of code don't have to worry about the surrounding scope they are in."

I certainly understand that, but it seems justified only when copy-pasting code.  It seems to me that a simple find-replace solves the problem better than silently masking variables.  The feature hardly compensates for the other problems I mentioned.

"Other languages allow the same behaviour."

Well C '89 and Python don't, and I've been led to believe that Go is supposed be similar to C and Python when possible.

Thanks.
Ryanne

--
www.ryannedolan.info

bflm

unread,
Nov 23, 2010, 3:11:58 AM11/23/10
to golang-nuts
On Nov 23, 8:25 am, Ryanne Dolan <ryannedo...@gmail.com> wrote:
> Why does Go let you redefine an identifier in a child block?
>
> foo := true
> {
> foo := false
> _ = foo
>
> }
>
> // foo is true

> "Other languages allow the same behaviour."
>
> Well C '89 and Python don't, and I've been led to believe that Go is
$ cat tmp.c && gcc -o tmp -pedantic -std=c89 tmp.c && ./tmp
#include <stdio.h>

int main() {
int foo = 1;
{
int foo = 0;
}
printf("foo is %d\n", foo);
}
foo is 1
$

chris dollin

unread,
Nov 23, 2010, 3:12:29 AM11/23/10
to Ryanne Dolan, Jessta, golang-nuts
On 23 November 2010 07:25, Ryanne Dolan <ryann...@gmail.com> wrote:
> Jessta,
>
> "The advantage of this is that blocks of code don't have to worry about the
> surrounding scope they are in."
> I certainly understand that, but it seems justified only when copy-pasting
> code.  It seems to me that a simple find-replace solves the problem better
> than silently masking variables.  The feature hardly compensates for the
> other problems I mentioned.
> "Other languages allow the same behaviour."
> Well C '89 and Python don't, and I've been led to believe that Go is
> supposed be similar to C and Python when possible.

It depends on exactly which bit of "the same behaviour" we mean,
but C89 certainly allows you to shadow variables in nested scopes.

Chris

--
Chris "allusive" Dollin

roger peppe

unread,
Nov 23, 2010, 3:42:22 AM11/23/10
to Ryanne Dolan, golang-nuts
On 23 November 2010 06:44, Ryanne Dolan <ryann...@gmail.com> wrote:
> I can't think of a legitimate reason to allow masking of variables.

there are at least two situations where i find this behaviour particularly
useful:

1) when narrowing a type, the name still refers to the same object,
even though the type is now different, so it makes sense to use
the same name:

func foo(x interface{}) {
switch x := x.(type) {
case int:
bar(x)
....
}
}

if x, ok := x.(SomeType); ok {
...
}

2) when using a closure inside a loop that refers to loop-local variables;
again, there's no need to make up an arbitrary new name for the same thing:

for _, x := range p {
x := x
foo(func() {fmt.Println(x)})
}

the Limbo language had restrictions on shadowing local variables,
and on balance i prefer things the Go way, although i admit i've
had my fair share of bugs from this too.

the special case for multiple-value := is more debatable.

Ryanne Dolan

unread,
Nov 23, 2010, 3:43:06 AM11/23/10
to chris dollin, Jessta, golang-nuts
Chris,

"C89 certainly allows you to shadow variables in nested scopes"

I guess you are right.  I still don't know why I would do that tho...

Thanks.
Ryanne

--
www.ryannedolan.info

chris dollin

unread,
Nov 23, 2010, 4:05:10 AM11/23/10
to Ryanne Dolan, Jessta, golang-nuts
On 23 November 2010 08:43, Ryanne Dolan <ryann...@gmail.com> wrote:
> Chris,
> "C89 certainly allows you to shadow variables in nested scopes"
> I guess you are right.  I still don't know why I would do that tho...

In the general case, it means that a function need not care what
variables are declared in the environment it is defined in, if it
does not need to use them.

Consider (in C)

#include "thingy.h"

int wossname()
{
int spoo = 17;
someThingyOperation( &spoo );
return spoo + 1;
}

It doesn't matter if thingy.h happens to declare something called
`spoo`; wossname doesn't need to be edited or recompiled if
thingy 2.0 introduces spoo(int) as its Fancy New Feature.

So function-locals shadowing non-locals is a useful feature,
at least in C-like languages.

You can make a case that shadowing locals of that same function
is asking for trouble; I'm not convinced that the loss of the simple
scope rule is a good tradeoff. The one time in C where shadowing
bit me was caught by the first run of the code it appeared in, and
had I properly understood the value and use of automated tests
at that time would have been caught in the tests pretty much
immediately.

[I carefully used "declared" above to exclude #defines for spoo,
which don't follow block-scope rules and hence can trample
freely over any and all your local variable declarations, boo
hiss rotters -- another illustration of the point.]

[Just adding Go-style package namespacing to a C-like language
doesn't make the problem go away, although it makes it less
common -- the package name can be shadowed.]

Ryanne Dolan

unread,
Nov 23, 2010, 4:15:40 AM11/23/10
to chris dollin, Jessta, golang-nuts
Chris,

"You can make a case that shadowing locals of that same function is asking for trouble"

Yeah, I'm worried about code like this:

foo () {
   int a;
   {
      int a;  // why would you ever mask/shadow/redefine 'a'?
   }
}

I didn't realize you _could_ do that in C '89, until you corrected me.

At least in C, you must deliberately define a new variable with a separate syntax.  In Go, the following code is ambiguous:

a, b := true, false

Depending on the environment in which it is compiled, you can get:

1) a is a new var, b is a new var
2) a is a new var, b is an old var
3) a is an old var, b is a new var

So I think in Go the problem is a little worse than in C, and I don't see that the "feature" is justified.

Thanks.
Ryanne

--
www.ryannedolan.info

chris dollin

unread,
Nov 23, 2010, 4:34:32 AM11/23/10
to Ryanne Dolan, Jessta, golang-nuts
On 23 November 2010 09:15, Ryanne Dolan <ryann...@gmail.com> wrote:
> Chris,
> "You can make a case that shadowing locals of that same function is asking
> for trouble"
> Yeah, I'm worried about code like this:
> foo () {
>    int a;
>    {
>       int a;  // why would you ever mask/shadow/redefine 'a'?
>    }

The time when I think one might want this really badly is in
automatically-generated code, possibly stitched together from
several sources, where you don't necessarily have control
over the names.

(The case where foo() is a BIG function I'd normally dismiss
with "write smaller functions!". But again, some functions are
written by programs, and program-writing programs routinely
break assumptions made by program-reading programs that
are written by programmers who assume programs are written
by people. Like, of COURSE no-one will write methods that
need more than 64K of bytecodes.)

Chris

--
No grammars were harmed in the composition of this message.

roger peppe

unread,
Nov 23, 2010, 4:59:56 AM11/23/10
to Ryanne Dolan, chris dollin, Jessta, golang-nuts
On 23 November 2010 09:15, Ryanne Dolan <ryann...@gmail.com> wrote:
> syntax.  In Go, the following code is ambiguous:
> a, b := true, false
> Depending on the environment in which it is compiled, you can get:
> 1) a is a new var, b is a new var
> 2) a is a new var, b is an old var
> 3) a is an old var, b is a new var
> So I think in Go the problem is a little worse than in C, and I don't see
> that the "feature" is justified.

as i said above, i think the multiple value case is an orthogonal
issue from whether to allow shadowing of local variables.

i can see why they did it though - it's common to want to do this,
where err is the same type throughout:

a, err := foo()
if err != nil { ... }

b, err := bar()
if err != nil { ... }

without the special case for multiple value :=, you have
to make up a new name for err each time.

Gustavo Niemeyer

unread,
Nov 23, 2010, 7:17:55 AM11/23/10
to roger peppe, Ryanne Dolan, chris dollin, Jessta, golang-nuts
> i can see why they did it though - it's common to want to do this,
> where err is the same type throughout:

Indeed, agreed it is handy, but to be honest I did find bugs in my
code already which were caught by unittests and caused by multi-value
return + shadowing from variable scoping.

The problem is that in an internal scope, this:

err = f()

Must be moved to this:

a, err2 := f()
err = err2

Rather than the obvious choice.

--
Gustavo Niemeyer
http://niemeyer.net
http://niemeyer.net/blog
http://niemeyer.net/twitter

roger peppe

unread,
Nov 23, 2010, 7:47:11 AM11/23/10
to Gustavo Niemeyer, Ryanne Dolan, chris dollin, Jessta, golang-nuts
On 23 November 2010 12:17, Gustavo Niemeyer <gus...@niemeyer.net> wrote:
>> i can see why they did it though - it's common to want to do this,
>> where err is the same type throughout:
>
> Indeed, agreed it is handy, but to be honest I did find bugs in my
> code already which were caught by unittests and caused by multi-value
> return + shadowing from variable scoping.
>
> The problem is that in an internal scope, this:
>
>    err = f()
>
> Must be moved to this:
>
>    a, err2 := f()
>    err = err2

the alternative is:

var a SomeType
a, err = f()

which may be preferable, although sometimes it's annoying to
have to write out the type name.

David Roundy

unread,
Nov 23, 2010, 7:52:30 AM11/23/10
to Jessta, Ryanne Dolan, golang-nuts
On Tue, Nov 23, 2010 at 2:07 AM, Jessta <jes...@jessta.id.au> wrote:
> On Tue, Nov 23, 2010 at 5:44 PM, Ryanne Dolan <ryann...@gmail.com> wrote:
>> Why does Go let you redefine an identifier in a child block?
>> foo := true
>> {
>>    foo := false
>>    _ = foo
>> }
>
> Other languages allow the same behaviour.

Are there any languages that have an equals-like operator that will
declare new variables that mask local variables? I'm not aware of any
such language.

> The advantage of this is that blocks of code don't have to worry about
> the surrounding scope they are in.

Which is actually false. Because when you move a block of code from
one scope to another, its meaning can change dramatically for
precisely this reason, with the classic example being

x,err := f()
if err != nil {
return
}

which doesn't quite falsify your claim, but to be honest I more often
copy "chunks" of code than brace-delimited blocks of code.

> The := operator makes this a bit less obvious.

Yeah, it's the := operator that is the real problem here. And the
fact that there is no way to determine how many of its arguments are
newly-declared. I'd love it if := were to refuse to mask any
variables. I'm fine with var declarations masking local variables,
since in that case it's clear that a variable is being declared, but
when you use := with two arguments there is no way to tell which of
them are being declared.
--
David Roundy

Mateusz Czapliński

unread,
Nov 23, 2010, 8:06:09 AM11/23/10
to golang-nuts
On Nov 23, 1:17 pm, Gustavo Niemeyer <gust...@niemeyer.net> wrote:
> > i can see why they did it though - it's common to want to do this,
> > where err is the same type throughout:
>
> Indeed, agreed it is handy, but to be honest I did find bugs in my
> code already which were caught by unittests and caused by multi-value
> return + shadowing from variable scoping.
>
> The problem is that in an internal scope, this:
>
>     err = f()
>
> Must be moved to this:
>
>     a, err2 := f()
>     err = err2
>
> Rather than the obvious choice.

Nice case! Didn't hit that, but had a:
var i int
for i, v := range tab {
if v == 44 {
break
}
}
print(i)
That was not a bug per se (compiler reported a "i declared and not
used"), but I was still surprised.

Reading through your discussion and seeing the other examples, I'm
personally starting to feel about that as a flaw in the language
syntax. Unfortunately, I don't have any brilliant ideas to suggest for
a solution.
The only one as of now is a bit crazy and doesn't look too pretty, but
would be to mark the single variables as being either defined, or
reused - e.g. like:
for i, v: = range tab {
// ...
where ':' goes to 'v', not to '='. And in the 'err' case would be:
a:, err = f()

greetz
Mateusz Czapliński

chris dollin

unread,
Nov 23, 2010, 8:08:26 AM11/23/10
to David Roundy, Jessta, Ryanne Dolan, golang-nuts
On 23 November 2010 12:52, David Roundy <rou...@physics.oregonstate.edu> wrote:
> On Tue, Nov 23, 2010 at 2:07 AM, Jessta <jes...@jessta.id.au> wrote:
>> On Tue, Nov 23, 2010 at 5:44 PM, Ryanne Dolan <ryann...@gmail.com> wrote:
>>> Why does Go let you redefine an identifier in a child block?
>>> foo := true
>>> {
>>>    foo := false
>>>    _ = foo
>>> }
>>
>> Other languages allow the same behaviour.
>
> Are there any languages that have an equals-like operator that will
> declare new variables that mask local variables? I'm not aware of any
> such language.

If you'll allow user-written extensions using the code-generation API,
then yes, Pop11. I wrote and routinely used a syntactic extension for
Poplog Pop11 to allow

expression -> lvar x

The syntax word `lvar` expected to be the target of an assignment
(-> is the Pop11 right-to-left assignment operator), declared the
variable (here, x) and then did a normal assignment (of the value
of `expression`, patiently wating on the stack) to that variable.

Since the declaration was a lexically-scoped one (that's the "l"
in "lvar"), it could ("of course") shadow an existing variable of
the same name in an outer lexical scope.

Chris

--
Chris "the nearly-universal counter-example language" Dollin

Corey Thomasson

unread,
Nov 23, 2010, 8:09:34 AM11/23/10
to David Roundy, Jessta, Ryanne Dolan, golang-nuts
On 23 November 2010 07:52, David Roundy <rou...@physics.oregonstate.edu> wrote:
> On Tue, Nov 23, 2010 at 2:07 AM, Jessta <jes...@jessta.id.au> wrote:
>> On Tue, Nov 23, 2010 at 5:44 PM, Ryanne Dolan <ryann...@gmail.com> wrote:
>>> Why does Go let you redefine an identifier in a child block?
>>> foo := true
>>> {
>>>    foo := false
>>>    _ = foo
>>> }
>>
>> Other languages allow the same behaviour.
>
> Are there any languages that have an equals-like operator that will
> declare new variables that mask local variables? I'm not aware of any
> such language.

:= is not equals, it declare & set, in which case yes.

>> The advantage of this is that blocks of code don't have to worry about
>> the surrounding scope they are in.
>
> Which is actually false.  Because when you move a block of code from
> one scope to another, its meaning can change dramatically for
> precisely this reason, with the classic example being
>
> x,err := f()
> if err != nil {
>  return
> }

I fail to see the example here

> which doesn't quite falsify your claim, but to be honest I more often
> copy "chunks" of code than brace-delimited blocks of code.
>
>> The := operator makes this a bit less obvious.
>
> Yeah, it's the := operator that is the real problem here.  And the
> fact that there is no way to determine how many of its arguments are
> newly-declared.  I'd love it if := were to refuse to mask any
> variables.  I'm fine with var declarations masking local variables,
> since in that case it's clear that a variable is being declared, but
> when you use := with two arguments there is no way to tell which of
> them are being declared.

I'd hate it if := couldn't "mask" a variable, I do it often when
writing closures, loops, etc. However, I wouldn't really mind if it
went the other way, so that x, y := _always_ declares both as new
variables. It prevents this "common" mistake without breaking := in a
large number of cases. Though personally I just avoid using := if it's
unclear what it's doing.

David Roundy

unread,
Nov 23, 2010, 8:20:50 AM11/23/10
to Corey Thomasson, Jessta, Ryanne Dolan, golang-nuts
On Tue, Nov 23, 2010 at 8:09 AM, Corey Thomasson <cthom...@gmail.com> wrote:
> On 23 November 2010 07:52, David Roundy <rou...@physics.oregonstate.edu> wrote:
>> On Tue, Nov 23, 2010 at 2:07 AM, Jessta <jes...@jessta.id.au> wrote:
>>> On Tue, Nov 23, 2010 at 5:44 PM, Ryanne Dolan <ryann...@gmail.com> wrote:
>>>> Why does Go let you redefine an identifier in a child block?
>>>> foo := true
>>>> {
>>>>    foo := false
>>>>    _ = foo
>>>> }
>>>
>>> Other languages allow the same behaviour.
>>
>> Are there any languages that have an equals-like operator that will
>> declare new variables that mask local variables? I'm not aware of any
>> such language.
>
> := is not equals, it declare & set, in which case yes.

What language? (And note that I did use the phrase "equal-like", and
do consider that significant.)

>>> The advantage of this is that blocks of code don't have to worry about
>>> the surrounding scope they are in.
>>
>> Which is actually false.  Because when you move a block of code from
>> one scope to another, its meaning can change dramatically for
>> precisely this reason, with the classic example being
>>
>> x,err := f()
>> if err != nil {
>>  return
>> }
>
> I fail to see the example here

Try moving this from the main block of a function to inside an if
statement. See issue 377:

http://code.google.com/p/go/issues/detail?id=377

>> which doesn't quite falsify your claim, but to be honest I more often
>> copy "chunks" of code than brace-delimited blocks of code.
>>
>>> The := operator makes this a bit less obvious.
>>
>> Yeah, it's the := operator that is the real problem here.  And the
>> fact that there is no way to determine how many of its arguments are
>> newly-declared.  I'd love it if := were to refuse to mask any
>> variables.  I'm fine with var declarations masking local variables,
>> since in that case it's clear that a variable is being declared, but
>> when you use := with two arguments there is no way to tell which of
>> them are being declared.
>
> I'd hate it if := couldn't "mask" a variable, I do it often when
> writing closures, loops, etc. However, I wouldn't really mind if it
> went the other way, so that x, y := _always_ declares both as new
> variables. It prevents this "common" mistake without breaking := in a
> large number of cases. Though personally I just avoid using := if it's
> unclear what it's doing.

Hmmm. If I were writing a style guide (for any language) not masking
variables would be the first rule I'd write. It's just makes it too
easy to not know what a given line of code is doing. You think you
see what the variable is that's being modified, but you're wrong,
because you didn't see a := somewhere else in the function, or didn't
notice that it was at a different scope than the original declaration.
--
David Roundy

Gustavo Niemeyer

unread,
Nov 23, 2010, 8:27:12 AM11/23/10
to David Roundy, Jessta, Ryanne Dolan, golang-nuts
> Yeah, it's the := operator that is the real problem here.  And the
> fact that there is no way to determine how many of its arguments are
> newly-declared.  I'd love it if := were to refuse to mask any
> variables.  I'm fine with var declarations masking local variables,

I'd also like to have some better protection from that kind of
easy-to-fall-into bug, but just preventing := from masking entirely
may not be ideal unfortunately.

Think about this typical case, for instance:

if ok := f(); ok {...}

Maybe the rule could instead be: mask all, or mask nothing. In other
words, in a return statement such as:

a, err := f()

One can only use the := operator if *both* a and err are being
shadowed, or *both* a and err are being newly declared, but not a
mixed case of these.

Some points for that rationale:

1) If neither is being shadowed, it's obviously fine.

2) If both are being shadowed, it's more likely that the shadowing
happened as an intended consequence, rather than a lazy "fix" for a
compiler error which complained that one of these variables did not
yet exist.

It certainly won't prevent the problem entirely, but it may be a step
in the right direction.

What do you think?

Cory Mainwaring

unread,
Nov 23, 2010, 8:27:47 AM11/23/10
to golang-nuts
Preventing := from masking variable takes out all of the usefulness
when doing a:

x, err := f()

You either have to declare x, or find a new err (such as err2, err3...
err42). Rather than being offended that it will mask variables,
embrace it. Indeed, the original poster was alarmed not at the use of
:= but at the scope of SimpleStmt in the if block.

var x interface{}
if x, err := f(); err != nil {
//Code
}
//Can't use x from f()

They were expecting the SimpleStmt to be part of the outside block,
when it wasn't. So, the simplest solution is to not use SimpleStmt or
remember that it's scoped inside of the if (the if starts the if
block, not the brackets) (this behavior is far more odd than :=
masking higher scope variables).

Also, don't call for the crosses and nooses when there's just a
misunderstanding in the language, := makes perfect sense masking
variables, as it is COMMON to mask variables in a local scope. The
fact that := will not redefine variables in the same scope is a nice
aspect, and removing it's ability to mask variables in a lower scope
would be a bad aspect. Leave it alone, instead attack the SimpleStmt
if you want to bring out the pitchforks and torches.

Gustavo Niemeyer

unread,
Nov 23, 2010, 8:32:55 AM11/23/10
to David Roundy, Jessta, Ryanne Dolan, golang-nuts
>    a, err := f()
>
> One can only use the := operator if *both* a and err are being
> shadowed, or *both* a and err are being newly declared, but not a
> mixed case of these.

Hmmm.. on a second thought.. this will create issues with constructions like

a, err := f()
b, err := g()

Which is certainly nice to have.

I recognize the problem and would appreciate protection from the
compiler, but have no good suggestion on how to do it at this point.

peterGo

unread,
Nov 23, 2010, 9:42:13 AM11/23/10
to golang-nuts
Ryanne,

> Why does Go let you redefine an identifier in a child block?

Since Algol, It has been a common feature of block-structured
programming languages.

"In a block-structured programming language, the names of variables
and other objects such as procedures which are declared in outer
blocks are visible inside other inner blocks, unless they are shadowed
by an object of the same name."

Block (programming)
http://en.wikipedia.org/wiki/Block_%28programming%29

Peter

Ryanne Dolan

unread,
Nov 23, 2010, 10:30:03 AM11/23/10
to David Roundy, Jessta, golang-nuts
" I'd love it if := were to refuse to mask any
variables.  I'm fine with var declarations masking local variables,
since in that case it's clear that a variable is being declared, but
when you use := with two arguments there is no way to tell which of
them are being declared."

I think this is a great compromise.  I still consider it a bad idea to mask local variables in general, but if some people like to for some reason, it would be great if it were required to be deliberate.

Thanks.
Ryanne

--
www.ryannedolan.info

peterGo

unread,
Nov 23, 2010, 11:34:54 AM11/23/10
to golang-nuts
Ryanne,

Duplicate of issue 377.

Issue 377: various changes to :=
http://code.google.com/p/go/issues/detail?id=377

Peter

On Nov 23, 1:44 am, Ryanne Dolan <ryannedo...@gmail.com> wrote:

peterGo

unread,
Nov 23, 2010, 11:37:03 AM11/23/10
to golang-nuts
Ryanne,

> when you use := with two arguments there is no way to tell which of
> them are being declared."

Short variable declarations, The Go Programming Language Specification
http://golang.org/doc/go_spec.html#Short_variable_declarations

Peter

On Nov 23, 10:30 am, Ryanne Dolan <ryannedo...@gmail.com> wrote:
> " I'd love it if := were to refuse to mask any
> variables.  I'm fine with var declarations masking local variables,
> since in that case it's clear that a variable is being declared, but
> when you use := with two arguments there is no way to tell which of
> them are being declared."
>
> I think this is a great compromise.  I still consider it a bad idea to mask
> local variables in general, but if some people like to for some reason, it
> would be great if it were required to be deliberate.
>
> Thanks.
> Ryanne
>
> --www.ryannedolan.info
>
> On Tue, Nov 23, 2010 at 6:52 AM, David Roundy <
>
> roun...@physics.oregonstate.edu> wrote:
> > On Tue, Nov 23, 2010 at 2:07 AM, Jessta <jes...@jessta.id.au> wrote:
> > > On Tue, Nov 23, 2010 at 5:44 PM, Ryanne Dolan <ryannedo...@gmail.com>

Gustavo Niemeyer

unread,
Nov 23, 2010, 12:04:55 PM11/23/10
to peterGo, golang-nuts
>> when you use := with two arguments there is no way to tell which of
>> them are being declared."
>
> Short variable declarations, The Go Programming Language Specification
> http://golang.org/doc/go_spec.html#Short_variable_declarations

The specification doesn't help his concern. When you see "a, b :=
f()", you can't be sure if a or b is being newly declared or not
without looking back in the code.

Ian Lance Taylor

unread,
Nov 23, 2010, 12:06:12 PM11/23/10
to Ryanne Dolan, golang-nuts
Ryanne Dolan <ryann...@gmail.com> writes:

> More alarmingly (and the source of several bugs in my code):
>
> var foo interface{}
>
> if foo, err := getFoo(); err != nil {
> // trouble getting foo
> }
> // foo is still nil
>
> I understand what is going on here, of course. But it isn't consistent with
> the following:
>
> var a int
> a, ok := 1, true
>
> ...in which 'a' is _not_ redefined, but only 'ok' is newly defined.

I agree that this is confusing.

The rules are fairly simple, but I have to agree that accidentally
typing := when you mean = can trip you up. The question is whether we
can think of a way to change this without losing useful features of the
language.

* Being able to redeclare variable names in an inner block is, I
believe, a useful feature. It makes it easier to use short names in
nested scopes. It makes it slightly easier to write, and read,
machine generated code. It's conventional in block scoped languages.

* Being able to redeclare existing variables with := is a useful
feature. It permits sequences of "v1, err := x()" which occur often
in Go code.

* The fact that in an inner block a redeclaration introduces a new
variable rather than reusing an existing variable is an unfortunate
consequence of the two preceding rules.

* An if, for, or switch statement which uses a := declaration introduces
a new inner block, but this is not syntactically obvious.

* The error you get when a variable is declared but not used is intended
to partially mitigate the problem of accidentally using := in an inner
scope, but of course it does not work in all cases.

The current situation is not bad but it is not perfect. Unfortunately I
don't see a good way to improve it. I personally don't want to abandon
block scoping, and I think that having if, for and switch introduce a
new scope is good. The redeclaration syntax is useful and widely used.
The only opening I see here is an adjustment to the meaning of
redeclaration in an inner scope, but I don't see how to make it useful
and easy to understand.

Ian

Gustavo Niemeyer

unread,
Nov 23, 2010, 12:19:39 PM11/23/10
to Ian Lance Taylor, Ryanne Dolan, golang-nuts
(...)

> The only opening I see here is an adjustment to the meaning of
> redeclaration in an inner scope, but I don't see how to make it useful
> and easy to understand.

Hmm, 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 }
}

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.

How do you all feel about this?

jimmy frasche

unread,
Nov 23, 2010, 1:42:27 PM11/23/10
to Gustavo Niemeyer, Ian Lance Taylor, Ryanne Dolan, golang-nuts

That seems further toward the opposite of inuitive than the present rules

ron minnich

unread,
Nov 23, 2010, 1:49:55 PM11/23/10
to jimmy frasche, Gustavo Niemeyer, Ian Lance Taylor, Ryanne Dolan, golang-nuts
If I ever hit a point of ambiguity as in the example, I just do a
declaration and use = everywhere. Probably not the best solution but
it works.

While I like the := form a lot, it does remind me of all the bugs I've
fixed over the years in near-dead languages that had a similar
capability. Of course the Go checking for usage helps a lot.

ron

Steven

unread,
Nov 23, 2010, 3:52:51 PM11/23/10
to ron minnich, jimmy frasche, Gustavo Niemeyer, Ian Lance Taylor, Ryanne Dolan, golang-nuts

Here's a toss out idea, similar to an earlier idea, but I think less
likely to cause problems:

(var y), err = f();

This way, you can be explicit about what variables are being newly
declared without needing to add the extra type declaration before the
statement. The brackets disambiguate it from the multiple
declaration/initialization var x, err = f(). This of course means that
var declarations would need to be allowed as simple statements in
if/switch/for. Of course, this still means someone could shadow
unsuspectingly with :=, but it provides a safer alternative.

Jessta

unread,
Nov 23, 2010, 5:07:43 PM11/23/10
to Steven, ron minnich, jimmy frasche, Gustavo Niemeyer, Ian Lance Taylor, Ryanne Dolan, golang-nuts
On Wed, Nov 24, 2010 at 7:52 AM, Steven <stev...@gmail.com> wrote:
> Here's a toss out idea, similar to an earlier idea, but I think less
> likely to cause problems:
>
> (var y), err = f();

I like this.
I find it annoying that you can't declare a variable with a type in
the simple statement in a for/if.

- jessta


--
=====================
http://jessta.id.au

Nigel Tao

unread,
Nov 23, 2010, 5:53:07 PM11/23/10
to Steven, golang-nuts
On 24 November 2010 07:52, Steven <stev...@gmail.com> wrote:
> Here's a toss out idea, similar to an earlier idea, but I think less
> likely to cause problems:
>
> (var y), err = f();
>
> This way, you can be explicit about what variables are being newly
> declared without needing to add the extra type declaration before the
> statement. The brackets disambiguate it from the multiple
> declaration/initialization var x, err = f(). This of course means that
> var declarations would need to be allowed as simple statements in
> if/switch/for. Of course, this still means someone could shadow
> unsuspectingly with :=, but it provides a safer alternative.

Another possible syntax for being explicit about which variables are
freshly declared is
$y, err = f()
but as Ian pointed out to me, the $ here would mean something much
different than Perl's or BASIC's $, wheras Go's := is similar to
Pascal's :=.

Gustavo Niemeyer

unread,
Nov 23, 2010, 9:10:59 PM11/23/10
to Nigel Tao, Steven, golang-nuts
> Another possible syntax for being explicit about which variables are
> freshly declared is
> $y, err = f()
> but as Ian pointed out to me, the $ here would mean something much
> different than Perl's or BASIC's $, wheras Go's := is similar to
> Pascal's :=.

If the character used is the only issue, it shouldn't be hard to come
with a more reasonable option.

As an example, suppose we pick the colon itself. Then 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.

jimmy frasche

unread,
Nov 23, 2010, 9:21:04 PM11/23/10
to Gustavo Niemeyer, Nigel Tao, Steven, golang-nuts

In the off chance we are going to introduce a character and as long as
there's a bike to shed, how about ^ for "use the one up there" so:
a := 0
...
{
...
^a, err := f() //return 5, nil
...
}
println(a) //prints 5

Michael MacInnis

unread,
Nov 23, 2010, 9:41:09 PM11/23/10
to golang-nuts
On Tue, Nov 23, 2010 at 9:42 AM, peterGo <go.pe...@gmail.com> wrote:
> Ryanne,

>
>> Why does Go let you redefine an identifier in a child block?
>
> Since Algol, It has been a common feature of block-structured
> programming languages.
>
> "In a block-structured programming language, the names of variables
> and other objects such as procedures which are declared in outer
> blocks are visible inside other inner blocks, unless they are shadowed
> by an object of the same name."
>
> Block (programming)
> http://en.wikipedia.org/wiki/Block_%28programming%29

Long time lurker, First time poster.

What if short variable declarations were not allowed to shadow
variables declared in outer blocks and none of the variables were
required to be new. A short variable declaration could be read as,
"Create any variables in this list that do not exist and assign them
the following values".

i := 1
{
i := 2
}

// Would be nice if the output was '2'.
fmt.Printf("'%v'\n", i)

// Would be convenient if this worked.
i := 3

Michael.

fango

unread,
Nov 23, 2010, 9:46:01 PM11/23/10
to golang-nuts
Dot seems more readable and it means define it HERE. Also can we omit
comma? white space serves as delimiter already.

.field1 .offset = nextField(str, 0)
.field2 offset = nextField(str, offset) // redeclares offset

cheers,
Fango

On Nov 24, 10:21 am, jimmy frasche <soapboxcic...@gmail.com> wrote:

Eoghan Sherry

unread,
Nov 23, 2010, 10:00:02 PM11/23/10
to fango, golang-nuts
On 23 November 2010 21:46, fango <fan.h...@gmail.com> wrote:
> Dot seems more readable and it means define it HERE. Also can we omit
> comma? white space serves as delimiter already.
>
> .field1 .offset = nextField(str, 0)
> .field2  offset = nextField(str, offset)  // redeclares offset
>

That'd cause problems.

s := struct { i int }{}
s .i = 10
s. i += 1
println(s . i)

Eoghan

fango

unread,
Nov 23, 2010, 10:10:13 PM11/23/10
to golang-nuts
I get what you meant. From spec:

"White space, formed from spaces (U+0020), horizontal tabs (U+0009),
carriage returns (U+000D), and newlines (U+000A), is ignored except as
it separates tokens that would otherwise combine into a single token.
"

But what's the rational for that? I always thought they are condensed
to a single space and served as delimiter for tokens.

Thanks,
Fango

On Nov 24, 11:00 am, Eoghan Sherry <ejshe...@gmail.com> wrote:

Scott Pakin

unread,
Nov 23, 2010, 10:23:01 PM11/23/10
to golang-nuts
On Nov 23, 10:06 am, Ian Lance Taylor <i...@google.com> wrote:
> The current situation is not bad but it is not perfect.  Unfortunately I
> don't see a good way to improve it.  I personally don't want to abandon
> block scoping, and I think that having if, for and switch introduce a
> new scope is good.  The redeclaration syntax is useful and widely used.
> The only opening I see here is an adjustment to the meaning of
> redeclaration in an inner scope, but I don't see how to make it useful
> and easy to understand.

So what do you think of Corey Thomasson's comment, "I wouldn't really
mind if it went the other way, so that x, y := _always_ declares both
as new variables"? To me, that seems useful and easy to understand.

jimmy frasche

unread,
Nov 23, 2010, 10:24:29 PM11/23/10
to Scott Pakin, golang-nuts

I may be misremembering but wasn't that how it was originally and it
was changed because it made it difficult to use the a, err idiom?

Eoghan Sherry

unread,
Nov 23, 2010, 10:34:38 PM11/23/10
to fango, golang-nuts
On 23 November 2010 22:10, fango <fan.h...@gmail.com> wrote:
> I get what you meant. From spec:
>
> "White space, formed from spaces (U+0020), horizontal tabs (U+0009),
> carriage returns (U+000D), and newlines (U+000A), is ignored except as
> it separates tokens that would otherwise combine into a single token.
> "
>
> But what's the rational for that? I always thought they are condensed
> to a single space and served as delimiter for tokens.
>

In "s.i" there are three tokens, regardless of white space
(check out 6g -x).
Go's handling of white space is conventional. Spaces have
served their purpose and are discarded once the input has
been tokenized.

Eoghan

Nigel Tao

unread,
Nov 23, 2010, 10:49:20 PM11/23/10
to Gustavo Niemeyer, Steven, golang-nuts
On 24 November 2010 13:10, Gustavo Niemeyer <gus...@niemeyer.net> wrote:
>> Another possible syntax for being explicit about which variables are
>> freshly declared is
>> $y, err = f()
>> but as Ian pointed out to me, the $ here would mean something much
>> different than Perl's or BASIC's $, wheras Go's := is similar to
>> Pascal's :=.
>
> If the character used is the only issue, it shouldn't be hard to come
> with a more reasonable option.

The exact syntax is more or less a trivial bikeshed to paint. Let's
not focus on that.

More importantly, I don't think it would make sense to have both the $
mechanism and the := mechanism for automatically declaring variables,
and while := isn't perfect, I'm not sure if $ is either, and changing
to $ might not be worth the cost of breaking almost all existing Go
code.

Ian Lance Taylor

unread,
Nov 23, 2010, 11:12:01 PM11/23/10
to Scott Pakin, golang-nuts
Scott Pakin <scot...@pakin.org> writes:

I don't think that really addresses the specific area of potential
confusion, which is

a := 1
if a, b := f(); b {
fmt.Println(a) // Prints result of f()
}
fmt.Println(a) // Prints 1

Ian

Steven

unread,
Nov 23, 2010, 11:48:14 PM11/23/10
to Ian Lance Taylor, Scott Pakin, golang-nuts

It does in a way since the problem arises since people get used to not
worrying about whether they are declaring a variable. This rule (which
is basically what we had before) would stop this pattern, so people
are less likely to run into unexpected behavior because they didn't
realize that scoping had changed.

If an alternative form for mixed declaration/reassignment were given
that made which is happening explicit, then people would never be
caught in this trap, regardless of whether they realize the simple
statements in if/for/switch is in a new scope (though perhaps it's
better to leave this a ragged edge so people trip on it, and thus
learn it, early on, rather than stumble on it later, long after they
have the rest of the language squared away). A form where declaration
is explicit (but still short) means the statement means the same thing
regardless of where it is.

Gustavo Niemeyer

unread,
Nov 24, 2010, 2:55:55 AM11/24/10
to Ian Lance Taylor, Scott Pakin, golang-nuts
> I don't think that really addresses the specific area of potential
> confusion, which is
>
>        a := 1
>        if a, b := f(); b {
>                fmt.Println(a)  // Prints result of f()
>        }
>        fmt.Println(a)  // Prints 1

Not entirely sure about what you mean with this. Either way, allowing
individual variables to be marked as new seems to solve all confusion
there.

E.g.

a := 1
if :a, err = f(); err == nil {
...
}
return err

SnakE

unread,
Nov 24, 2010, 2:57:46 AM11/24/10
to Ian Lance Taylor, Ryanne Dolan, golang-nuts
2010/11/23 Ian Lance Taylor <ia...@google.com>
Ryanne Dolan <ryann...@gmail.com> writes:

> More alarmingly (and the source of several bugs in my code):
>
> var foo interface{}
>
> if foo, err := getFoo(); err != nil {
>    // trouble getting foo
> }
> // foo is still nil

I agree that this is confusing.

The rules are fairly simple, but I have to agree that accidentally
typing := when you mean = can trip you up.  The question is whether we
can think of a way to change this without losing useful features of the
language.

* Being able to redeclare variable names in an inner block is, I
 believe, a useful feature.  It makes it easier to use short names in
 nested scopes.  It makes it slightly easier to write, and read,
 machine generated code.  It's conventional in block scoped languages.

This feature is justified in C with its preprocessor.  Without shadowing, writing macros would be a real pain.  In Go on the other hand, the only real use case is machine code generation which can be solved with a smarter generator or careful preparation of the code pieces.

* Being able to redeclare existing variables with := is a useful
 feature.  It permits sequences of "v1, err := x()" which occur often
 in Go code.

I tend to agree.  And if you forbid shadowing variables within functions you can make it even more useful:

    func foo() err {
      x, err := bar() // fine, x is declared
      // ...
      x, err := baz() // fine! x is reused
      // ...
      if err == nil {
        x, err := smth() // that same x is used, you asked for it
      }
      println(x)
    }

There is no confusion because there is exactly one variable with a particular name in a function.  So `x` is always `x` and `err` is always `err` no matter what.  You can refactor this code freely, you can remove the `bar()` call or move it after `baz()` without the need to add or remove those tiny colons in assignments.

yy

unread,
Nov 24, 2010, 4:04:11 AM11/24/10
to jimmy frasche, Gustavo Niemeyer, Nigel Tao, Steven, golang-nuts
2010/11/24 jimmy frasche <soapbo...@gmail.com>:

>
> In the off chance we are going to introduce a character and as long as
> there's a bike to shed, how about ^ for "use the one up there" so:
>  a := 0
>  ...
>  {
>    ...
>    ^a, err := f() //return 5, nil
>    ...
>  }
>  println(a) //prints 5
>

There are two possible interpretations of "the one up there", with
their pros and cons:

- The one declared outside this block: then you have some new
possibilities, like using ^a to refer to the outside variable even if
you are re-declaring it inside this block. However, if you use a, err
:= f() (instead of ^a) it won't be an error, so we will still have
difficult to find bugs.

- A previously declared variable: then, I suppose ^ would always be
required to re-define variables and the compiler would give an error
if you try to use := with an existing variable. This explicitness
would avoid subtle bugs, but you would need to change a lot of
existing code and the , err idiom would become the (uglier) , ^err
idiom.

I think there is no perfect solution to this problem. If you want to
keep the (very useful) comma-err idiom then := will be unpredictable
without context. It will probably have to be a compromise solution and
I'm not sure the actual behavior is worse than any of the ideas seen
on this thread.

--
- yiyus || JGL . 4l77.com

Mateusz Czapliński

unread,
Nov 24, 2010, 4:12:12 AM11/24/10
to golang-nuts
On Nov 23, 9:52 pm, Steven <steven...@gmail.com> wrote:
> Here's a toss out idea, similar to an earlier idea, but I think less
> likely to cause problems:
>
> (var y), err = f();

A similar one also came to my mind in the meantime, but actually with
opposite semantics:

a, err := foo1()
b, (err) := foo2() // reusing 'err'
x, y, (b, err) := foo3() // reusing 'b' and 'err'

Before you throw "bikeshedding" at me, please note, that I'm not going
to argue over my proposal - thus please consider the word
"brainstorming" as a hopefully more adequate alternative.

greetz
Mateusz Czapliński

roger peppe

unread,
Nov 24, 2010, 4:21:53 AM11/24/10
to Gustavo Niemeyer, Nigel Tao, Steven, golang-nuts
On 24 November 2010 02:10, Gustavo Niemeyer <gus...@niemeyer.net> wrote:
>> Another possible syntax for being explicit about which variables are
>> freshly declared is
>> $y, err = f()
>> but as Ian pointed out to me, the $ here would mean something much
>> different than Perl's or BASIC's $, wheras Go's := is similar to
>> Pascal's :=.
>
> If the character used is the only issue, it shouldn't be hard to come
> with a more reasonable option.
>
> As an example, suppose we pick the colon itself. Then 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.

+1 for that.

i was just about to write a post suggesting the same thing
when i saw your post, which is perhaps a sign that
it doesn't feel too unnatural.

> 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.

if this was added to the language, i'm sure partial declarations should go, yes.

jimmy frasche

unread,
Nov 24, 2010, 1:47:10 PM11/24/10
to yy, Gustavo Niemeyer, Nigel Tao, Steven, golang-nuts
On Wed, Nov 24, 2010 at 1:04 AM, yy <yiyu...@gmail.com> wrote:
> 2010/11/24 jimmy frasche <soapbo...@gmail.com>:
>>
>> In the off chance we are going to introduce a character and as long as
>> there's a bike to shed, how about ^ for "use the one up there" so:
>> a := 0
>> ...
>> {
>> ...
>> ^a, err := f() //return 5, nil
>> ...
>> }
>> println(a) //prints 5
>>
>
> There are two possible interpretations of "the one up there", with
> their pros and cons:
>
> - The one declared outside this block: then you have some new
> possibilities, like using ^a to refer to the outside variable even if
> you are re-declaring it inside this block. However, if you use a, err
> := f() (instead of ^a) it won't be an error, so we will still have
> difficult to find bugs.
>
> - A previously declared variable: then, I suppose ^ would always be
> required to re-define variables and the compiler would give an error
> if you try to use := with an existing variable. This explicitness
> would avoid subtle bugs, but you would need to change a lot of
> existing code and the , err idiom would become the (uglier) , ^err
> idiom.

I was certainly thinking the latter. It would be a rather big backward
incompatiable change but working around the current rules when they
get in your way is, to me, more annoying than being explicit or
changing the hundred lines of code that would be effected by the
change. I find it particularly annoying in this case:

func f() (err os.Error) {
if true {
a, e := g() //can't shadow named return
if e != nil {
err = e
return
}
}
...
}
or:
func f() (err os.Error) {
if true {
a, err := g() //oops we shadowed err
if err != nil {
return err //have to circumvent named return value
}
...
}
}
and this seems clearer to me:
func f() (err os.Error) {
if true {
a, ^err := g() //no shadow
if err != nil {
return //get to use named return now, everything explicit and clean
}
...
}
}

> I think there is no perfect solution to this problem. If you want to
> keep the (very useful) comma-err idiom then := will be unpredictable
> without context. It will probably have to be a compromise solution and
> I'm not sure the actual behavior is worse than any of the ideas seen
> on this thread.

Agreed. Mostly I'd just rather see ^x than $x or :x if such a change
is made (though the argument against $ being different than it is in
other languages would apply equally well to ^ especially since Go is
in the pascal extended family but then again perhaps less because
languages with $ sigils are fare more prolific than ones with ^ as
dereferencers)

I have absolutely zero problems with shadowing but the interaction
between shadowing and :=, even with the reuse existant variables in
this scope rule can be troublesome and when it is none of the
solutions feel very elegant and I always feel like I am in some way
working against the language to express myself.

Steven

unread,
Nov 24, 2010, 3:14:23 PM11/24/10
to jimmy frasche, yy, Gustavo Niemeyer, Nigel Tao, golang-nuts
On Wed, Nov 24, 2010 at 1:47 PM, jimmy frasche <soapbo...@gmail.com> wrote:
Agreed. Mostly I'd just rather see ^x than $x or :x if such a change
is made (though the argument against $ being different than it is in
other languages would apply equally well to ^ especially since Go is
in the pascal extended family but then again perhaps less because
languages with $ sigils are fare more prolific than ones with ^ as
dereferencers)

I'm the opposite. I think marking declarations is more intuitive and in line with current Go than marking reuse. : already has implications as a declaration operator, since, when combined with =, produces "declare equal", just as other operators are combined with =, so it seems preferable. $ just doesn't match well with normal characters. It really stands out, so it seems excessive to use for something as common as declaration. I'd only use it for something that stands somewhat outside the regular code, since its extremely easy to spot. It also is strongly associated with a bunch of concepts in other languages that are properly analogous to this usage, so it would more likely spark confusion.

jimmy frasche

unread,
Nov 24, 2010, 3:50:19 PM11/24/10
to Steven, yy, Gustavo Niemeyer, Nigel Tao, golang-nuts
On Wed, Nov 24, 2010 at 12:14 PM, Steven <stev...@gmail.com> wrote:
>
>
> On Wed, Nov 24, 2010 at 1:47 PM, jimmy frasche <soapbo...@gmail.com>
> wrote:
>>
>> Agreed. Mostly I'd just rather see ^x than $x or :x if such a change
>> is made (though the argument against $ being different than it is in
>> other languages would apply equally well to ^ especially since Go is
>> in the pascal extended family but then again perhaps less because
>> languages with $ sigils are fare more prolific than ones with ^ as
>> dereferencers)
>
> I'm the opposite. I think marking declarations is more intuitive and in line
> with current Go than marking reuse. : already has implications as a
> declaration operator, since, when combined with =, produces "declare equal",
> just as other operators are combined with =, so it seems preferable.

As far as,
:a, b, :c = f()
vs
a, ^b, c := f()

I prefer the latter because it introduces the least changes. You keep
:= and only mark the variable that is the exception.

Of course the actually important question isn't what color to paint
the bikeshed but do we have enough bikes that we need another shed. I
think it's something to consider. I don't think we can get rid of
confusing edge cases. There's always going to be one or two to get
stuck on, but in my adventures with Go I've been cut by the current :=
edges enough times to consider something like this change, though I'd
have to go back and make quite a few changes to my various projects. I
certainly like making things more explicit.

> $ just
> doesn't match well with normal characters. It really stands out, so it seems
> excessive to use for something as common as declaration. I'd only use it for
> something that stands somewhat outside the regular code, since its extremely
> easy to spot. It also is strongly associated with a bunch of concepts in
> other languages that are properly analogous to this usage, so it would more
> likely spark confusion.

I don't know if I like the confusion argument in general. It may be
initially surprising to some people but once you get it, you get it
and that's that. I understand where the apprehension comes from,
though, and maybe it's worth it just to avoid getting the same e-mail
every month from a confused newcomer.

Scott Pakin

unread,
Nov 24, 2010, 3:54:16 PM11/24/10
to golang-nuts
That sounds pretty reasonable.

Russ Cox

unread,
Nov 29, 2010, 1:19:05 PM11/29/10
to golan...@googlegroups.com
On Monday, November 22, 2010 10:44:50 PM UTC-8, Ryanne Dolan wrote:
Why does Go let you redefine an identifier in a child block?

Why wouldn't it?

The "you should never redefine an identifier in a child block"
meme was popularized by Java as part of its safety scissors
approach to language design.  You're really asking "why is Go
different from Java" and again the answer is "why wouldn't it be?".

Russ

Ryanne Dolan

unread,
Nov 29, 2010, 3:05:42 PM11/29/10
to r...@golang.org, golang-nuts
Russ,

"You're really asking "why is Go
different from Java""

If my question had been "why does 2 + 2 = 22 in SomeLanguage?", your response would have been equally constructive.

Clearly, a deliberate decision was made to allow shadowing of identifiers within child blocks.  The only valid reason I can think for this decision is "because many other languages do", which is equally relevant as "because Java does the opposite".  Can you provide a valid reason to allow this that can't be solved by just changing the variable name?

On the other hand, I can think of several reasons _not_ to allow shadowing of variables.  Relevant to the current discussion is that the following code does not behave as expected:

var foo Something
if foo, err := getFoo(); err != nil {
...
}

Please note that I am not arguing against block-level scope, just against shadowing of identifiers within the same function.  Specifically, I'm arguing against shadowing of identifiers by the := operator, which currently has surprising behavior, in my opinion.

Of course, you could say "well that's according to spec; that's just how Go is"... but I am wondering if there is a good reason for the current semantics in the face of the problems I've highlighted (and others).

Thanks.
Ryanne

--
www.ryannedolan.info

Ryanne Dolan

unread,
Nov 29, 2010, 3:11:53 PM11/29/10
to SnakE, Ian Lance Taylor, golang-nuts
SnakE,

func foo() err {
      x, err := bar() // fine, x is declared
      // ...
      x, err := baz() // fine! x is reused
      // ...
      if err == nil {
        x, err := smth() // that same x is used, you asked for it
      }
      println(x)
    }

Yeah, this is how I was writing Go for a few days and got frustrated trying to hunt down bugs, which amounted to extra colons in some places.  It kinda defeats the purpose of the := operator, in my opinion, when you need to be deliberate with it, as it seems to be meant for convenience, not confusion.

Thanks.
Ryanne

--
www.ryannedolan.info

Cory Mainwaring

unread,
Nov 29, 2010, 3:25:39 PM11/29/10
to Ryanne Dolan, r...@golang.org, golang-nuts
On 29 November 2010 15:05, Ryanne Dolan <ryann...@gmail.com> wrote:
> var foo Something
> if foo, err := getFoo(); err != nil {
> ...
> }

That case has very little to do with the := operator and very much to
do with a misunderstanding of the scope of statements after if and
before the conditional. It's quite similar to one expecting the
following to work like it does in other languages:

if err!= nil
{
//...
}

It doesn't, rather having the if block always be called, because the
compiler translates it to the equivalent of:

if err != nil; true {
//...
}

All of this has very little to do with braces and semi-colons, and
very much to do with a change that was made to the if statement that
differs from other languages. The if statement may have a statement
before the conditional, delimited by a semi-colon, that is in the same
scope as the if block, such that:

if err := blar(x); err != nil { //... }

Works conveniently.

In short, this has little to do with the := operator, so stop blaming it.

Eoghan Sherry

unread,
Nov 29, 2010, 3:38:27 PM11/29/10
to Ryanne Dolan, r...@golang.org, golang-nuts
On 29 November 2010 15:05, Ryanne Dolan <ryann...@gmail.com> wrote:
> On the other hand, I can think of several reasons _not_ to allow shadowing
> of variables.  Relevant to the current discussion is that the following code
> does not behave as expected:
> var foo Something
> if foo, err := getFoo(); err != nil {
> ...
> }
> Please note that I am not arguing against block-level scope, just against
> shadowing of identifiers within the same function.  Specifically, I'm
> arguing against shadowing of identifiers by the := operator, which currently
> has surprising behavior, in my opinion.

I'd like to see things move in the other direction by allowing names to
be redeclared regardless of previous declarations. For instance,

a := 1
f := func() int { return a }
a := "2"
println(f(), a)

would declare two variables named a, the second hiding the first, and
print 1 2.

This is what ML-like languages do and the semantics are clear.
In fact, changing only two lines in src/gc seems to make it all work.

Eoghan

Ryanne Dolan

unread,
Nov 29, 2010, 3:40:50 PM11/29/10
to Cory Mainwaring, r...@golang.org, golang-nuts
Cory,

I fully understand Go's block-level scope, semi-colon insertion rules, if-statements, etc, but I thought that := worked one way, when it worked another way.  In fact, I've written a lot of Go code at this point (I've been using it as my primary language for almost a year), and I never noticed that := worked differently than I expected.

The fact that you think the confusion comes from somewhere else (which several people have assumed) just serves to strengthen my argument that the current semantics are goofy.  If := changed, or if shadowing were disallowed, my mental model of how Go works would continue to be useful.

If you are a beginning Go programmer, it is easy to make the mistake I made, as well as the ones you mentioned.  Anytime code does something entirely different than what it clearly indicates... there is a major problem.  I bet if you showed that code to 100 programmers, 100 would misread it.  As in my case, even comparatively experienced Go programmers might misread it.

Thanks.
Ryanne

--
www.ryannedolan.info

munificent

unread,
Nov 29, 2010, 4:18:38 PM11/29/10
to golang-nuts
On Nov 29, 10:19 am, Russ Cox <r...@golang.org> wrote:
> The "you should never redefine an identifier in a child block"
> meme was popularized by Java as part of its safety scissors
> approach to language design. You're really asking "why is Go
> different from Java" and again the answer is "why wouldn't it be?".

A response like that is unhelpful and honestly comes across to me as
defensive.

Shadowing a variable is right on the line where it's unclear what the
user's intent is. Sometimes it's a mistake, sometimes it's
intentional. A language designer, to make the semantics unambiguous,
has to pick a single interpretation. Java picks that it's a mistake
and flags it as an error. Go, (and C and C++) do not.

In Go's case, I'm guessing the rationale was "that's what C does". By
default, unless there's a compelling reason otherwise, Go seems to
follow the C path.

In Java's case, it's pretty easy to see why they would choose
otherwise: Java doesn't have global variables. C really needs to allow
shadowing because global variables combined with a penchant for really
short identifiers and no namespaces means lots of really useful names
are polluting the global scope. If C didn't allow shadowing, then an
addition to some random header you happen to be including (directly or
indirectly) could cause *your* code to spontaneously stop compiling if
they added an identifier you happened to be using locally.

Java doesn't have a global variable scope, so it doesn't have that
problem. If you're shadowing in Java, the odds are *much* greater that
it's a mistake, so not shadowing there is a reasonable design choice
to help users write error-free code. (And, of course, Go does make
"safety scissors" design decisions like these too, like not allowing
unused variables.)

As far as Go is concerned, it could go either way. Go does h