Блоки в Go

Объявление привязывает идентификатор к значению, такому как пакет, переменная, тип и т. Д. После объявления важно знать, где в исходном коде идентификатор относится к указанному значению (в разговорной речи, где такое имя может использоваться).

Go имеет лексическую область видимости, поэтому разрешение идентификатора зависит от того, где в исходном коде объявлено имя. Это совершенно другой подход, чем в языках с динамической областью видимости, где видимость не связана с местом объявления. Рассмотрим сценарий Bash ниже:

#!/bin/bash
f() {
    local v=1
    g
}
g() {
    echo "g sees v as $v"
}
h() {
    echo "h sees v as $v"
}
f
g
h

Переменная v определена в функции f, но поскольку g вызывается с помощью f, у нее есть доступ к ней:

> ./scope.sh
g sees v as 1
g sees v as
h sees v as

Когда g вызывается независимо или используется другая функция, например h, тогда переменная v не определена. Видимость в языках с динамической областью видимости не является статической (лексическая область видимости также называется статической областью видимости), а зависит от потока управления.

Попытка скомпилировать аналогичный код в Go вызывает ошибку компиляции:

package main
import “fmt”
func f() {
    v := 1
    g()
}
func g() {
    fmt.Println(v)  // "undefined: v"
}
func main() {
    f()
}

Лексическая область видимости в Go использует блоки, поэтому важно сначала понять, что это за блок, прежде чем пытаться понять правила видимости.

Блок - это последовательность операторов (пустая последовательность также допустима). Блоки могут быть вложенными и обозначаются фигурными скобками:

package main
import “fmt”
func main() {
    { // start outer block
        a := 1
        fmt.Println(a)
        { // start inner block
            b := 2
            fmt.Println(b)
        } // end inner block
    } // end outer block
}

Помимо явно отмеченных блоков, есть несколько неявных:

  • блок юниверса содержит весь исходный код,
  • блок пакета содержит весь исходный код пакета (пакет может быть распределен по нескольким файлам в одном каталоге),
  • файловый блок содержит исходный код файла,
  • Оператор for находится в собственном неявном блоке:
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

поэтому переменная i, объявленная в операторе init, может быть доступна в условии, операторе post и вложенном блоке с телом цикла. Попытка использовать i после оператора for вызывает ошибку компиляции «undefined: i».

  • Оператор if находится в собственном неявном блоке:
if i := 0; i >= 0 {
    fmt.Println(i)
}

позволяет объявлять переменную, которая может использоваться в выражении, вложенный блок, оцениваемый, когда выражение истинно, или блок для предложения else.

  • Оператор switch находится в собственном неявном блоке:
switch i := 2; i * 4 {
case 8:
    fmt.Println(i)
default:
    fmt.Println(“default”)
}

Так же, как с оператором if, можно использовать f.ex. короткое объявление переменной для введения привязки, доступной в предложениях case.

  • каждое предложение в инструкции switch действует как неявный блок
switch i := 2; i * 4 {
case 8:
    j := 0
    fmt.Println(i, j)
default:
    // "j" is undefined here
    fmt.Println(“default”)
}
// "j" is undefined here

Если каждое предложение case будет блоком в соответствии с грамматикой языка, то это не будет дополнительным регистром. Однако для каждого предложения потребуется использовать фигурные скобки, что сделает код менее читабельным и кратким.

  • каждое предложение в инструкции select действует как неявный блок. Аналогичный случай с предложениями в операторах switch:
    tick := time.Tick(100 * time.Millisecond)
LOOP:
    for {
        select {
        case <-tick:
            i := 0
            fmt.Println(“tick”, i)
            break LOOP
        default:
            // "i" is undefined here
            fmt.Println(“sleep”)
            time.Sleep(30 * time.Millisecond)
        }
    }
    // "i" is undefined here

Объем (видимость) описан в Scope in Go. Блоки играют решающую роль в определении всего механизма, стоящего за прицелами.

Ресурсы