2
votes

I've stumbled across a strange issue where the code below fails to compile:

func main() {
    var val reflect.Value
    var tm time.Time

    if tm, err := time.Parse(time.RFC3339, "2018-09-11T17:50:54.247Z"); err != nil {
        panic(err)
    }
    val = reflect.ValueOf(tm)

    fmt.Println(val, tm, reflect.TypeOf(tm))
}

with the error (the code is what linter recommends).:

$ go run main.go
# command-line-arguments
./main.go:13:5: tm declared and not used

Note the tm variable is indeed used.

If however I add an else block - everything compiles as expected:

func main() {
    var val reflect.Value
    var tm time.Time

    if tm, err := time.Parse(time.RFC3339, "2018-09-11T17:50:54.247Z"); err != nil {
        panic(err)
    } else {
        val = reflect.ValueOf(tm)
    }

    fmt.Println(val, tm, reflect.TypeOf(tm))
}

This looks like a bug in the compiler or perhaps a known issue? Any idea? (I'm using go 1.11)

edit: to all respondends so far. As per: https://golang.org/ref/spec#Short_variable_declarations

Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new. As a consequence, 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.

2
whoever left a downvote - can you please elaborate why? Enlighten meAlex
WRT the quote from GO docs: critical keyword here is 'same block'. if opens a new nested block, so this doesn't apply (see my answer for a more detailed explanation).Leo K

2 Answers

10
votes

This part:

if tm, err := time.Parse(...)

creates a new variable tm that has scope only within the if statement - it is NOT the one you declared as var tm time.Time.

This new variable is not used within the if, therefore you get the error. Note you also don't get the outer-level tm assigned, so fmt.Println will print the zero time, not what time.Parse returned.

To fix this: declare err and change your if to read:

var err error
if tm, err = time.Parse(...)

NOTE this is a subtle thing in GO and a fairly common source of mistakes. The := statement can in fact be used with a mix of variables that are already declared and one or more new variables - if the already-declared ones are in the same lexical scope. Then, only the new ones are auto-declared by := and the rest are just assigned (as with =). However, if you use := in a new scope, then ALL variables are declared in that scope and mask any outer-scope variables with the same name (such as in an if; note that the if condition is not inside the braces, but is still considered as if it were within the {code} block; same happens with the for and other compound statements in GO).

3
votes

Your if statement declares a new variable tm that exists only within the scope of the if block and is indeed never used:

if tm, err := time.Parse(time.RFC3339, "2018-09-11T17:50:54.247Z"); err != nil {
    panic(err)
}

In Go, := declares a new variable and initializes it. You probably meant:

func main() {
    var val reflect.Value
    var tm time.Time
    var err error

    // Note the change to normal assignment here instead of :=
    if tm, err = time.Parse(time.RFC3339, "2018-09-11T17:50:54.247Z"); err != nil {
        panic(err)
    }
    val = reflect.ValueOf(tm)

    fmt.Println(val, tm, reflect.TypeOf(tm))
}

The := shortcut operator is demonstrated in the Tour of Go and explained in the Go spec, the latter of which includes:

It is shorthand for a regular variable declaration with initializer expressions but no types:

"var" IdentifierList = ExpressionList .

Scoping is explained in the Go spec as well.