Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why a short variable declaration in an else if statement doesn't fail to compile even though no new variable is being defined on the left side?

The following code is expected to fail with an error on compilation:

package main

import (
    "fmt"
)

func main() {
    x := 10
    x := x + 1
    fmt.Println(x)
}

The compilation error is:

./prog.go:9:4: no new variables on left side of :=

So I was expecting this code to also fail with error:

package main

import (
    "fmt"
)

func main() {
    if x := 10; x < 10 {
        fmt.Println("if block: x:", x)
    } else if x := x + 1; x < 20 {
        fmt.Println("else if block: x: ", x)
    }
}

Here is the output:

else if block: x:  11

Why does the second program succeed even though the := operator in else if x := x + 1 is not defining any new variable?

like image 802
Lone Learner Avatar asked Dec 11 '22 00:12

Lone Learner


2 Answers

From the Go specs, here is how an if statement is defined:

IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .

Later on, in the Declarations and Scope sections it is said:

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.

Now, the if statement is an implicit block:

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

Then as you can see from the IfStmt definition, after the keyword else may come:

  • a Block, i.e. else { /* code */ }
  • an IfStmt again, as in your case, i.e. else if /* statement */ { /* code */ }. This means the IfStmt is recursive, and it is an implicit block within another IfStmt (still an implicit block). Therefore it meets the condition for redeclaring the identifier.

Compare also with explicit blocks:

func foo() {
    x := 10
    {
        x := 20
        fmt.Println("Inner x:", x) // 20
    }
    fmt.Println("Outer x:", x) // 10
}
like image 194
blackgreen Avatar answered Dec 12 '22 13:12

blackgreen


You're dealing with two different x variables due to your second x := being in another scope. So it looks like the same x to you, but it's not, even though it is based off the value of the outer x, it doesn't affect it. The x you are printing is the inner x.

Consider this example:

package main

import (
    "fmt"
)

func main() {

    // Define x in the func scope
    x := 10
    // Print out global scope x
    fmt.Printf("x1:%v\n", x)

    // Not allowed (x already defined in this scope)
    //x := x + 1

    // Allowed (different x)
    {
        x := x + 1
        // Print new local scope x (this is a second x)
        fmt.Printf("x2:%v\n", x)

    }

    // Allowed (different x defined)
    if x := x + 1; x > 10 {
        // Print new local scope x (this is a third x)
        fmt.Printf("x3:%v\n", x)
    }

    // Print out global scope x
    fmt.Printf("x1:%v\n", x)
}

In this example you have 3 x variables. The func level one, the first scoped one within {}, then another one (independent again) within the if block. All three of these are independent, and the two inner ones shadow the outer one after they are defined (within that scope), so even if you choose to base x 2 and 3 off the initial x, they don't impact its value.

You can see this as you print out the global scope x at the end, and because x3 is not affected by the value of x2, so we end up at the end of the function:

  • x1: 10
  • x2: 11
  • x3: 11
like image 20
Kenny Grant Avatar answered Dec 12 '22 14:12

Kenny Grant