Search code examples
gosyntaxgrammarsemantics

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?


Solution

  • 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
    }