Блоки в 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. Блоки играют решающую роль в определении всего механизма, стоящего за прицелами.