Scope of variable redeclarations

160 views
Skip to first unread message

Brian Candler

unread,
Jan 14, 2026, 7:36:34 AM (4 days ago) Jan 14
to golang-nuts
I'm having trouble understanding something in the go spec for short variable (re)declarations.  The spec says:

"redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original."

It's that last sentence that I'm having problems squaring with observed behaviour.


package main

import "fmt"

func myfunc() (int, error) {
return 123, nil
}

func main() {
var code int
if code, err := myfunc(); err != nil {   // line 11
fmt.Printf("Error: %s", err)
return
}
fmt.Printf("code is %d", code)
}

This gives a compile error:

./prog.go:11:5: declared and not used: code

Adding a line to workaround this gives Example 2: https://go.dev/play/p/TVfTCc2bjAO

func main() {
var code int
if code, err := myfunc(); err != nil {
fmt.Printf("Error: %s", err)
_ = code
return
}
fmt.Printf("code is %d", code)
}

This prints 0, not 123.

These examples seem to show that the variable "code" (re)declared in the if-statement is a completely new variable which is local to the if block only.

(Aside: what I was actually trying to achieve is to tidy some program logic so that the "err" variable is local to the block - and hence can't accidentally be bound to later on in the function - whilst making the "code" value persist beyond the block where it can be used later)

Jan Mercl

unread,
Jan 14, 2026, 7:52:22 AM (4 days ago) Jan 14
to Brian Candler, golang-nuts
On Wed, Jan 14, 2026 at 1:36 PM 'Brian Candler' via golang-nuts
<golan...@googlegroups.com> wrote:

> I'm having trouble understanding something in the go spec for short variable (re)declarations. The spec says:

I believe the code is the equivalent of
https://go.dev/play/p/y96Q3YWjI-y. Which fails the same way, but IMO
not/less surprisingly, because the second 'code' variable is declared
in a different scope than the first one.

And it is a separate block because the specs says
(https://go.dev/ref/spec#Blocks)

""""
4. Each "if", "for", and "switch" statement is considered to be in its
own implicit block.
""""

-j

Def Ceb

unread,
Jan 14, 2026, 7:53:34 AM (4 days ago) Jan 14
to golang-nuts
This is a better example: https://go.dev/play/p/MRvbZLGB2Bi
If you now change the function signature to return (int32, error), then it will complain about a type mismatch.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/37978205-dafe-40e3-bf6d-f3f55aff7463n%40googlegroups.com.

Brian Candler

unread,
Jan 14, 2026, 7:54:26 AM (4 days ago) Jan 14
to golang-nuts
Hmm, but this prints 123:

func main() {
var code int
code, err := myfunc()
if err != nil {

fmt.Printf("Error: %s", err)
return
}
fmt.Printf("code is %d", code)
}

OK, so it must be to do with "if" declaring a new block scope:

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

Brian Candler

unread,
Jan 14, 2026, 8:53:29 AM (4 days ago) Jan 14
to golang-nuts
OK, it's a block scope thing, because using an "if" statement I can also violate the rule that "at least one of the non-blank variables is new".

func main() {
var code int
var err error
if code, err := myfunc(); err != nil {  // this is accepted

fmt.Printf("Error: %s", err)
_ = code
return
}
fmt.Printf("code is %d, err is %v", code, err)  // 0, <nil>
}

So I need to imagine a curly bracket enclosing the whole 'if' statement.

Sorry for the noise.

Robert Engels

unread,
Jan 14, 2026, 11:44:59 AM (3 days ago) Jan 14
to Brian Candler, golang-nuts
Not noise. You highlight a very dumb rule of the syntax in my opinion. The mixing of new and old declarations with new variables possibly being introduced or old ones shadowed. 

I know it won’t/can’t change - but took the opportunity to rant :)

On Jan 14, 2026, at 7:54 AM, 'Brian Candler' via golang-nuts <golan...@googlegroups.com> wrote:

OK, it's a block scope thing, because using an "if" statement I can also violate the rule that "at least one of the non-blank variables is new".
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Jan Mercl

unread,
Jan 14, 2026, 11:48:01 AM (3 days ago) Jan 14
to Robert Engels, Brian Candler, golang-nuts
On Wed, Jan 14, 2026 at 5:44 PM Robert Engels <ren...@ix.netcom.com> wrote:

> You highlight a very dumb rule of the syntax in my opinion. The mixing of new and old declarations with new variables possibly being introduced or old ones shadowed.

It's not dumb because it does not do what you wrote it does.

Robert Engels

unread,
Jan 14, 2026, 12:10:44 PM (3 days ago) Jan 14
to Jan Mercl, Brian Candler, golang-nuts
Yes it does. You can declare new variables while assigning to old in Go.

A very poor design choice imo.

> On Jan 14, 2026, at 10:47 AM, Jan Mercl <0xj...@gmail.com> wrote:

Jan Mercl

unread,
Jan 14, 2026, 12:23:48 PM (3 days ago) Jan 14
to Robert Engels, Brian Candler, golang-nuts
On Wed, Jan 14, 2026 at 6:09 PM Robert Engels <ren...@ix.netcom.com> wrote:

> Yes it does. You can declare new variables while assigning to old in Go.

No it does not AFAICT. You wrote:

""""
... mixing of new and old declarations with new variables possibly
being introduced or old ones shadowed.
""""

Please show a playground program where this happens. Note: shadowing
can happen only across blocks. The rules for short variable
declarations apply only within one block.

Caveat emptor: Go specs do not define the term "shadowing". So you may
be using a very different definition of shadowing than most other
people have in mind when talking about shadowing variables in Go.

Robert Engels

unread,
Jan 14, 2026, 12:38:39 PM (3 days ago) Jan 14
to Jan Mercl, Brian Candler, golang-nuts
Yes, I was using shadowing loosely.

When you have

var x int
… 100 lines of code …
x,y := somefunc()

And y is a new variable - you have no idea at the call site which is a new variable and which is not.

That is the “shadowing” I’m referring to - loosely in that it obscures what is actually happening at the call site. You need to know other context - breaking Go’s desire for explicitness.

> On Jan 14, 2026, at 11:23 AM, Jan Mercl <0xj...@gmail.com> wrote:

Mike Schilling

unread,
Jan 15, 2026, 6:37:59 PM (2 days ago) Jan 15
to golang-nuts
It's handy behavior, because it let you write

  x , err := method1()
  if err !=nil {
    ...
  }
  y , err := method2()
  if err !=nil {
    ...
  }
instead of having to have multiple error variables.

Robert Engels

unread,
Jan 15, 2026, 6:45:45 PM (2 days ago) Jan 15
to Mike Schilling, golang-nuts
No doubt, but it also increases the cognitive load at the call site - which is sort of against Go’s desire for explicitness. 

On Jan 15, 2026, at 5:38 PM, Mike Schilling <mike.s.s...@gmail.com> wrote:

It's handy behavior, because it let you write
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Mike Schinkel

unread,
Jan 15, 2026, 10:36:31 PM (2 days ago) Jan 15
to Robert Engels, Mike Schilling, golang-nuts
On Jan 14, 2026, at 11:43 AM, Robert Engels <ren...@ix.netcom.com> wrote:
I know it won’t/can’t change - but took the opportunity to rant :)

