Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding variable scope in Go

I am going through the Go specification to learn the language, and these points are taken from the spec under Declarations and scope.

Though I am able to understand points 1-4, I am confused on points 5 and 6:

  1. The scope of a constant or variable identifier declared inside a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl for short variable declarations) and ends at the end of the innermost containing block.
  2. The scope of a type identifier declared inside a function begins at the identifier in the TypeSpec and ends at the end of the innermost containing block.

This is the code which I used to understand scope in Go:

package main

import "fmt"

func main() {
    x := 42
    fmt.Println(x)
    {
        fmt.Println(x)
        y := "The test message"
        fmt.Println(y)
    }
    // fmt.Println(y) // outside scope of y
}

From this what I understand is scope of x is within the main function, and the scope of y is inside the opening and closing brackets after fmt.Println(x), and I cannot use y outside of the closing brackets.

If I understand it correctly, both points 4 and 5 are saying the same thing. So my questions are:

  1. If they are saying the same thing, what is the importance of both the points?

  2. If they are different, can you please let me know the difference?

like image 687
MBB Avatar asked Feb 03 '23 21:02

MBB


2 Answers

They're making the same point, with the same rules, about two different things: the first is about variables and constants, the second is about type identifiers. So, if you declare a type inside a block, the same scoping rules apply as would apply to a variable declared at the same spot.

like image 190
Adrian Avatar answered Feb 15 '23 12:02

Adrian


Besides applying to different things (rule #5 is for constant- and variable declarations, rule #6 is for type declarations), there is also an important difference in wording:

  1. The scope of a constant or variable identifier declared inside a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl for short variable declarations) and ends at the end of the innermost containing block.
  2. The scope of a type identifier declared inside a function begins at the identifier in the TypeSpec and ends at the end of the innermost containing block.

This is the reason why there are 2 rules and not one.

What does this mean? What does the difference imply?

# 5 Variable and Constant declarations (inside a function)

The scope of the declared variables or constants begins at the end of the declaration. This means if you're creating a function variable, initializing it with an anonymous function, it can't refer to itself.

This is invalid:

f := func() {
    f()
}

Attempting to compile:

prog.go:5:3: undefined: f

This is because the declaration ends after the closing bracket of the anonymous function, so inside of it you can't call f(). A workaround would be:

var f func()
f = func() {
    f()
}

Now here the declaration of f ends at the closing parenthesis (of its type func()), so in the next line when we assign an anonymous function to it, it is valid to refer to f (to call the function value stored in the f variable) because it is now in scope. See related question: Define a recursive function within a function in Go

Similarly, when initializing a variable e.g. with a composite literal, you can't refer to the variable inside of it:

var m = map[int]string{
    1:  "one",
    21: "twenty-" + m[1],
}

This gives a compile-time error ("undefined: m"), because m is not in scope yet inside the composite literal.

And obviously this workaround works:

var m = map[int]string{
    1: "one",
}
m[21] = "twenty-" + m[1]

# 6 Type declarations (inside a function)

The scope of the declared type begins at the identifier in the declaration. So in contrast to rule #5, it is valid to refer to the type itself inside its declaration.

Does it have any advantage / significance?

Yes, you can declare recursive types, such as this:

type Node struct {
    Left, Right *Node
}

The type identifier Node is in scope right after it appears in the type declaration, which ends with the closing bracket, but before that we could refer to it, meaningfully.

Another example would be a slice type whose element type is itself:

type Foo []Foo

You can read more about it here: How can a slice contain itself?

Yet another valid example:

type M map[int]M
like image 45
icza Avatar answered Feb 15 '23 11:02

icza