Understanding variable scope in Go
Asked Answered
S

2

10

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?

Slough answered 25/9, 2018 at 17:15 Comment(0)
H
6

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.

Hybris answered 25/9, 2018 at 17:17 Comment(6)
As for the usage of locally scoped types, sometimes it is useful to declare a throwaway type in a lesser scope so you don't have to worry about muddying the namespace.Dermott
Or for use in a locally-declared function. But generally, locally-declared types are a fairly rare occurrence.Hybris
I use them quite a bit in my API when calling some 3rd party API. I declare the response type locally in the function because the response is Unmarshalled, dismantled, and its values are used only inside that function (See mholt.github.io/json-to-go). Just my two cents for an example. I should benchmark to see if a local declared type is more/less overhead... probably no difference, but could be an interesting experiment.Dermott
I feel like anonymous types are a more common solution for that use case, but I may well be wrong about that.Hybris
Er sorry, that's what I meant. It's anonymous.Dermott
Good answer, except that it's almost the same point with almost the same rules made about two different things (as clarified by @icza's answer.)Collotype
B
7

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
Bladdernose answered 25/9, 2018 at 20:19 Comment(1)
That's an important distinction, allowing a type to refer to itself (e.g. a struct containing a pointer to its type) but not allowing a variable or constant declaration to refer to the variable being declared.Collotype
H
6

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.

Hybris answered 25/9, 2018 at 17:17 Comment(6)
As for the usage of locally scoped types, sometimes it is useful to declare a throwaway type in a lesser scope so you don't have to worry about muddying the namespace.Dermott
Or for use in a locally-declared function. But generally, locally-declared types are a fairly rare occurrence.Hybris
I use them quite a bit in my API when calling some 3rd party API. I declare the response type locally in the function because the response is Unmarshalled, dismantled, and its values are used only inside that function (See mholt.github.io/json-to-go). Just my two cents for an example. I should benchmark to see if a local declared type is more/less overhead... probably no difference, but could be an interesting experiment.Dermott
I feel like anonymous types are a more common solution for that use case, but I may well be wrong about that.Hybris
Er sorry, that's what I meant. It's anonymous.Dermott
Good answer, except that it's almost the same point with almost the same rules made about two different things (as clarified by @icza's answer.)Collotype

© 2022 - 2024 — McMap. All rights reserved.