I will second this rant.  

That behavior has resulted in more bugs — that I have personally seen — than I can count on all my digits. :-(


On Jan 15, 2026, at 6:37 PM, Mike Schilling <mike.s.s...@gmail.com> wrote:
It's handy behavior, because it let you write

  x , err := method1()
  if err !=nil {
    ...
  }
  y , err := method2()
  if err !=nil {
    ...
  }
instead of having to have multiple error variables.

Shadowed err variables is where shadowing has bitten me the worst. I recently wrote a linter to flag ALL variable shadowing, especially err.

On Jan 15, 2026, at 6:44 PM, Robert Engels <ren...@ix.netcom.com> wrote:
No doubt, but it also increases the cognitive load at the call site - which is sort of against Go’s desire for explicitness. 

THIS.

-Mike

Robert Engels

unread,
Jan 16, 2026, 12:36:08 AM (yesterday) Jan 16
to Mike Schinkel, Mike Schilling, golang-nuts
Not that the best software is designed by consensus but I appreciate the support Mike. I like Go. Nothing is perfect. It’s amazing they were able to add generics in a backwards compatible manner. But I long for a Go 2.0 where they broke compatibility and fixed some critical errors that I suspect the core Go is well aware of. 

On Jan 15, 2026, at 9:36 PM, Mike Schinkel <mi...@newclarity.net> wrote:



Patrick Smith

unread,
Jan 16, 2026, 1:38:02 AM (yesterday) Jan 16
to Robert Engels, Mike Schinkel, Mike Schilling, golang-nuts
On Thu, Jan 15, 2026 at 9:35 PM Robert Engels <ren...@ix.netcom.com> wrote:
Not that the best software is designed by consensus but I appreciate the support Mike. I like Go. Nothing is perfect. It’s amazing they were able to add generics in a backwards compatible manner. But I long for a Go 2.0 where they broke compatibility and fixed some critical errors that I suspect the core Go is well aware of. 

This one could be fixed without breaking any existing code. For example, we might say that, on the left side of a plain assignment, prefixing an identifier with an @ sign means the identifier is being declared there, as if by :=. The example above would become

  @x , @err = method1()
  if err !=nil {
    ...
  }
  @y , err = method2()   // Using existing err, so not @err
  if err !=nil {
    ...
  }

I suspect this wouldn't fly... but if the problem bothers enough people sufficiently, something could be done.

Brian Candler

unread,
Jan 16, 2026, 3:58:15 AM (yesterday) Jan 16
to golang-nuts
On Thursday, 15 January 2026 at 23:37:59 UTC Mike Schilling wrote:
It's handy behavior, because it let you write

  x , err := method1()
  if err !=nil {
    ...
  }
  y , err := method2()
  if err !=nil {
    ...
  }
instead of having to have multiple error variables.

There are awkward cases though. For example, suppose you had declared x and y previously, but not err. Then you are forced to make it asymmetrical:

  x , err := method1()
  ...
  y , err = method2()
  ...

And if you write the similar-looking but more compact version:

  if x , err := method1(); err != nil {
    ...
  }

this has completely different semantics.
Reply all
Reply to author
Forward
0 new messages