Golang mixed assignment and declaration
Asked Answered
U

4

31

I started working with go for a few weeks, and (once again) I stumbled across something that seems odd for me:

// Not working
a := 1
{
    a, b := 2, 3
}

// Works
a := 1
a, b := 2, 3

playground

I want to assign two variables simultaneously. One is already declared, in a superior scope, the other one is not.

It does not work: the compiler tries to redeclare the former variable. However, it works fine if this variable is declared in the same scope.

Why is that?

Unlimited answered 20/5, 2016 at 16:1 Comment(3)
prog.go:11: a declared and not usedBeeswax
I use it just below the brackets : fmt.Println(a). I don't understand why a is redeclared inside the brackets.Unlimited
Possible duplicate of "declared and not used" ErrorMustachio
A
26

What you're experiencing is commonly known as "variable shadowing". When you use := with any variable in an inner scope, including in statements like if and for despite the lack of braces, a new value and type are associated with that variable:

n := "Example"
//Prints the string variable `n` to standard output and
// returns the number of bytes written in int variable `n` and
// an error indicator in error variable `err`.
if n, err := fmt.Println(n); err != nil {
    panic(err)
} else {
    fmt.Println(n, "bytes written")
}

//Prints the string variable `n` to standard output.
fmt.Printf("n = %q\n", n)

Output:

Example
8 bytes written
n = "Example"

There are a few different ways to fix the issue:

  • declare the variables you need before they're used and use normal assignment with =
  • use different variable names
  • create a new scope and save the variable's value for later access, use the variable name with := as you wanted, and before the scope ends, restore the value; it's normally easier to just use different variable names since you're creating another variable anyway

The opposite effect can also occur, where you declare something in an inner scope and don't realize it:

if _, err := fmt.Println(n); err != nil {
    panic(err)
} else {
    fmt.Println(n, "bytes written")
}

//undefined: err
if _, err = fmt.Println(n); err != nil {
    //undefined: err
    panic(err)
}

There are, again, a few different ways to fix this issue:

  • declare the variables you need before they're used and use normal assignment with =
  • separate the first := and if statement, so the variable is declared as intended; this allows you to use = for all other instances of that variable in the context of that scope and any scopes in which it's enclosed
  • change all instances of = to := to fix the error

Note that you may encounter the variable shadowing issue in any of the last two cases when a function returns multiple values, but that can be resolved as explained above.

Try both examples on the Go Playground.

Your last example illustrates the combination of declaring and initializing a new variable b while also assigning a value to the existing variable a. No new scope is created, so you're not shadowing the original variable a, which you can verify by printing the address of a after each assignment (but before the next declaration/assignment):

a := 1
fmt.Println(&a)
a, b := 2, 3
fmt.Println(&a)
a = b          // avoids a "declared but not used" error for `b`

Of course, if you didn't declare b, then you'd receive an error from the compiler that there are no new variables on the left side of := for the second declaration, which is a roundabout way of saying that you're trying to declare a twice in the same scope.

Note that this idea, if applied carefully, can also be used to find variables that are shadowed. For example, the "not working" code in your example would print different addresses for a, depending on whether the a inside the inner scope has been declared yet or not:

a := 1
{
    fmt.Println(&a)    // original `a`
    a, b := 2, 3
    fmt.Println(&a)    // new `a`
    a = b              // avoids a "declared but not used" error for `b`
}
fmt.Println(&a)        // original `a`
Athey answered 21/5, 2016 at 4:42 Comment(2)
The last part of the example in the question uses := twice on what looks like to be the same scope (no new braces or indentation introduced). Your answer explains nicely what happens when a new scope is added with conditionals or loops (if or for), but what about two consecutive declarations at the same level? Does a, b := 2, 3 shadow the previous statement's a declaration for the rest of the block?Overstretch
@Overstretch Updated to address that concern. Thank you for spotting my oversight.Athey
D
9

According to the golang the documentation:

An identifier declared in a block may be redeclared in an inner block.

That's exactly what your example is showing, a is redeclared within the brackets, because of the ':=', and is never used.

A solution is to declare both variable and then use it:

var a, b int
{
    b, a = 2, 3
    fmt.Println(b)
}
fmt.Println(a)
Diclinous answered 20/5, 2016 at 16:22 Comment(2)
Well, it doesn't always make sense to me, in this case I don't use b outside of the inner block, so it's more logical to declare it inside. Or else I could declare a temporary variable and assign a to it... Both solutions are odd to me.Unlimited
Yes sur, if b is only used within a scope, so it has to be declared in this scope. But, in case you need b outside the scope, then declare it before. Anyway, in the first place, you shouldn't declare inline blocks, there is no reason to do it, or I'm missing something?Diclinous
N
3

your Question has 2 parts:
first part:
= is just assignment
:= is define and assign for new vars(at least one new var) inside the function block(not global), working sample:

package main

import (
    "fmt"
)

func main() {
    var u1 uint32      //declare a variable and init with 0
    u1 = 32            //assign its value
    var u2 uint32 = 32 //declare a variable and assign its value at once
    //declare a new variable with defining data type:
    u3 := uint32(32)        //inside the function block this is equal to: var u3 uint32 = 32
    fmt.Println(u1, u2, u3) //32 32 32
    //u3 := 20//err: no new variables on left side of :=
    u3 = 20
    fmt.Println(u1, u2, u3)       //32 32 20
    u3, str4 := 100, "str"        // at least one new var
    fmt.Println(u1, u2, u3, str4) //32 32 100 str
}

second part:
An identifier declared in a block may be redeclared in an inner block.
Here 4 different working samples for Variable scoping and shadowing:

simple way to limit variables scope:

package main
import "fmt"
func main() {
    i := 1
    j := 2
    //new scope :
    {
        i := "hi" //new local var
        j++
        fmt.Println(i, j) //hi 3
    }
    fmt.Println(i, j) //1 3
}

limit variable scope using function calls:

package main
import "fmt"
func fun(i int, j *int) {
    i++                //+nice: use as local var without side effect
    *j++               //+nice: intentionally use as global var
    fmt.Println(i, *j) //11 21
}
func main() {
    i := 10 //scope: main
    j := 20
    fun(i, &j)
    fmt.Println(i, j) //10 21
}

using short-hand assignment inside statements:

package main
import "fmt"
func main() {
    i := 10 //scope: main
    j := 4
    for i := 'a'; i < 'b'; i++ {
        fmt.Println(i, j) //97 4
    }
    fmt.Println(i, j) //10 4

    if i := "test"; len(i) == j {
        fmt.Println(i, j) // i= test , j= 4
    } else {
        fmt.Println(i, j) //test 40
    }
    fmt.Println(i, j) //10 4
}

shadowing global vars:

package main
import "fmt"
var i int = 1 //global
func main() {
    j := 2
    fmt.Println(i, j) //1 2
    i := 10           //Shadowing global var
    fmt.Println(i, j) //10 2
    fun(i, j)         //10 2
}
func fun(i, j int) {
    //i := 100   //no new variables on left side of :=
    fmt.Println(i, j) //10 2
}
Nganngc answered 21/5, 2016 at 8:22 Comment(0)
B
1

In short: as a, b = 2, 3 means "assign both" and a, b := 2, 3 means "declare and assign both", and you need to assign one and declare and assign the other, the solution is to declare the other and assign both:

a := 1
{
    var b int
    a, b = 2, 3
}
Buxom answered 3/5, 2020 at 10:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.