Спецификация языка программирования Go

Версия языка go1.25 (12 августа 2025)

Введение

Это справочное руководство по языку программирования Go. Для получения дополнительной информации и других документов посетите go.dev.

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

Синтаксис компактен и прост для разбора, что облегчает анализ с помощью автоматических инструментов, таких как интегрированные среды разработки.

Нотация

Синтаксис описан с использованием варианта расширенной формы Бэкуса — Наура (EBNF):

Syntax      = { Production } .
Production  = production_name "=" [ Expression ] "." .
Expression  = Term { "|" Term } .
Term        = Factor { Factor } .
Factor      = production_name | token [ "…" token ] | Group | Option | Repetition .
Group       = "(" Expression ")" .
Option      = "[" Expression "]" .
Repetition  = "{" Expression "}" .

Правила (productions) — это выражения, составленные из терминов и следующих операторов (в порядке возрастания приоритета):

|   alternation
()  grouping
[]  option (0 or 1 times)
{}  repetition (0 to n times)

Имена правил, записанные строчными буквами, используются для обозначения лексических (терминальных) токенов. Нетерминальные символы пишутся в стиле CamelCase. Лексические токены заключаются в двойные кавычки "" или обратные кавычки ``.

Запись a … b обозначает множество символов от a до b включительно. Горизонтальное многоточие также используется в других частях спецификации для неформального обозначения различных перечислений или фрагментов кода, которые не описываются подробно. Символ (в отличие от трёх отдельных символов ...) не является токеном языка Go.

Ссылка вида [Go 1.xx] указывает, что описанная особенность языка (или её аспект) была изменена или добавлена в версии языка Go 1.xx и, следовательно, требует как минимум этой версии для сборки. Подробности см. в соответствующем разделе и в приложении.

Представление исходного кода

Исходный код представляет собой текст в кодировке Unicode в формате UTF-8. Текст не канонизируется, поэтому одна кодовая точка с диакритическим знаком отличается от того же символа, составленного из комбинации диакритического знака и буквы; они рассматриваются как две кодовые точки. Для простоты в этом документе будет использоваться неквалифицированный термин символ для обозначения кодовой точки Unicode в исходном тексте.

Каждая кодовая точка различна; например, прописные и строчные буквы являются разными символами.

Ограничение реализации: для совместимости с другими инструментами компилятор может запрещать символ NUL (U+0000) в исходном тексте.

Ограничение реализации: для совместимости с другими инструментами компилятор может игнорировать метку порядка байтов в кодировке UTF-8 (U+FEFF), если она является первой кодовой точкой Unicode в исходном тексте. Метка порядка байтов может быть запрещена в любом другом месте исходного кода.

Символы

Следующие термины используются для обозначения конкретных категорий символов Unicode:

newline        = /* кодовая точка Unicode U+000A */ .
unicode_char   = /* произвольная кодовая точка Unicode, кроме newline */ .
unicode_letter = /* кодовая точка Unicode, относящаяся к категории "Letter" (Буква) */ .
unicode_digit  = /* кодовая точка Unicode, относящаяся к категории "Number, decimal digit" (Число, десятичная цифра) */ .

В Стандарте Unicode 8.0, Раздел 4.5 "Общая категория" определяет набор категорий символов. Go рассматривает все символы из любой из категорий букв Lu, Ll, Lt, Lm или Lo как буквы Unicode, а символы из категории чисел Nd как цифры Unicode.

Буквы и цифры

Символ подчеркивания _ (U+005F) считается строчной буквой.

letter        = unicode_letter | "_" .
decimal_digit = "0" … "9" .
binary_digit  = "0" | "1" .
octal_digit   = "0" … "7" .
hex_digit     = "0" … "9" | "A" … "F" | "a" … "f" .

Лексические элементы

Комментарии

Комментарии служат в качестве документации программы. Существует две формы:

  1. Строчные комментарии начинаются с последовательности символов // и заканчиваются в конце строки.
  2. Общие комментарии начинаются с последовательности символов /* и заканчиваются первой последующей последовательностью символов */.

Комментарий не может начинаться внутри рунного или строкового литерала, или внутри комментария. Общий комментарий, не содержащий символов новой строки, действует как пробел. Любой другой комментарий действует как символ новой строки.

Токены

Токены образуют словарь языка Go. Существует четыре класса: идентификаторы, ключевые слова, операторы и знаки пунктуации и литералы. Пробельные символы, состоящие из пробелов (U+0020), горизонтальных табуляций (U+0009), возвратов каретки (U+000D) и символов новой строки (U+000A), игнорируются, за исключением случаев, когда они разделяют токены, которые в противном случае объединились бы в один токен. Кроме того, символ новой строки или конец файла может инициировать вставку точки с запятой. При разбиении входных данных на токены следующий токен — это самая длинная последовательность символов, образующая допустимый токен.

Точки с запятой

Формальный синтаксис использует точки с запятой ";" в качестве терминаторов в ряде конструкций. Программы на Go могут опускать большинство этих точек с запятой, используя следующие два правила:

  1. Когда входные данные разбиваются на токены, точка с запятой автоматически вставляется в поток токенов сразу после последнего токена строки, если этот токен является
  2. Чтобы разрешить сложным операторам занимать одну строку, точка с запятой может быть опущена перед закрывающими ")" или "}".

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

Идентификаторы

Идентификаторы именуют программные сущности, такие как переменные и типы. Идентификатор — это последовательность из одной или более букв и цифр. Первый символ в идентификаторе должен быть буквой.

identifier = letter { letter | unicode_digit } .
a
_x9
ThisVariableIsExported
αβ

Некоторые идентификаторы являются предварительно объявленными.

Ключевые слова

Следующие ключевые слова зарезервированы и не могут использоваться в качестве идентификаторов.

break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

Операторы и знаки пунктуации

Следующие последовательности символов представляют операторы (включая операторы присваивания) и знаки пунктуации [Go 1.18]:

+    &     +=    &=     &&    ==    !=    (    )
-    |     -=    |=     ||    <     <=    [    ]
*    ^     *=    ^=     <-    >     >=    {    }
/    <<    /=    <<=    ++    =     :=    ,    ;
%    >>    %=    >>=    --    !     ...   .    :
     &^          &^=          ~

Целочисленные литералы

Целочисленный литерал — это последовательность цифр, представляющая целочисленную константу. Необязательный префикс задает недесятичное основание: 0b или 0B для двоичного, 0, 0o или 0O для восьмеричного, и 0x или 0X для шестнадцатеричного [Go 1.13]. Одиночный 0 считается десятичным нулем. В шестнадцатеричных литералах буквы от a до f и от A до F представляют значения от 10 до 15.

Для удобочитаемости символ подчеркивания _ может появляться после префикса основания или между последовательными цифрами; такие подчеркивания не изменяют значение литерала.

int_lit        = decimal_lit | binary_lit | octal_lit | hex_lit .
decimal_lit    = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] .
binary_lit     = "0" ( "b" | "B" ) [ "_" ] binary_digits .
octal_lit      = "0" [ "o" | "O" ] [ "_" ] octal_digits .
hex_lit        = "0" ( "x" | "X" ) [ "_" ] hex_digits .
decimal_digits = decimal_digit { [ "_" ] decimal_digit } .
binary_digits  = binary_digit { [ "_" ] binary_digit } .
octal_digits   = octal_digit { [ "_" ] octal_digit } .
hex_digits     = hex_digit { [ "_" ] hex_digit } .
42
4_2
0600
0_600
0o600
0O600       // второй символ — заглавная буква 'O'
0xBadFace
0xBad_Face
0x_67_7a_2f_cc_40_c6
170141183460469231731687303715884105727
170_141183_460469_231731_687303_715884_105727
_42         // идентификатор, а не целочисленный литерал
42_         // недопустимо: _ должен разделять последовательные цифры
4__2        // недопустимо: только один _ за раз
0_xBadFace  // недопустимо: _ должен разделять последовательные цифры

Литералы с плавающей запятой

Литерал с плавающей запятой — это десятичное или шестнадцатеричное представление константы с плавающей запятой.

Десятичный литерал с плавающей запятой состоит из целой части (десятичных цифр), десятичной запятой, дробной части (десятичных цифр) и экспоненты (e или E, за которыми может следовать знак и десятичные цифры). Целая часть или дробная часть могут быть опущены; десятичная запятая или экспонента могут быть опущены. Значение экспоненты exp масштабирует мантиссу (целую и дробную часть) на 10exp.

Шестнадцатеричный литерал с плавающей запятой состоит из префикса 0x или 0X , целой части (шестнадцатеричные цифры), десятичной запятой, дробной части (шестнадцатеричные цифры) и экспоненты (p или P, за которым может следовать знак и десятичные цифры). Целая часть или дробная часть могут быть опущены; десятичная запятая также может быть опущена, но экспонента обязательна. (Этот синтаксис соответствует синтаксису, приведенному в IEEE 754-2008 §5.12.3.) Значение экспоненты exp масштабирует мантиссу (целую и дробную часть) на 2exp [Go 1.13].

Для удобства чтения символ подчеркивания _ может появляться после префикса базы или между последовательными цифрами; такие подчеркивания не изменяют значение литерала.

float_lit         = decimal_float_lit | hex_float_lit .
decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] |
                    decimal_digits decimal_exponent |
                    "." decimal_digits [ decimal_exponent ] .
decimal_exponent  = ( "e" | „E“ ) [ "+" | "-" ] decimal_digits .
hex_float_lit     = "0" ( „x“ | "X" ) hex_mantissa hex_exponent .
hex_mantissa      = [ "_" ] hex_digits "." [ hex_digits ] |
                    [ "_" ] hex_digits |
                    "." hex_digits .
hex_exponent      = ( "p" | „P“ ) [ "+" | "-" ] decimal_digits .
0.
72.40
072.40       // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
1_5.         // == 15.0
0.15e+0_2    // == 15.0
0x1p-2       // == 0.25
0x2.p10      // == 2048.0
0x1.Fp+0     // == 1.9375
0X.8p-0      // == 0.5
0X_1FFFP-16  // == 0.1249847412109375
0x15e-2      // == 0x15e - 2 (вычитание целых чисел)
0x.p1        // недопустимо: мантисса не содержит цифр
1p-2         // недопустимо: экспонента p требует шестнадцатеричной мантиссы
0x1.5e-2     // недопустимо: шестнадцатеричная мантисса требует экспоненты p
1_.5         // недопустимо: _ должен разделять последовательные цифры
1._5         // недопустимо: _ должен разделять последовательные цифры
1.5_e1       // недопустимо: _ должен разделять последовательные цифры
1.5e_1       // недопустимо: _ должен разделять последовательные цифры
1.5e1_       // недопустимо: _ должен разделять последовательные цифры

Мнимые литералы

Мнимый литерал представляет мнимую часть комплексной константы. Он состоит из целого или плавающего литерала, за которым следует строчная буква i. Значение мнимого литерала равно значению соответствующего целого или плавающего литерала, умноженному на мнимую единицу i [Go 1.13]

imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .

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

0i
0123i         // == 123i для обратной совместимости
0o123i        // == 0o123 * 1i == 83i
0xabci        // == 0xabc * 1i == 2748i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i
0x1p-2i       // == 0x1p-2 * 1i == 0.25i

Литералы рун

Литерал руны представляет собой константу руны, целое число, идентифицирующее кодовую точку Unicode. Рунический литерал выражается одним или несколькими символами, заключенными в одинарные кавычки, например, „x“ или „\n“. Внутри кавычек может находиться любой символ, кроме символа новой строки и неэкранированной одинарной кавычки. Один символ в кавычках представляет значение Unicode самого символа, а многосимвольные последовательности, начинающиеся с обратной косой черты, кодируют значения в различных форматах.

Самая простая форма представляет один символ в кавычках; поскольку исходный текст Go представляет собой символы Unicode, закодированные в UTF-8, несколько байт, закодированных в UTF-8, могут представлять одно целое значение. Например, литерал „a“ содержит один байт, представляющий литерал a, Unicode U+0061, значение 0x61, в то время как „ä“ содержит два байта (0xc3 0xa4), представляющих литерал a-диэрезис, U+00E4, значение 0xe4.

Несколько экранирующих символов обратной косой черты позволяют кодировать произвольные значения в виде ASCII-текста. Существует четыре способа представления целочисленного значения в виде числовой константы: \x, за которым следует ровно две шестнадцатеричные цифры; \u, за которым следует ровно четыре шестнадцатеричные цифры; \U, за которым следует ровно восемь шестнадцатеричных цифр, и простой обратный слеш \, за которым следует ровно три восьмеричные цифры. В каждом случае значение литерала является значением, представленным цифрами в соответствующей системе счисления.

Хотя все эти представления дают целое число, они имеют различные допустимые диапазоны. Восьмеричные экранирующие символы должны представлять значение от 0 до 255 включительно. Шестнадцатеричные экранирующие символы удовлетворяют этому условию по своей конструкции. Экранирующие символы \u и \U представляют кодовые точки Unicode, поэтому некоторые значения внутри них являются недопустимыми, в частности, значения выше 0x10FFFF и суррогатные половинки.

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

\a   U+0007 сигнал тревоги или звонок
\b   U+0008 обратный пробел
\f   U+000C переход на новую страницу
\n   U+000A переход на новую строку или новая строка
\r   U+000D возврат каретки
\t   U+0009 горизонтальная табуляция
\v   U+000B вертикальная табуляция
\\   U+005C обратный слеш
\'   U+0027 одинарная кавычка  (допустимый экранирующий символ только в литералах рун)
\"   U+0022 двойная кавычка  (допустимый экранирующий символ только в литералах строк)

Нераспознанный символ, следующий за обратной косой чертой в литерале руны, является недопустимым.

rune_lit         = "'" ( unicode_value | byte_value ) "'" .
unicode_value    = unicode_char | little_u_value | big_u_value | escaped_char .
byte_value       = octal_byte_value | hex_byte_value .
octal_byte_value = `\` octal_digit octal_digit octal_digit .
hex_byte_value   = `\` "x" hex_digit hex_digit .
little_u_value   = `\` "u" hex_digit hex_digit hex_digit hex_digit .
big_u_value      = `\` "U" hex_digit hex_digit hex_digit hex_digit
                           hex_digit hex_digit hex_digit hex_digit .
escaped_char     = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | „v“ | `\` | "'" | `"` ) .
„a“
'ä'
„本“
'\t'
„\000“
'\007'
„\377“
'\x07'
„\xff“
'\u12e4'
'\U00101234'
„\“'         // рунический литерал, содержащий символ одинарной кавычки
„aa“         // недопустимо: слишком много символов
„\k“         // недопустимо: k не распознается после обратной косой черты
'\xa'        // недопустимо: слишком мало шестнадцатеричных цифр
„\0“         // недопустимо: слишком мало восьмеричных цифр
„\400“       // недопустимо: восьмеричное значение больше 255
„\uDFFF“     // недопустимо: суррогатная половина
„\U00110000“ // недопустимо: недопустимая кодовая точка Unicode

Строковые литералы

Строковый литерал представляет собой строковую константу, полученную путем объединения последовательности символов. Существует две формы: необработанные строковые литералы и интерпретируемые строковые литералы.

Необработанные строковые литералы представляют собой последовательности символов между обратными кавычками, как в `foo`. Внутри кавычек может встречаться любой символ, кроме обратной кавычки. Значение необработанного строкового литерала — это строка, состоящая из неинтерпретируемых (неявно закодированных в UTF-8) символов между кавычками; в частности, обратные косые черты не имеют особого значения, и строка может содержать символы новой строки. Символы возврата каретки („\r“) внутри необработанных строковых литералов исключаются из значения необработанной строки.

Интерпретируемые строковые литералы — это последовательности символов между двойными кавычками, как в "bar". Внутри кавычек может встречаться любой символ, кроме символа новой строки и неэкранированной двойной кавычки. Текст между кавычками образует значение литерала, при этом обратные косые черты интерпретируются так же, как и в рунических литералах (за исключением того, что \' является недопустимым, а \" — допустимым), с теми же ограничениями. Трехзначные восьмеричные (\nnn) и двухзначные шестнадцатеричные (\xnn) экранирующие символы представляют отдельные байты результирующей строки; все остальные экранирующие символы представляют (возможно многобайтовую) кодировку UTF-8 отдельных символов. Таким образом, внутри строкового литерала \377 и \xFF представляют один байт со значением 0xFF=255, а ÿ, \u00FF, \U000000FF и \xc3\xbf представляют два байта 0xc3 0xbf кодировки UTF-8 символа U+00FF.

string_lit             = raw_string_lit | interpreted_string_lit .
raw_string_lit         = "`" { unicode_char | newline } "`" .
interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc`                // то же, что и "abc"
`\n
\n`                  // то же, что и "\\n\n\\n"
"\n"
"\„“                 // то же самое, что `"`
"Hello, world!\n"
„日本語“
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800"             // недопустимо: замещающая половина
"\U00110000"         // недопустимо: недействительная кодовая точка Unicode

Все эти примеры представляют одну и ту же строку:

"日本語"                                 // входной текст UTF-8
`日本語`                                 // входной текст UTF-8 в виде необработанного литерала
"\u65e5\u672c\u8a9e"                    // явные кодовые точки Unicode
"\U000065e5\U0000672c\U00008a9e"        // явные кодовые точки Unicode
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"  // явные байты UTF-8

Если исходный код представляет символ в виде двух кодовых точек, например, комбинированную форму, включающую акцент и букву, то в случае размещения в руническом литерале (это не единичная кодовая точка) будет получена ошибка, а в случае размещения в строковом литерале будет отображаться как две кодовые точки.

Константы

Существуют булевы константы, рунные константы, целочисленные константы, константы с плавающей запятой, комплексные константы и строковые константы. Рунные, целочисленные, константы с плавающей запятой и комплексные константы в совокупности называются числовыми константами.

Константное значение представляется руной, целым числом, число с плавающей запятой, мнимым числом, или строкой, идентификатором, обозначающим константу, константным выражением, преобразованием с результатом, являющимся константой, или результат некоторых встроенных функций, таких как min или max, примененных к постоянным аргументам, unsafe. Sizeof, примененные к определенным значениям, cap или len, примененные к некоторым выражениям, real и imag, примененные к комплексной константе и complex, применяемые к числовым константам. Булевы значения истины представлены заранее объявленными константами true и false. Заранее объявленный идентификатор iota обозначает целочисленную константу.

В общем случае комплексные константы являются формой постоянного выражения и рассматриваются в этом разделе.

Числовые константы представляют точные значения произвольной точности и не переполняются. Следовательно, нет констант, обозначающих отрицательное нулевое значение IEEE 754, бесконечность и значения, не являющиеся числами.

Константы могут быть типизированными или нетипизированными. Литеральные константы, true, false, iota, и некоторые константные выражения, содержащие только нетипизированные константные операнды, являются нетипизированными.

Константе может быть явно присвоен тип с помощью объявления константы или преобразования, либо неявно при использовании в объявлении переменной или операторе присваивания или в качестве операнда в выражении. Ошибка возникает, если значение константы не может быть представлено как значение соответствующего типа. Если тип является типовым параметром, константа преобразуется в неконстантное значение типового параметра.

Нетипизированная константа имеет тип по умолчанию, который является типом, в который константа неявно преобразуется в контекстах, где требуется типизированное значение, например, в коротком объявлении переменной таком как i := 0, где нет явного типа. Тип по умолчанию для константы без типа — это bool, rune, int, float64, complex128 или string соответственно, в зависимости от того, является ли она константой типа boolean, rune, integer, floating-point, complex или string.

Ограничение реализации: хотя числовые константы имеют произвольную точность в языке, компилятор может реализовывать их с использованием внутреннего представления с ограниченной точностью. При этом каждая реализация должна:

Эти требования применяются как к литеральным константам, так и к результату вычисления константных выражений.

Переменные

Переменная — это место хранения для значения. Набор допустимых значений определяется типом переменной type.

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

Структурированные переменные типов array, среза и типа struct имеют элементы и поля, которые могут быть адресованы индивидуально. Каждый такой элемент действует как переменная.

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

var x interface{}  // x равно nil и имеет статический тип interface{}
var v *T           // v имеет значение nil, статический тип *T
x = 42             // x имеет значение 42 и динамический тип int
x = v              // x имеет значение (*T)(nil) и динамический тип *T

Значение переменной извлекается путем ссылки на переменную в выражении; это самое последнее значение, присвоенное переменной. Если переменной еще не присвоено значение, ее значением является нулевое значение для ее типа.

Типы

Тип определяет набор значений вместе с операциями и методами, специфичными для этих значений. Тип может обозначаться именем типа, если оно есть, за которым должны следовать аргументы типа, если тип является общим. Тип также может быть указан с помощью литерала типа, который составляется из существующих типов.

Тип     = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" .
TypeName = identifier | QualifiedIdent .
TypeArgs = "[" TypeList [ "," ] "]" .
TypeList = Type { "," Type } .
TypeLit  = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
           SliceType | MapType | ChannelType .

Язык предварительно объявляет определенные имена типов. Другие вводятся с помощью объявлений типов или списков параметров типов. Составные типы — массивы, структуры, указатели, функции, интерфейсы, срезы, карты и каналы — могут быть построены с использованием типовых литералов.

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

Булевы типы

Булевый тип представляет собой набор булевых значений истины, обозначенных заранее объявленными константами true и false. Заранее объявленный булевый тип — bool; он является определенным типом.

Числовые типы

Тип integer, floating-point или complex представляет собой набор целочисленных, плавающих или комплексных значений соответственно. Их совокупность называется числовыми типами. Предварительно объявленные архитектурно-независимые числовые типы:

uint8       набор всех целых чисел без знака  8 бит (от 0 до 255)
uint16      набор всех целых чисел без знака 16 бит (от 0 до 65535)
uint32      набор всех целых чисел без знака 32 бит (от 0 до 4294967295)
uint64      набор всех 64-разрядных целых чисел без знака (от 0 до 18446744073709551615)
int8        набор всех 8-разрядных целых чисел со знаком (от -128 до 127)
int16       набор всех 16-разрядных целых чисел со знаком (от -32768 до 32767)
int32       набор всех 32-разрядных целых чисел со знаком (от -2147483648 до 2147483647)
int64       набор всех 64-разрядных целых чисел со знаком (от -9223372036854775808 до 9223372036854775807)
float32     набор всех 32-разрядных чисел с плавающей запятой IEEE 754
float64     набор всех 64-разрядных чисел с плавающей запятой IEEE 754
complex64   набор всех комплексных чисел с вещественной и мнимой частями float32
complex128  набор всех комплексных чисел с вещественной и мнимой частями float64
byte        псевдоним для uint8
rune        псевдоним для int32

Значение n-разрядного целого числа имеет ширину n разрядов и представляется с использованием арифметики двоичного дополнения.

Существует также набор заранее объявленных типов целых чисел с размерами, зависящими от реализации:

uint     32 или 64 бита
int      такой же размер, как uint
uintptr  целое число без знака, достаточное для хранения неинтерпретируемых битов значения указателя

Чтобы избежать проблем с переносимостью, все числовые типы являются определенными типами и, таким образом, отличаются друг от друга, за исключением byte, который является псевдонимом для uint8, и rune, который является псевдонимом для int32. Явные преобразования требуются, когда в выражении или присваивании смешиваются разные числовые типы. Например, int32 и int не являются одинаковыми типами, даже если они могут иметь одинаковый размер на определенной архитектуре.

Типы строк

Тип string представляет набор строковых значений. Строковое значение — это (возможно пустая) последовательность байтов. Число байтов называется длиной строки и никогда не бывает отрицательным. Строки являются неизменяемыми: после создания невозможно изменить содержимое строки. Предварительно объявленный тип строки — string; это определенный тип.

Длину строки s можно узнать с помощью встроенной функции len. Длина является константой времени компиляции, если строка является константой. Доступ к байтам строки можно получить с помощью целочисленных индексов от 0 до len(s)-1. Недопустимо получать адрес такого элемента; если s[i] является i-м байтом строки, то &s[i] является недопустимым.

Типы массивов

Массив — это пронумерованная последовательность элементов одного типа, называемого типом элемента. Количество элементов называется длиной массива и никогда не бывает отрицательным.

ArrayType   = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .

Длина является частью типа массива; она должна оцениваться как неотрицательная константа представляемой значением типа int. Длину массива a можно определить с помощью встроенной функции len. Элементы могут быть адресованы целочисленными индексами от 0 до len(a)-1. Типы массивов всегда одномерны, но могут комбинироваться для формирования многомерных типов.

[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64  // то же, что [2]([2]([2]float64))

Тип массива T не может иметь элемент типа T или типа, содержащего T в качестве компонента, прямо или косвенно, если эти содержащие типы являются только типами массива или структуры.

// недопустимые типы массивов
type (
    T1 [10]T1                 // тип элемента T1 — T1
    T2 [10]struct{ f T2 }     // T2 содержит T2 в качестве компонента структуры
    T3 [10]T4                 // T3 содержит T3 в качестве компонента структуры в T4
    T4 struct{ f T3 }         // T4 содержит T4 как компонент массива T3 в структуре
)
// допустимые типы массивов
type (
    T5 [10]*T5                // T5 содержит T5 как компонент указателя
    T6 [10]func() T6          // T6 содержит T6 в качестве компонента типа функции
    T7 [10]struct{ f []T7 }   // T7 содержит T7 в качестве компонента слайса в структуре
)

Типы срезов

Срез — это дескриптор для непрерывного сегмента базового массива, который обеспечивает доступ к пронумерованной последовательности элементов из этого массива. Тип среза обозначает набор всех срезов массивов его типа элементов. Количество элементов называется длиной среза и никогда не бывает отрицательным. Значение неинициализированного среза — nil.

SliceType = "[" "]" ElementType .

Длину фрагмента s можно определить с помощью встроенной функции len; в отличие от массивов, она может изменяться во время выполнения. Элементы могут быть адресованы целочисленными индексами от 0 до len(s)-1. Индекс фрагмента данного элемента может быть меньше индекса того же элемента в базовом массиве.

После инициализации срез всегда связан с базовым массивом, который содержит его элементы. Таким образом, срез делит хранилище с массивом и другими срезами того же массива; напротив, различные массивы всегда представляют собой отдельные хранилища.

Массив, лежащий в основе среза, может простираться за пределы среза. Емкость является мерой этого расширения: это сумма длины фрагмента и длины массива за пределами фрагмента; фрагмент длиной до этой емкости может быть создан путем разделения нового фрагмента из исходного фрагмента. Емкость фрагмента a можно определить с помощью встроенной функции cap(a).

Новое инициализированное значение фрагмента для заданного типа элемента T может быть создано с помощью встроенной функции make, которая принимает тип фрагмента и параметры, определяющие длину и, опционально, емкость. Срез, созданный с помощью make, всегда выделяет новый скрытый массив, на который ссылается возвращаемое значение среза. То есть выполнение

make([]T, length, capacity)

приводит к созданию того же самого слайса, что и выделение массива и разбиение его, поэтому эти два выражения эквивалентны:

make([]int, 50, 100)
new([100]int)[0:50]

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

Типы структур

Структура — это последовательность именованных элементов, называемых полями, каждое из которых имеет имя и тип. Имена полей могут быть указаны явно (IdentifierList) или неявно (EmbeddedField). Внутри структуры имена полей, отличные от пустых, должны быть уникальными.

StructType    = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl     = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName [ TypeArgs ] .
Tag           = string_lit .
// Пустая структура.
struct {}
// Структура с 6 полями.
struct {
    x, y int
    u float32
    _ float32  // заполнение
    A *[]int
    F func()
}

Поле, объявленное с типом, но без явного имени поля, называется встроенным полем. Встроенное поле должно быть указано как имя типа T или как указатель на имя типа, не являющегося интерфейсом, *T, причем само T не может быть типом указателя или параметром типа. Неопределенное имя типа выступает в качестве имени поля.

// Структура с четырьмя встроенными полями типов T1, *T2, P.T3 и *P.T4
struct {
    T1        // имя поля — T1
    *T2       // имя поля — T2
    P.T3      // имя поля — T3
    *P.T4     // имя поля — T4
    x, y int  // имена полей — x и y
}

Следующее объявление является недопустимым, поскольку имена полей должны быть уникальными в типе структуры:

struct {
    T     // конфликтует со встроенным полем *T и *P.T
    *T    // конфликтует со встроенным полем T и *P.T
    *P.T  // конфликтует со встроенным полем T и *T
}

Поле или метод f встроенного поля в структуре x называется продвинутым, если x.f является допустимым селектором, обозначающим это поле или метод f. это поле или метод f.

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

При наличии типа структуры S и имени типа T повышенные методы включаются в набор методов структуры следующим образом:

За объявлением поля может следовать необязательный строковый литерал tag, который становится атрибутом для всех полей в соответствующем объявлении поля. Пустая строка тега эквивалентна отсутствующему тегу. Теги становятся видимыми через интерфейс рефлексии и участвуют в идентификации типа для структур, но в остальных случаях игнорируются.

struct {
    x, y float64 ""  // пустая строка тега эквивалентна отсутствующему тегу
    name string  "в качестве тега допускается любая строка"
	_    [4]byte "ceci n'est pas un champ de structure"
}
// Структура, соответствующая протокольному буферу TimeStamp.
// Строки тегов определяют номера полей протокольного буфера;
// они следуют соглашению, изложенному в пакете reflect.
struct {
    microsec  uint64 `protobuf:"1"`
    serverIP6 uint64 `protobuf:"2"`
}

Тип структуры T не может содержать поле типа T или типа, содержащего T в качестве компонента, прямо или косвенно, если эти содержащие типы являются только типами массива или структуры.

// недопустимые типы структур
type (
    T1 struct{ T1 }            // T1 содержит поле T1
    T2 struct{ f [10]T2 }      // T2 содержит T2 в качестве компонента массива
    T3 struct{ T4 }            // T3 содержит T3 в качестве компонента массива в struct T4
    T4 struct{ f [10]T3 }      // T4 содержит T4 в качестве компонента struct T3 в массиве
)
// допустимые типы структур
type (
    T5 struct{ f *T5 }         // T5 содержит T5 в качестве компонента указателя
    T6 struct{ f func() T6 }   // T6 содержит T6 в качестве компонента типа функции
    T7 struct{ f [10][]T7 }    // T7 содержит T7 в качестве компонента слайса в массиве
)

Типы указателей

Тип указателя обозначает набор всех указателей на переменные заданного типа, называемого базовым типом указателя. Значение неинициализированного указателя равно nil.

PointerType = "*" BaseType .
BaseType    = Type .
*Point
*[4]int

Типы функций

Тип функции обозначает набор всех функций с одинаковыми типами параметров и результатов. Значение значения неинициализированной переменной типа функции равно nil.

FunctionType  = "func" Signature .
Сигнатура     = Параметры [ Результат ] .
Результат        = Параметры | Тип .
Параметры    = "(" [ ParameterList [ "," ] ] ")" .
Список параметров = ParameterDecl { "," ParameterDecl } .
ParameterDecl = [ IdentifierList ] [ "..." ] Type .

В списке параметров или результатов имена (IdentifierList) должны быть либо все присутствовать, либо все отсутствовать. Если они присутствуют, каждое имя означает один элемент (параметр или результат) указанного типа, и все имена, кроме пустых, в сигнатуре должны быть уникальными. Если отсутствует, каждый тип обозначает один элемент этого типа. Списки параметров и результатов всегда заключаются в скобки, за исключением случаев, когда есть ровно один неназванный результат, который может быть записан как тип без скобок.

Последний входящий параметр в сигнатуре функции может иметь тип с префиксом .... Функция с таким параметром называется вариадической и может вызываться с нулем или более аргументами для этого параметра.

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

Типы интерфейсов

Тип интерфейса определяет набор типов. Переменная типа интерфейса может хранить значение любого типа, который входит в набор типов интерфейса. Такой тип называется реализующим интерфейс. Значение неинициализированной переменной типа интерфейса равно nil.

InterfaceType  = "interface" "{" { InterfaceElem ";" } "}" .
InterfaceElem  = MethodElem | TypeElem .
MethodElem     = MethodName Signature .
MethodName     = identifier .
TypeElem       = TypeTerm { "|" TypeTerm } .
TypeTerm       = Type | UnderlyingType .
UnderlyingType = "~" Type .

Тип интерфейса определяется списком элементов интерфейса. Элемент интерфейса может быть либо методом, либо элементом типа, где элемент типа представляет собой объединение одного или нескольких терминов типа. Термин типа может быть либо одиночным типом, либо одиночным базовым типом.

Базовые интерфейсы

В своей наиболее базовой форме интерфейс определяет (возможно пустой) список методов. Набор типов, определяемый таким интерфейсом, представляет собой набор типов, которые реализуют все эти методы, а соответствующий набор методов состоит именно из методов, определенных интерфейсом. Интерфейсы, наборы типов которых могут быть полностью определены списком методов, называются базовыми интерфейсами.

// Простой интерфейс File.
interface {
    Read([]byte) (int, error)
    Write([]byte) (int, error)
    Close() error
}

Имя каждого явно указанного метода должно быть уникальным и не должно быть пустым.

interface {
    String() string
    String() string  // недопустимо: строка не уникальна
    _(x int)         // недопустимо: метод должен иметь имя, не являющееся пустым
}

Интерфейс может реализовывать более одного типа. Например, если два типа S1 и S2 имеют метод set

func (p T) Read(p []byte) (n int, err error)
func (p T) Write(p []byte) (n int, err error)
func (p T) Close() error

(где T означает либо S1, либо S2) то интерфейс File реализуется как S1, так и S2, независимо от того, какие другие методы S1 и S2 могут иметь или совместно использовать.

Каждый тип, являющийся членом набора типов интерфейса, реализует этот интерфейс. Любой данный тип может реализовывать несколько различных интерфейсов. Например, все типы реализуют пустой интерфейс, который обозначает набор всех (не интерфейсных) типов:

interface{}

Для удобства предварительно объявленный тип any является псевдонимом для пустого интерфейса. [Go 1.18]

Аналогично, рассмотрим эту спецификацию интерфейса, которая появляется в объявлении типа для определения интерфейса с именем Locker:

type Locker interface {
    Lock()
    Unlock()
}

Если S1 и S2 также реализуют

func (p T) Lock() { … }
func (p T) Unlock() { … }

они реализуют интерфейс Locker, а также интерфейс File.

Встроенные интерфейсы

В более общей форме интерфейс T может использовать (возможно, квалифицированный) тип интерфейса имя E в качестве элемента интерфейса. Это называется встраиванием интерфейса E в T [Go 1.14]. Набор типов T является пересечением наборов типов, определенных явно объявленными методами T, и наборов типов встроенных интерфейсов T. Другими словами, набор типов T — это набор всех типов, которые реализуют все явно объявленные методы T, а также все методы E [Go 1.18].

type Reader interface {
    Read(p []byte) (n int, err error)
    Close() error
}
type Writer interface {
    Write(p []byte) (n int, err error)
    Close() error
}
// Методы ReadWriter — это Read, Write и Close.
type ReadWriter interface {
    Reader  // включает методы Reader в набор методов ReadWriter
    Writer  // включает методы Writer в набор методов ReadWriter
}

При встраивании интерфейсов методы с одинаковыми именами должны иметь идентичные сигнатуры.

type ReadCloser interface {
    Reader   // включает методы Reader в набор методов ReadCloser
    Close()  // недопустимо: сигнатуры Reader.Close и Close различаются
}

Общие интерфейсы

В своей наиболее общей форме элемент интерфейса также может быть произвольным термином типа T, или термином вида ~T, указывающим базовый тип T, или объединением терминов t1|t2|…|tn [Go 1.18]. Вместе со спецификациями методов эти элементы позволяют точно определить набор типов интерфейса следующим образом:

Квантификация "набор всех типов, не являющихся интерфейсами" относится не только ко всем (не являющимся интерфейсами) типам, объявленным в данной программе, но и ко всем возможным типам во всех возможных программах, и поэтому является бесконечным. Аналогично, если задано множество всех типов, не являющихся интерфейсами, которые реализуют определенный метод, то пересечение множеств методов этих типов будет содержать именно этот метод, даже если все типы в данной программе всегда сопрягают этот метод с другим методом.

По конструкции, набор типов интерфейса никогда не содержит тип интерфейса.

// Интерфейс, представляющий только тип int.
interface {
    int
}
// Интерфейс, представляющий все типы с базовым типом int.
interface {
    ~int
}
// Интерфейс, представляющий все типы с базовым типом int, которые реализуют метод String.
interface {
    ~int
    String() string
}
// Интерфейс, представляющий пустой набор типов: нет типа, который был бы одновременно int и string.
interface {
    int
    string
}

В термине вида ~T базовый тип T должен быть самим собой, и T не может быть интерфейсом.

type MyInt int
interface {
    ~[]byte  // базовый тип []byte — это он сам
    ~MyInt   // недопустимо: базовый тип MyInt — не MyInt
    ~error   // недопустимо: error — интерфейс
}

Элементы объединения обозначают объединения наборов типов:

// Интерфейс Float представляет все типы с плавающей запятой
// (включая любые именованные типы, базовыми типами которых являются
// float32 или float64).
type Float interface {
    ~float32 | ~float64
}

Тип T в термине вида T или ~T не может быть типовым параметром, а наборы типов всех неинтерфейсных терминов должны быть попарно непересекающимися (попарное пересечение наборов типов должно быть пустым). При заданном типовом параметре P:

interface {
    P                // недопустимо: P является типовым параметром
    int | ~P         // недопустимо: P является типовым параметром
    ~int | MyInt     // недопустимо: типовые множества для ~int и MyInt не являются непересекающимися (~int включает MyInt)
    float32 | Float  // пересекающиеся множества типов, но Float является интерфейсом
}

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

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

var x Float                     // недопустимо: Float не является базовым интерфейсом
var x interface{} = Float(nil)  // недопустимо
type Floatish struct {
    f Float                 // недопустимо
}

Тип интерфейса T не может встраивать элемент типа, то есть содержать или встраивать T, прямо или косвенно.

// недопустимо: Bad не может встраивать себя
type Bad interface {
    Bad
}
// недопустимо: Bad1 не может встраивать себя с помощью Bad2
type Bad1 interface {
    Bad2
}
type Bad2 interface {
    Bad1
}
// недопустимо: Bad3 не может встраивать объединение, содержащее Bad3
type Bad3 interface {
    ~int | ~string | Bad3
}
// недопустимо: Bad4 не может встраивать массив, содержащий Bad4 в качестве типа элемента
type Bad4 interface {
    [10]Bad4
}

Реализация интерфейса

Тип T реализует интерфейс I, если

Значение типа T реализует интерфейс, если T реализует интерфейс.

Типы карт

Карта — это неупорядоченная группа элементов одного типа, называемого типом элемента, индексируемая набором уникальных ключей другого типа, называемого типом ключа. Значение неинициализированной карты равно nil.

MapType = "map" "[" KeyType "]" ElementType .
KeyType = Type .

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

map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}

Количество элементов карты называется ее длиной. Для карты m ее можно узнать с помощью встроенной функции len и может изменяться во время выполнения. Элементы могут добавляться во время выполнения с помощью присваиваний и извлекаться с помощью индексных выражений; они могут быть удалены с помощью встроенных функций delete и clear.

Новое пустое значение карты создается с помощью встроенной функции make, которая принимает в качестве аргументов тип карты и необязательное указание емкости:

make(map[string]int)
make(map[string]int, 100)

Начальная емкость не ограничивает размер: карты увеличиваются, чтобы вместить количество элементов, хранящихся в них, за исключением карт nil. Карта nil эквивалентна пустой карте, за исключением того, что в нее нельзя добавлять элементы.

Типы каналов

Канал предоставляет механизм для одновременного выполнения функций для обмена данными путем отправки и получения значений указанного типа элемента. Значение значения неинициализированного канала равно nil.

ChannelType = ( "chan" | „chan“ "<-" | "<-" "chan" ) ElementType .

Необязательный оператор <- указывает направление канала, отправку или прием. Если направление указано, канал является направленным, в противном случае он является двунаправленным. Канал может быть ограничен только отправкой или только приемом с помощью присвоения или явного преобразования.

chan T          // может использоваться для отправки и приема значений типа T
chan<- float64  // может использоваться только для отправки float64
<-chan int      // может использоваться только для приема int

Оператор <- ассоциируется с самым левым chan возможным:

chan<- chan int    // то же, что chan<- (chan int)
chan<- <-chan int  // то же, что chan<- (<-chan int)
<-chan <-chan int  // то же, что <-chan (<-chan int)
chan (<-chan int)

Новое инициализированное значение канала можно создать с помощью встроенной функции make, которая принимает в качестве аргументов тип канала и опциональную capacity:

make(chan int, 100)

Емкость, выраженная в количестве элементов, задает размер буфера в канале. Если емкость равна нулю или отсутствует, канал является небуферизованным, и обмен данными удается только тогда, когда и отправитель, и получатель готовы. В противном случае канал является буферизованным, и обмен данными удается без блокировки, если буфер не заполнен (отправка) или не пуст (прием). Канал nil никогда не готов к обмену данными.

Канал может быть закрыт с помощью встроенной функции close. Форма многозначного присваивания оператора receive сообщает, было ли полученное значение отправлено до закрытия канала.

Один канал может использоваться в операторах отправки, операциях приема, и вызовах встроенных функций cap и len любым количеством goroutines без дополнительной синхронизации. Каналы действуют как очереди по принципу "первым пришел, первым обслужен". Например, если один goroutine отправляет значения по каналу, а второй goroutine их принимает, значения принимаются в том же порядке, в котором они были отправлены.

Свойства типов и значений

Представление значений

Значения предварительно объявленных типов (см. ниже интерфейсы any и error), массивы и структуры являются самодостаточными: Каждое такое значение содержит полную копию всех своих данных, и переменные таких типов хранят все значение. Например, переменная массива предоставляет хранилище (переменные) для всех элементов массива. Соответствующие нулевые значения специфичны для типов значений; они никогда не являются nil.

Значения указателей, функций, срезов, карт и каналов, отличные от nil, содержат ссылки на базовые данные, которые могут быть общими для нескольких значений:

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

Когда несколько значений используют общие базовые данные, изменение одного значения может привести к изменению другого. Например, изменение элемента среза приведет к изменению этого элемента в базовом массиве для всех срезов, которые используют этот массив.

Базовые типы

Каждый тип T имеет базовый тип: если T является одним из заранее объявленных типов boolean, numeric или string, либо литералом типа, то соответствующим базовым типом является сам T. В противном случае базовым типом T является базовый тип типа, на который T ссылается в своем объявлении. Для параметра типа, который является базовым типом его ограничением типа, который всегда является интерфейсом.

type (
    A1 = string
    A2 = A1
)
type (
    B1 string
    B2 B1
    B3 []B1
    B4 B3
)
func f[P any](x P) { … }

Базовый тип string, A1, A2, B1, и B2string. Базовый тип []B1, B3 и B4[]B1. Базовый тип Pinterface{}.

Идентичность типов

Два типа могут быть либо идентичными ("одинаковыми"), либо различными.

Именованный тип всегда отличается от любого другого типа. В противном случае два типа являются идентичными, если их базовые типовые литералы структурно эквивалентны, то есть имеют одинаковую литеральную структуру, а соответствующие компоненты имеют идентичные типы. Подробнее:

Учитывая объявления

type (
	A0 = []string
	A1 = A0
	A2 = struct{ a, b int }
	A3 = int
	A4 = func(A3, float64) *A0
	A5 = func(x int, _ float64) *[]string
	B0 A0
	B1 []string
	B2 struct{ a, b int }
	B3 struct{ a, c int }
	B4 func(int, float64) *B0
	B5 func(x int, y float64) *A1
	C0 = B0
	D0[P1, P2 any] struct{ x P1; y P2 }
	E0 = D0[int, string]
)

эти типы идентичны:

A0, A1 и []string
A2 и struct{ a, b int }
A3 и int
A4, func(int, float64) *[]string, и A5
B0 и C0
D0[int, string] и E0
[]int и []int
struct{ a, b *B5 } и struct{ a, b *B5 }
func(x int, y float64) *[]string, func(int, float64) (result *[]string) и A5

B0 и B1 различаются, поскольку они являются новыми типами, созданными отдельными определениями типов; func(int, float64) *B0 и func(x int, y float64) *[]string отличаются, потому что B0 отличается от []string; а P1 и P2 отличаются, потому что они являются разными параметрами типа. D0[int, string] и struct{ x int; y string } отличаются, потому что первый является инстанцированным определенным типом, а второй — типовым литералом (но они по-прежнему присваиваемы).

Присваиваемость

Значение x типа V является присваиваемым переменной переменной типа T ("x присваивается T"), если выполняется одно из следующих условий:

Кроме того, если тип x V или T являются параметрами типа, x может быть присвоен переменной типа T, если выполняется одно из следующих условий:

Представимость

Константа x x является представимой значением типа T, где T не является типовым параметром, если выполняется одно из следующих условий:

Если T является типовым параметром, x может быть представлено значением типа T, если x может быть представлено значением каждого типа в наборе типов T.

x                   T           x может быть представлено значением T, потому что
„a“                 byte        97 входит в набор значений byte
97                  rune        rune является псевдонимом для int32, а 97 входит в набор 32-битных целых чисел
"foo"               string      "foo" находится в наборе значений string
1024                int16       1024 находится в наборе 16-битных целых чисел
42.0                byte        42 находится в наборе 8-битных целых чисел без знака
1e10                uint64      10000000000 входит в набор 64-разрядных целых чисел без знака
2.718281828459045   float32     2. 718281828459045 округляется до 2.7182817, которое входит в набор значений float32
-1e-1000            float64     -1e-1000 округляется до IEEE -0.0, которое далее упрощается до 0.0
0i                  int         0 является целочисленным значением
(42 + 0i)           float32     42.0 (с нулевой мнимой частью) входит в набор значений float32
x                   T           x не может быть представлено значением T, потому что
0                   bool        0 не входит в набор значений boolean
„a“                 string      „a“ является руной, оно не входит в набор значений string
1024                byte        1024 не входит в набор 8-битных целых чисел без знака
-1                  uint16      -1 не входит в набор 16-битных целых чисел без знака
1.1                 int         1.1 не является целым числом
42i                 float32     (0 + 42i) не входит в набор значений float32
1e1000              float64     1e1000 переполняется до IEEE +Inf после округления

Наборы методов

Набор методов типа определяет методы, которые могут быть вызваны на операнде этого типа. Каждый тип имеет связанный с ним (возможно пустой) набор методов:

К структурам (и указателям на структуры), содержащим встроенные поля, применяются дополнительные правила, описанные в разделе Типы структур. Любой другой тип имеет пустой набор методов.

В наборе методов каждый метод должен иметь уникальное непустое имя метода.

Блоки

Блок — это потенциально пустая последовательность объявлений и операторов внутри соответствующих фигурных скобок.

Block         = "{" StatementList "}" .
StatementList = { Statement ";" } .

Помимо явных блоков в исходном коде, существуют неявные блоки:

  1. Блок universe охватывает весь исходный текст Go.
  2. Каждый пакет имеет блок пакета, содержащий весь исходный текст Go для этого пакета.
  3. Каждый файл имеет блок файла, содержащий весь исходный текст Go в этом файле.
  4. Каждое "if", "for" и "switch" утверждение считается находящимся в своем собственном неявном блоке.
  5. Каждое предложение в операторе "switch" или "select" действует как неявный блок.

Блоки вложены друг в друга и влияют на область действия.

Декларации и область действия

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

Пустой идентификатор blank identifier может использоваться как любой другой идентификатор в объявлении, но он не вводит связь и, следовательно, не объявляется. В блоке пакета идентификатор init может использоваться только для init function declarations, and like the blank identifier it does not introduce a new binding.

Declaration  = ConstDecl | TypeDecl | VarDecl .
TopLevelDecl = Declaration | FunctionDecl | MethodDecl .

Область действия объявленного идентификатора — это часть исходного текста, в которой идентификатор обозначает указанную константу, тип, переменную, функцию, метку или пакет.

В Go лексическая область действия определяется с помощью блоков:

  1. Область действия предварительно объявленного идентификатора — это блок universe.
  2. Область действия идентификатора, обозначающего константу, тип, переменную или функцию (но не метод), объявленного на верхнем уровне (вне любой функции), — это блок package.
  3. Область действия имени пакета импортированного пакета — это блок файла файла, содержащего объявление импорта.
  4. Область действия идентификатора, обозначающего получателя метода, параметр функции, или переменную результата, — это тело функции.
  5. Область действия идентификатора, обозначающего параметр типа функции или объявленного получателем метода, начинается после имени функции и заканчивается в конце тела функции.
  6. Область действия идентификатора, обозначающего параметр типа типа, начинается после имени типа и заканчивается в конце TypeSpec.
  7. Область действия идентификатора константы или переменной, объявленного внутри функции, начинается в конце ConstSpec или VarSpec (ShortVarDecl для объявлений коротких переменных) и заканчивается в конце самого внутреннего блока, его содержащего.
  8. Область действия идентификатора типа, объявленного внутри функции, начинается с идентификатора в TypeSpec и заканчивается в конце самого внутреннего блока, его содержащего.

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

Положение пакета не является объявлением; имя пакета не появляется ни в одной области действия. Его цель — идентифицировать файлы, принадлежащие к одному и тому же пакету, и указать имя пакета по умолчанию для импортных.

Области действия меток

Метки объявляются с помощью обозначенных операторов и используются в операторах "break", "continue" и "goto". Не допускается определять метку, которая никогда не используется. В отличие от других идентификаторов, метки не имеют области действия блока и не конфликтуют с идентификаторами, которые не являются метками. Область действия метки — это тело функции, в которой она объявлена, и исключает тела любой вложенной функции.

Пустой идентификатор

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

Предварительно объявленные идентификаторы

Следующие идентификаторы неявно объявляются в блоке universe [Go 1.18] [Go 1.21]:

Типы:
	any bool byte comparable
	complex64 complex128 error float32 float64
	int int8 int16 int32 int64 rune string
	uint uint8 uint16 uint32 uint64 uintptr
Константы:
        true false iota
Нулевое значение:
        nil
Функции:
        append cap clear close complex copy delete imag len
        make max min new panic print println real recover

Экспортируемые идентификаторы

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

  1. первый символ имени идентификатора является заглавной буквой Unicode (категория символов Unicode Lu); и
  2. идентификатор объявлен в блоке пакета или является именем поля или имя метода.

Все остальные идентификаторы не экспортируются.

Уникальность идентификаторов

При наличии набора идентификаторов идентификатор называется уникальным, если он отличается от всех других в наборе. Два идентификатора отличаются друг от друга, если они написаны по-разному или появляются в разных пакетах и не экспортируются. В противном случае они считаются одинаковыми.

Объявления констант

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

ConstDecl      = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec      = IdentifierList [ [ Type ] "=" ExpressionList ] .
IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .

Если тип присутствует, все константы принимают указанный тип, и выражения должны быть присваиваемыми этому типу, который не должен быть типовым параметром. Если тип опущен, константы принимают отдельные типы соответствующих выражений. Если значения выражений являются нетипизированными константами, объявленные константы остаются безтипными, а идентификаторы констант обозначают значения констант. Например, если выражение является литералом с плавающей запятой, идентификатор константы обозначает константу с плавающей запятой, даже если дробная часть литерала равна нулю.

const Pi float64 = 3.14159265358979323846
const zero = 0.0         // константа с плавающей запятой без типа
const (
    size int64 = 1024
    eof        = -1  // константа целого типа без типа
)
const a, b, c = 3, 4, "foo"  // a = 3, b = 4, c = "foo", константы целого типа и строковые константы без указания типа
const u, v float32 = 0, 3    // u = 0.0, v = 3.0

В скобках const объявления списка список выражений может быть опущен из любого, кроме первого ConstSpec. Такой пустой список эквивалентен текстовой подстановке первого предшествующего непустого списка выражений и его типа, если таковой имеется. Таким образом, опущение списка выражений эквивалентно повторению предыдущего списка. Количество идентификаторов должно быть равно количеству выражений в предыдущем списке. Вместе с генератором констант iota этот механизм позволяет легко объявлять последовательные значения:

const (
    Sunday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Partyday
    numberOfDays  // эта константа не экспортируется
)

Iota

В пределах объявлением константы предварительно объявленный идентификатор iota представляет собой последовательные нетипизированные целые константы. Его значением является индекс соответствующего ConstSpec в этом объявлении константы, начиная с нуля. Его можно использовать для построения набора связанных констант:

const (
    c0 = iota  // c0 == 0
    c1 = iota  // c1 == 1
    c2 = iota  // c2 == 2
)
const (
    a = 1 << iota  // a == 1  (iota == 0)
    b = 1 << iota  // b == 2  (iota == 1)
    c = 3          // c == 3  (iota == 2, неиспользуется)
    d = 1 << iota  // d == 8  (iota == 3)
)
const (
    u         = iota * 42  // u == 0     (нетипизированная целочисленная константа)
    v float64 = iota * 42  // v == 42.0  (константа float64)
    w         = iota * 42  // w == 84    (нетипизированная целочисленная константа)
)
const x = iota  // x == 0
const y = iota  // y == 0

По определению, несколько использований iota в одном и том же ConstSpec имеют одинаковое значение:

const (
    bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0  (iota == 0)
    bit1, mask1                           // bit1 == 2, mask1 == 1  (iota == 1)
    _, _                                  //                        (iota == 2, unused)
    bit3, mask3                           // bit3 == 8, mask3 == 7  (iota == 3)
)

В последнем примере используется неявное повторение последнего непустого списка выражений.

Объявления типов

Объявление типа связывает идентификатор, имя типа, с типом. Объявления типов бывают двух видов: объявления псевдонимов и определения типов.

TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .

Объявления псевдонимов

Объявление псевдонима связывает идентификатор с заданным типом [Go 1.9].

AliasDecl = identifier [ TypeParameters ] "=" Type .

В пределах области действия идентификатора он служит в качестве псевдонима для данного типа.

type (
    nodeList = [] *Node  // nodeList и []*Node являются идентичными типами
    Polar    = polar    // Polar и polar обозначают идентичные типы
)

Если в объявлении псевдонима указаны параметры типа [Go 1.24], имя типа обозначает обобщенный псевдоним. Обобщенные псевдонимы должны быть инстанцированы при их использовании.

type set[P comparable] = map[P]bool

В объявлении псевдонима данный тип не может быть параметром типа.

type A[P any] = P    // недопустимо: P является параметром типа

Определения типов

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

TypeDef = identifier [ TypeParameters ] Type .

Новый тип называется определенным типом. Он отличается от любого другого типа, включая тип, из которого он создан.

type (
    Point struct{ x, y float64 }  // Point и struct{ x, y float64 } являются разными типами
    polar Point                   // polar и Point обозначают разные типы
)
type TreeNode struct {
    left, right *TreeNode
    value any
}
type Block interface {
    BlockSize() int
    Encrypt(src, dst []byte)
    Decrypt(src, dst []byte)
}

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

// Mutex — это тип данных с двумя методами: Lock и Unlock.
type Mutex struct         { /* Поля Mutex */ }
func (m *Mutex) Lock()    { /* Реализация Lock */ }
func (m *Mutex) Unlock()  { /* Реализация Unlock */ }
// NewMutex имеет тот же состав, что и Mutex, но его набор методов пуст.
type NewMutex Mutex
// Набор методов базового типа PtrMutex *Mutex остается неизменным,
// но набор методов PtrMutex пуст.
type PtrMutex *Mutex
// Набор методов *PrintableMutex содержит методы
// Lock и Unlock, связанные с его встроенным полем Mutex.
type PrintableMutex struct {    Mutex
}
// MyBlock — это тип интерфейса, который имеет тот же набор методов, что и Block.
type MyBlock Block

Определения типов могут использоваться для определения различных типов булевых, числовых или строковых типов и связывания с ними методов:

type TimeZone int
const (
    EST TimeZone = -(5 + iota)
    CST
    MST
    PST
)
func (tz TimeZone) String() string {
    return fmt.Sprintf("GMT%+dh", tz)
}

Если определение типа указывает параметры типа, имя типа обозначает универсальный тип. Универсальные типы должны быть инстанцированы при их использовании.

type List[T any] struct {
    next  *List[T]
    value T
}

В определении типа данный тип не может быть параметром типа.

type T[P any] P    // недопустимо: P является типовым параметром
func f[T any]() {
    type L T   // недопустимо: T является типовым параметром, объявленным окружающей функцией
}

Универсальный тип также может иметь связанные с ним методы. В этом случае получатели метода должны объявлять такое же количество параметров типа, как и в определении универсального типа.

// Метод Len возвращает количество элементов в связанном списке l.
func (l *List[T]) Len() int  { … }

Объявления параметров типа

Список параметров типа объявляет параметры типа обобщенной функции или объявления типа. Список параметров типа выглядит как обычный список параметров функции, за исключением того, что все имена параметров типа должны быть присутствовать, а список заключается в квадратные скобки, а не в круглые [Go 1.18].

TypeParameters = "[" TypeParamList [ "," ] "]" .
TypeParamList  = TypeParamDecl { "," TypeParamDecl } .
TypeParamDecl  = IdentifierList TypeConstraint .

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

[P any]
[S interface{ ~[]byte|string }]
[S ~[]E, E any]
[P Constraint[int]]
[_ any]

Так же как каждый обычный параметр функции имеет тип параметра, каждый параметр типа имеет соответствующий (мета-)тип, который называется его ограничением типа.

Неоднозначность разбора возникает, когда список типовых параметров для обобщенного типа объявляет единственный типовой параметр P с ограничением C таким образом, что текст P C образует допустимое выражение:

type T[P *C] …
type T[P (C)] …
type T[P *C|Q] …
…

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

type T[P interface{*C}] …
type T[P *C,] …

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

В списке параметров типа общего типа T ограничение типа не может (прямо или косвенно через список параметров типа другого общего типа) ссылаться на T.

type T1[P T1[P]] …                    // недопустимо: T1 ссылается на себя
type T2[P interface{ T2[int] }] …     // недопустимо: T2 ссылается на себя
type T3[P interface{ m(T3[int])}] …   // недопустимо: T3 ссылается на себя
type T4[P T5[P]] …                    // недопустимо: T4 ссылается на T5 и
type T5[P T4[P]] …                    //                T5 ссылается на T4
type T6[P int] struct{ f *T6[P] }     // допустимо: ссылка на T6 отсутствует в списке параметров типа

Ограничения типов

Ограничение типа — это интерфейс, который определяет набор допустимых аргументов типа для соответствующего параметра типа и контролирует операции, поддерживаемые значениями этого параметра типа [Go 1.18].

TypeConstraint = TypeElem .

Если ограничение является литералом интерфейса в форме interface{E}, где E является встроенным элементом типа (а не методом) в типе список параметров, то для удобства можно опустить заключающий его interface{ … }:

[T []P]                      // = [T interface{[]P}]
[T ~int]                     // = [T interface{~int}]
[T int|string]               // = [T interface{int|string}]
type Constraint ~int         // недопустимо: ~int отсутствует в списке параметров типа

Предварительно объявленные тип интерфейса comparable обозначает набор всех типов, не являющихся интерфейсами, которые являются строго сопоставимыми [Go 1.18].

Несмотря на то, что интерфейсы, которые не являются параметрами типа, являются сопоставимыми, они не являются строго сопоставимыми и, следовательно, не реализуют comparable. Однако они удовлетворяют comparable.

int                          // реализует сравнимость (int является строго сопоставимым)
[]byte                       // не реализует comparable (срезы не могут быть сопоставлены)
interface{}                  // не реализует comparable (см. выше)
interface{ ~int | ~string }  // только параметр типа: реализует comparable (типы int, string являются строго сопоставимыми)
interface{ comparable }      // только параметр типа: реализует сравниваемость (comparable реализует себя)
interface{ ~int | ~[]byte }  // только параметр типа: не реализует сравниваемость (срезы не сравнимы)
interface{ ~struct{ any } }  // только параметр типа: не реализует сравнимость (поле any не является строго сравнимым)

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

Соответствие ограничению типа

Аргумент типа T удовлетворяет ограничению типа C , если T является элементом набора типов, определенного C; другими словами, если T реализует C. В качестве исключения, строго сопоставимое ограничение типа может также удовлетворяться сопоставимым (не обязательно строго сопоставимым) аргументом типа [Go 1.20]. Точнее:

Тип T удовлетворяет ограничению C, если

type argument      type constraint                // constraint satisfaction
int                interface{ ~int }              // satisfied: int implements interface{ ~int }
string             comparable                     // satisfied: string implements comparable (string is strictly comparable)
[]byte             comparable                     // not satisfied: slices are not comparable
any                interface{ comparable; int }   // not satisfied: any does not implement interface{ int }
any                comparable                     // satisfied: any is comparable and implements the basic interface any
struct{f any}      comparable                     // satisfied: struct{f any} is comparable and implements the basic interface any
any                interface{ comparable; m() }   // not satisfied: any does not implement the basic interface interface{ m() }
interface{ m() }   interface{ comparable; m() }   // satisfied: interface{ m() } is comparable and implements the basic interface interface{ m() }

Из-за исключения в правиле удовлетворения ограничений сравнение операндов типа параметров может вызвать панику во время выполнения (даже если сопоставимые параметры типа всегда строго сопоставимы).

Объявления переменных

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

VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec = IdentifierList ( Тип [ "=" Список выражений ] | "=" Список выражений ) .
var i int
var U, V, W float64
var k = 0
var x, y float32 = -1, -2
var (
    i       int
    u, v, s = 2.0, 3.0, "bar"
)
var re, im = complexSqrt(-1)
var _, found = entries[name]  // поиск в карте; интересует только "found"

Если задан список выражений, переменные инициализируются с помощью выражений в соответствии с правилами для операторов присваивания. В противном случае каждая переменная инициализируется своим нулевым значением.

Если тип присутствует, каждой переменной присваивается этот тип. В противном случае каждой переменной присваивается тип соответствующего инициализирующего значения в присваивании. Если это значение является константой без типа, оно сначала неявно преобразуется в свой тип по умолчанию; если это нетипизированное булево значение, оно сначала неявно преобразуется в тип bool. Предварительно объявленный идентификатор nil не может использоваться для инициализации переменной без явного типа.

var d = math.Sin(0.5)  // d является float64
var i = 42             // i является int
var t, ok = x.(T)      // t является T, ok является bool
var n = nil            // недопустимо

Ограничение реализации: компилятор может запретить объявление переменной внутри тела функции, если переменная никогда не используется.

Короткие объявления переменных

Краткое объявление переменной использует синтаксис:

ShortVarDecl = IdentifierList ":=" ExpressionList .

Это сокращенная форма обычного объявления переменной с выражениями инициализации, но без типов:

"var" IdentifierList "=" ExpressionList .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w, _ := os.Pipe()  // os.Pipe() возвращает связанную пару файлов и ошибку, если она есть
_, y, _ := coord(p)   // coord() возвращает три значения; интересует только координата y

В отличие от обычных объявлений переменных, сокращенное объявление переменных может переобъявить переменные, если они были первоначально объявлены ранее в том же блоке (или в списке параметров, если блок является телом функции) с тем же типом, и по крайней мере одна из не-пустых переменных является новой. Следовательно, повторное объявление может появляться только в коротком объявлении с несколькими переменными. Повторное объявление не вводит новую переменную; оно просто присваивает новое значение исходному. Непустые имена переменных слева от := должны быть уникальными.

field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)  // повторное объявление offset
x, y, x := 1, 2, 3                        // недопустимо: x повторяется в левой части :=

Короткие объявления переменных могут появляться только внутри функций. В некоторых контекстах, таких как инициализаторы для "if", „for“ или "switch" , они могут использоваться для объявления локальных временных переменных.

Объявления функций

Объявление функции связывает идентификатор, имя функции, с функцией.

FunctionDecl = "func" FunctionName [ TypeParameters ] Signature [ FunctionBody ] .
FunctionName = identifier .
FunctionBody = Block .

Если в сигнатуре функции signature объявлены параметры результата, список операторов в теле функции должен заканчиваться завершающим оператором.

func IndexRune(s string, r rune) int {
    for i, c := range s {
       if c == r {
          return i
       }
    }
    // неверно: отсутствует оператор return
}

Если в объявлении функции указаны параметры типа, имя функции обозначает универсальную функцию. Общая функция должна быть инстанцирована, прежде чем ее можно будет вызвать или использовать в качестве значения.

func min[T ~int|~float64](x, y T) T {
	if x < y {
		return x
	}
	return y
}

В объявлении функции без типовых параметров можно опустить тело. Такое объявление предоставляет сигнатуру для функции, реализованной вне Go, например, для ассемблерной процедуры.

func flushICache(begin, end uintptr)  // реализовано внешне

Объявления методов

Метод — это функция с приемником. Объявление метода связывает идентификатор, имя метода, с методом и связывает метод с базовым типом приемника.

MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
Receiver   = Parameters .

Приемник указывается с помощью дополнительного раздела параметров, предшествующего имени метода . В этом разделе параметров должен быть объявлен единственный невариативный параметр — приемник. Его тип должен быть определенным типом T или указателем на определенный тип T, за которым может следовать список имен типов параметров [P1, P2, …], заключенным в квадратные скобки. T называется базовым типом приемника. Базовый тип получателя не может быть указателем или типом интерфейса и должен быть определен в том же пакете, что и метод. Метод считается связанным с базовым типом получателя, и имя метода видимо только в пределах селекторов для типа T или *T.

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

Для базового типа имена методов, связанных с ним, не должны быть пустыми и должны быть уникальными. Если базовый тип является типом структуры, то имена методов и полей не должны быть пустыми и должны быть уникальными.

Для заданного типа Point объявления

func (p *Point) Length() float64 {
    return math.Sqrt(p.x * p.x + p.y * p.y)
}
func (p *Point) Scale(factor float64) {
    p.x *= factor
    p.y *= factor
}

привяжите методы Length и Scale, с типом приемника *Point, к базовому типу Point.

Если базовый тип приемника является общим типом, то спецификация приемника должна объявить соответствующие типовые параметры для метода , который будет использоваться. Это делает типовые параметры приемника доступными для метода. Синтаксически объявление типового параметра выглядит как инстанциизация базового типа получателя: аргументы типа должны быть идентификаторами, обозначающими объявляемые параметры типа, по одному для каждого параметра типа базового типа получателя. Имена параметров типа не должны совпадать с соответствующими именами параметров в определении базового типа приемника , и все непустые имена параметров должны быть уникальными в разделе параметров приемника и сигнатуре метода. Ограничения параметров типа приемника подразумеваются определением базового типа приемника: соответствующие параметры типа имеют соответствующие ограничения.

type Pair[A, B any] struct {
    a A
    b B
}
func (p Pair[A, B]) Swap() Pair[B, A]  { … }  // приемник объявляет A, B
func (p Pair[First, _]) First() First  { … }  // приемник объявляет First, соответствует A в Pair

Если тип приемника обозначается (указателем на) псевдонимом, псевдоним не должен быть общим и не должен обозначать инстанциированный общий тип, ни прямо, ни косвенно через другой псевдоним, и независимо от косвенных указателей.

type GPoint[P any] = Point
type HPoint        = *GPoint[int]
type IPair         = Pair[int, int]
func (*GPoint[P]) Draw(P)   { … }  // недопустимо: псевдоним не должен быть общим
func (HPoint) Draw(P)       { … }  // недопустимо: псевдоним не должен обозначать инстанциированный тип GPoint[int]
func (*IPair) Second() int  { … }  // недопустимо: псевдоним не должен обозначать инстанциированный тип Pair[int, int]

Выражения

Выражение определяет вычисление значения путем применения операторов и функций к операндам.

Операнды

Операнды обозначают элементарные значения в выражении. Операнд может быть литералом, (возможно квалифицированным) не-пустым идентификатором, обозначающим константу, переменную или функцию, или выражение в скобках.

Operand     = Literal | OperandName [ TypeArgs ] | "(" Expression ")" .
Literal     = BasicLit | CompositeLit | FunctionLit .
BasicLit    = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent .

За именем операнда, обозначающим общую функцию , может следовать список аргументов типа; результирующий операнд является инстанциированной функцией.

Пустой идентификатор blank identifier может появляться в качестве операнда только в левой части оператора присваивания.

Ограничение реализации: компилятор не обязан сообщать об ошибке, если тип операнда является типовым параметром с пустым набором типов. Функции с такими параметрами типа не могут быть инстанцированы; любая попытка приведет к ошибке в месте инстанцирования.

Квалифицированные идентификаторы

Квалифицированный идентификатор — это идентификатор, квалифицированный префиксом имени пакета. И имя пакета, и идентификатор не должны быть пустыми.

QualifiedIdent = PackageName "." identifier .

Квалифицированный идентификатор обращается к идентификатору в другом пакете, который должен быть импортирован. Идентификатор должен быть экспортирован и объявлен в блоке package block этого пакета.

math.Sin // обозначает функцию Sin в пакете math

Составные литералы

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

CompositeLit = LiteralType LiteralValue .
LiteralType  = StructType | ArrayType | "[" "... " "]" ElementType |
               SliceType | MapType | TypeName [ TypeArgs ] .
LiteralValue = "{" [ ElementList [ "," ] ] "}" .
ElementList  = KeyedElement { "," KeyedElement } .
KeyedElement = [ Key ":" ] Element .
Key          = FieldName | Expression | LiteralValue .
FieldName    = identifier .
Element      = Expression | LiteralValue .

Если LiteralType не является параметром типа, его базовый тип должен быть типом struct, array, slice или map (синтаксис налагает это ограничение, за исключением случаев, когда тип задан как TypeName). Если LiteralType является параметром типа, все типы в его наборе типов должны иметь один и тот же базовый тип, который должен быть допустимым составным литеральным типом. Типы элементов и ключей должны быть присваиваемыми соответствующим типам полей, элементов и ключей типа T; дополнительного преобразования не происходит. Ключ интерпретируется как имя поля для литералов структуры, индекс для литералов массива и среза и ключ для литералов карты. Для литералов карты все элементы должны иметь ключ. Ошибкой является указание нескольких элементов с одинаковым именем поля или постоянным значением ключа. Для неконстантных ключей карты см. раздел порядке оценки.

Для литералов структур применяются следующие правила:

Учитывая объявления

type Point3D struct { x, y, z float64 }
type Line struct { p, q Point3D }

можно написать

origin := Point3D{}                            // нулевое значение для Point3D
line := Line{origin, Point3D{y: -4, z: 12.3}}  // нулевое значение для line.q.x

Для массивов и слайсов-литералов применяются следующие правила:

Получение адреса составного литерала генерирует указатель на уникальную переменную, инициализированную значением литерала.

var pointer *Point3D = &Point3D{y: 1000}

Обратите внимание, что нулевое значение для типа slice или map не совпадает с инициализированным, но пустым значением того же типа. Следовательно, получение адреса пустого композитного литерала типа slice или map не дает того же эффекта, что и выделение нового значения типа slice или map с помощью new.

p1 := &[]int{}    // p1 указывает на инициализированный пустой срез со значением []int{} и длиной 0
p2 := new([]int)  // p2 указывает на неинициализированный срез со значением nil и длиной 0

Длина массива-литерала равна длине, указанной в типе литерала. Если в литерале указано меньше элементов, чем длина, то отсутствующие элементы устанавливаются в нулевое значение для типа элемента массива. Ошибкой является указание элементов с индексными значениями, выходящими за пределы диапазона индексов массива. Обозначение ... указывает длину массива, равную максимальному индексу элемента плюс один.

buffer := [10]string{}             // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5}       // len(intSet) == 6
days := [...]string{"Sat", "Sun"}  // len(days) == 2

Слайс-литерал описывает весь базовый массив-литерал. Таким образом, длина и емкость слайс-литерала равны максимальному индексу элемента плюс один. Слайс-литерал имеет форму

[]T{x1, x2, … xn}

и является сокращением для операции над срезом, примененной к массиву:

tmp := [n]T{x1, x2, … xn}
tmp[0 : n]

Внутри составного литерала типа массива, среза или карты T элементы или ключи карты, которые сами являются составными литералами, могут опускать соответствующий тип литерала, если он идентичен типу элемента или ключа T. Аналогично, элементы или ключи, которые являются адресами составных литералов, могут опускать &T, когда тип элемента или ключа является *T.

[...]Point{{1.5, -3.5} , {0, 0}}     // то же, что [...]Point{Point{1.5, -3.5}, Point{0, 0}}
[][]int{{1, 2, 3}, {4, 5}}          // то же самое, что [][]int{[]int{ 1, 2, 3}, []int{4, 5}}
[][]Point{{{0, 1} , {1, 2}}}         // то же самое, что [][]Point{[]Point{Point{0, 1}, Point{1, 2}}}
map[string]Point{"orig": {0, 0}}    // то же самое, что map[string]Point{" orig": Point{0, 0}}
map[Point]string{{0, 0}: "orig"}    // то же, что map[Point]string{Point{0, 0}: "orig"}
type PPoint *Point
[2]*Point{{1.5, -3.5} , {}}          // то же самое, что [2]*Point{&Point{1.5, -3.5}, &Point{}}
[2]PPoint{{1.5, -3.5}, {}}          // то же самое, что [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}

Неоднозначность разбора возникает, когда составной литерал, использующий форму TypeName типа LiteralType, появляется в качестве операнда между ключевым словом и открывающей скобкой блока оператора "if", "for" или "switch", и составной литерал не заключен в круглые, квадратные или фигурные скобки. В этом редком случае открывающая скобка литерала ошибочно анализируется как вводящая блок операторов. Чтобы устранить неоднозначность, составной литерал должен находиться в круглых скобках.

if x == (T{a,b,c}[i]) { … }
if (x == T{a,b,c}[i]) { … }

Примеры допустимых литералов массива, среза и карты:

// список простых чисел
primes := []int{2, 3, 5, 7, 9, 2147483647}
// гласные[ch] истинно, если ch является гласной
гласные := [128]bool{„a“: true, „e“: true, „i“: true, „o“: true, „u“: true, „y“: true}
// массив [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1}
// частоты в Гц для равномерно темперированной гаммы (A4 = 440 Гц)
noteFrequency := map[string]float32{
    "C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
    "G0": 24.50, „A0“: 27.50, "B0": 30.87,
}

Функциональные литералы

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

FunctionLit = "func" Signature FunctionBody .
func (a, b int, z float64) bool { return a*b < int(z) }

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

f := func(x, y int) int { return x + y }
func(ch chan int) { ch <- ACK }(replyChan)

Функциональные литералы являются замыканиями: они могут ссылаться на переменные , определенные в окружающей функции. Эти переменные затем совместно используются окружающей функцией и функциональным литералом и сохраняются до тех пор, пока они доступны.

Первичные выражения

Первичные выражения являются операндами для унарных и бинарных выражений.

PrimaryExpr   = Operand |
                Conversion |
                MethodExpr |
                PrimaryExpr Selector |
                PrimaryExpr Index |
                PrimaryExpr Slice |
                PrimaryExpr TypeAssertion |
                PrimaryExpr Arguments .
Selector      = "." identifier .
Index         = "[" Expression [ "," ] "]" .
Slice         = "[" [ Expression ] ":" [ Expression ] "]" |
                "[" [ Expression ] ":" Expression ":" Expression "]" .
TypeAssertion = "." "(" Type ")" .
Arguments     = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
x
2
(s + ".txt")
f(3.1415, true)
Point{1, 2}
m["foo"]
s[i : j + 1]
obj.color
f.p[i].x ()

Селекторы

Для первичного выражения x, которое не является именем пакета, выражение селектора

x.f

обозначает поле или метод f значения x (или иногда *x; см. ниже). Идентификатор f называется (полем или методом) селектором; он не должен быть пустым идентификатором. Тип выражения селектора — это тип f. Если x является именем пакета, см. раздел о квалифицированных идентификаторах.

Селектор f может обозначать поле или метод f типа T, или он может ссылаться на поле или метод f вложенного встроенного поля T. Количество вложенных полей, проходящих для достижения f, называется его глубиной в T. Глубина поля или метода f, объявленного в T, равна нулю. Глубина поля или метода f, объявленного во встроенном поле A в T равна глубине f в A плюс один.

К селекторам применяются следующие правила:

  1. Для значения x типа T или *T , где T не является типом указателя или интерфейса, x.f обозначает поле или метод на самой мелкой глубине в T, где существует такой f. Если не существует ровно одного f с наименьшей глубиной, выражение селектора является недопустимым.
  2. Для значения x типа I, где I является типом интерфейса, x.f обозначает фактический метод с именем f динамического значения x. Если в наборе методов I нет метода с именем f, выражение селектора является недопустимым.
  3. В качестве исключения, если тип x является определенным типом указателя, а (*x).f является допустимым выражением селектора, обозначающим поле (но не метод), x.f является сокращением для (*x).f.
  4. Во всех других случаях x.f является недопустимым.
  5. Если x имеет тип указателя и значение nil, а x.f обозначает поле структуры, то присвоение или вычисление x.f вызывает панику во время выполнения.
  6. Если x имеет тип интерфейса и значение nil, вызов или вычисление метода x.f вызывает панику во время выполнения.

Например, при наличии следующих объявлений:

type T0 struct {    x int
}
func (*T0) M0()
type T1 struct {
    y int
}
func (T1) M1()
type T2 struct {
    z int
    T1
    *T0
}
func (*T2) M2()
type Q *T2
var t T2     // с t.T0 != nil
var p *T2    // с p != nil и (*p).T0 != nil
var q Q = p

можно написать:

t.z          // t.z
t.y          // t.T1.y
t.x          // (*t.T0).x
p.z          // (*p).z
p.y          // (*p).T1.y
p.x          // (*(*p).T0).x
q.x          // (*(*q).T0).x        (*q).x является допустимым селектором поля
p.M0()       // ((*p).T0).M0()      M0 ожидает приемник *T0
p.M1()       // ((*p).T1).M1()      M1 ожидает приемник T1
p.M2()       // p.M2()              M2 ожидает приемник *T2
t.M2()       // (&t).M2()           M2 ожидает приемник *T2, см. раздел "Вызовы"

но следующее недействительно:

q.M0()       // (*q).M0 является действительным, но не является селектором поля

Выражения методов

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

MethodExpr   = ReceiverType "." MethodName .
ReceiverType = Type .

Рассмотрим структурный тип T с двумя методами: Mv, приемник которого имеет тип T, и Mp, приемник которого имеет тип *T.

type T struct {
    a int
}
func (tv  T) Mv(a int) int         { return 0 }  // приемник значения
func (tp *T) Mp(f float32) float32 { return 1 }  // указатель-приемник
var t T

Выражение

T.Mv

дает функцию, эквивалентную Mv, но с явным приемником в качестве первого аргумента; она имеет сигнатуру

func(tv T, a int) int

Эта функция может вызываться обычным образом с явным приемником, поэтому эти пять вызовов эквивалентны:

t.Mv(7)
T.Mv(t, 7)
(T).Mv(t, 7)
f1 := T.Mv; f1(t, 7)
f2 := (T).Mv; f2(t, 7)

Аналогично, выражение

(*T).Mp

возвращает значение функции, представляющее Mp с сигнатурой

func(tp *T, f float32) float32

Для метода с приемником значения можно вывести функцию с явным приемником указателя, так что

(*T).Mv

дает значение функции, представляющее Mv с сигнатурой

func(tv *T, a int) int

Такая функция опосредованно через приемник создает значение для передачи в качестве приемника в базовый метод; метод не перезаписывает значение, адрес которого передается в вызове функции.

Последний случай, функция-получатель значения для метода-получателя указателя, является недопустимым, поскольку методы-получатели указателя не входят в набор методов типа значения.

Значения функций, полученные из методов, вызываются с помощью синтаксиса вызова функции; получатель предоставляется в качестве первого аргумента вызова. То есть, если задано f := T.Mv, то f вызывается как f(t, 7), а не t.f(7). Чтобы создать функцию, которая связывает получателя, используйте литерал функции или значение метода.

Допустимо получать значение функции из метода типа интерфейса. Результирующая функция принимает явный приемник этого типа интерфейса.

Значения методов

Если выражение x имеет статический тип T и M находится в наборе методов типа T, x.M называется значением метода. Значение метода x.M является значением функции, которое можно вызвать с теми же аргументами, что и вызов метода x.M. Выражение x вычисляется и сохраняется во время вычисления значения метода; сохраненная копия затем используется в качестве получателя в любых вызовах, которые могут быть выполнены позже.

type S struct { *T }
type T int
func (t T) M() { print(t) }
t := new(T)
s := S{T: t}
f := t.M                    // приемник *t оценивается и сохраняется в f
g := s.M                    // приемник *(s.T) оценивается и сохраняется в g
*t = 42                     // не влияет на сохраненные приемники в f и g

Тип T может быть интерфейсным или неинтерфейсным типом.

Как и в описании выражений методов выше, рассмотрим тип структуры T с двумя методами: Mv, приемник которого имеет тип T, и Mp, приемник которого имеет тип *T.

type T struct {
    a int
}
func (tv  T) Mv(a int) int         { return 0 }  // приемник значения
func (tp *T) Mp(f float32) float32 { return 1 }  // приемник указателя
var t T
var pt *T
func makeT() T

Выражение

t.Mv

возвращает значение функции типа

func(int) int

Эти два вызова эквивалентны:

t.Mv(7)
f := t.Mv; f(7)

Аналогично, выражение

pt.Mp

возвращает значение функции типа

func(float32) float32

Как и в случае с селекторами, ссылка на метод, не являющийся интерфейсом, с приемником значения, использующим указатель, автоматически разыменовывает этот указатель: pt.Mv эквивалентно (*pt).Mv.

Как и в случае с вызовами методов, ссылка на метод, не относящийся к интерфейсу, с указателем приемником , использующим адресуемое значение, автоматически принимает адрес этого значения: t.Mp эквивалентно (&t).Mp.

f := t.Mv; f(7)   // как t.Mv(7)
f := pt.Mp; f(7)  // как pt.Mp(7)
f := pt.Mv; f(7)  // как (*pt).Mv(7)
f := t.Mp; f(7)   // как (&t).Mp (7)
f := makeT().Mp   // недопустимо: результат makeT() не является адресуемым

Хотя в приведенных выше примерах используются типы, не относящиеся к интерфейсам, также допустимо создавать значение метода из значения типа интерфейса.

var i interface { M(int) } = myVal
f := i.M; f(7)  // как i.M(7)

Индексные выражения

Первичный выражение вида

a[x]

обозначает элемент массива, указателя на массив, слайса, строки или карты a, индексированный x. Значение x называется соответственно индексом или ключом карты. Применяются следующие правила:

Если a не является ни картой, ни параметром типа:

Для a типа массива A:

Для a типа указатель на массив:

Для a типа типа слайса S:

Для a типа строка:

Для a типа map M:

Для a типа типа параметра P:

В противном случае a[x] является недопустимым.

Индексный выражение на карте a типа map[K]V используемое в операторе присваивания или инициализации специальной формы

v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]

дает дополнительное нетипизированное булево значение. Значение ok равно true, если ключ x присутствует в карте, и false в противном случае.

Присвоение элементу карты nil вызывает панику во время выполнения.

Выражения среза

Выражения-срезы создают подстроку или срез из строки, массива, указателя на массив или операнда среза. Существует два варианта: простая форма, которая указывает нижнюю и верхнюю границы, и полная форма, которая также указывает границу емкости.

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

Простые выражения среза

Для строки, массива, указателя на массив или слайса a первичное выражение

a[low : high]

создает подстроку или слайс. Индексы low и high выбирают, какие элементы операнда a появятся в результате. Результат имеет индексы, начинающиеся с 0, и длину, равную high - low. После нарезки массива a

a := [5]int{1, 2, 3, 4, 5}
s := a[1:4]

срез s имеет тип []int, длину 3, емкость 4 и элементы

s[0] == 2
s[1] == 3
s[2] == 4

Для удобства любой из индексов может быть опущен. Отсутствующий низкий индекс по умолчанию равен нулю; отсутствующий высокий индекс по умолчанию равен длине разбитого операнда:

a[2:]  // то же, что a[2 : len(a)]
a[:3]  // то же, что a[0 : 3]
a[:]   // то же, что a[0 : len(a)]

Если a является указателем на массив, a[low : high] является сокращением для (*a)[low : high].

Для массивов или строк индексы находятся в диапазоне, если 0 <= low <= high <= len(a), в противном случае они выходят за пределы диапазона. Для фрагментов верхний предел индекса — это емкость фрагмента cap(a), а не длина. Константный индекс должен быть неотрицательным и представляемым значением типа int; для массивов или константных строк константные индексы также должны находиться в диапазоне. Если оба индекса являются константами, они должны удовлетворять условию low <= high. Если индексы выходят за пределы диапазона во время выполнения, возникает паника во время выполнения.

За исключением нетипизированных строк, если операнд срезки является строкой или срезом, результатом операции срезки является неконстантное значение того же типа, что и операнд. Для нетипизированных строковых операндов результатом является неконстантное значение типа string. Если операнд, подвергаемый нарезке, является массивом, он должен быть адресуемым , а результатом операции нарезки является фрагмент с тем же типом элементов, что и массив.

Если срезанный операнд действительного выражения среза является срезом nil, то результатом является срез nil. В противном случае, если результатом является срез, он имеет общий массив с операндом.

var a [10]int
s1 := a[3:7]   // базовый массив s1 — это массив a; &s1[2] == &a[5]
s2 := s1[1:4]  // базовый массив s2 является базовым массивом s1, который является массивом a; &s2[1] == &a[5]
s2[1] = 42     // s2[1] == s1[2] == a[5] == 42; все они ссылаются на один и тот же элемент базового массива
var s []int
s3 := s[:0]    // s3 == nil

Выражения полного среза

Для массива, указателя на массив или среза a (но не для строки) первичное выражение

a[low : high : max]

создает фрагмент того же типа, с той же длиной и элементами, что и простое выражение фрагмента a[low : high]. Кроме того, оно контролирует емкость результирующего фрагмента , устанавливая ее равной max - low. Только первый индекс может быть опущен; по умолчанию он равен 0. После нарезки массива a

a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]

срез t имеет тип []int, длину 2, емкость 4 и элементы

t[0] == 2
t[1] == 3

Что касается простых выражений с срезами, если a является указателем на массив, a[low : high : max] является сокращением для (*a)[low : high : max]. Если оперируемый срез является массивом, он должен быть адресуемым.

Индексы находятся в диапазоне, если 0 <= low <= high <= max <= cap(a), в противном случае они выходят за пределы диапазона. Константный индекс должен быть неотрицательным и представимым значением типа int; для массивов постоянные индексы также должны находиться в диапазоне. Если несколько индексов являются постоянными, то присутствующие константы должны находиться в диапазоне относительно друг друга . Если индексы находятся вне диапазона во время выполнения, происходит паника во время выполнения.

Утверждения типа

Для выражения x типа интерфейса, но не типового параметра, и типа T, основное выражение

x.(T)

утверждает, что x не является nil и что значение, хранящееся в x, имеет тип T. Обозначение x.(T) называется утверждением типа.

Точнее, если T не является типом интерфейса, x.(T) утверждает , что динамический тип x типу T. В этом случае T должен реализовывать (интерфейсный) тип x; в противном случае утверждение типа недействительно, поскольку x не может хранить значение типа T. Если T является типом интерфейса, x.(T) утверждает, что динамический тип x реализует интерфейс T.

Если утверждение типа верно, значение выражения является значением , хранящимся в x, и его тип — T. Если утверждение типа ложно, возникает паника во время выполнения. Другими словами, даже если динамический тип x известен только во время выполнения, тип x.(T) известен как T в корректной программе.

var x interface{} = 7          // x имеет динамический тип int и значение 7
i := x.(int)                   // i имеет тип int и значение 7
type I interface { m() }
func f(y I) {
    s := y.(string)        // недопустимо: string не реализует I (отсутствует метод m)
    r := y.(io.Reader)     // r имеет тип io.Reader, а динамический тип y должен реализовывать как I, так и io.Reader
	…
}

Утверждение типа, используемое в операторе присваивания или инициализации специальной формы

v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok interface{} = x.(T) // динамические типы v и ok — T и bool

дает дополнительное нетипизированное булево значение. Значение ok равно true , если утверждение верно. В противном случае оно равно false, а значение v равно нулевому значению для типа T. В этом случае не происходит run-time panic.

Вызовы

При заданном выражении f типа функции F,

f(a1, a2, … an)

вызывает f с аргументами a1, a2, … an. За исключением одного особого случая, аргументы должны быть однозначными выражениями присваиваемыми типам параметров F и вычисляются до вызова функции. Тип выражения является типом результата F. Вызов метода аналогичен, но сам метод указывается как селектор по значению типа получателя для метода.

math.Atan2(x, y)  // вызов функции
var pt *Point
pt.Scale(3.5)     // вызов метода с приемником pt

Если f обозначает общий функцию, она должна быть инстанцирована перед тем, как ее можно будет вызвать или использовать в качестве значения функции.

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

В вызове функции значение функции и аргументы вычисляются в обычном порядке. После их вычисления выделяется новое хранилище для переменных функции, включая ее параметры и результаты. Затем аргументы вызова передаются функции, что означает, что они присваиваются соответствующим параметрам функции, и вызванная функция начинает выполняться. Параметры возврата функции передаются обратно вызывающему, когда функция возвращается.

Вызов значения функции nil вызывает панику во время выполнения.

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

func Split(s string, pos int) (string, string) {
    return s[0:pos], s[pos:]
}
func Join(s, t string) string {
    return s + t
}
if Join(Split(value, len(value)/2)) != value {
    log.Panic("test fails")
}

Вызов метода x.m() является допустимым, если набор методов (типа) x содержит m, а список аргументов может быть присвоен списку параметров m. Если x является адресуемым и набор методов &x содержит m, то x.m() является сокращением для (&x).m ():

var p Point
p.Scale(3.5)

Нет отдельного типа метода и нет литералов метода.

Передача аргументов параметрам ...

Если f является вариативным с конечным параметром p типа ...T, то в пределах f тип p эквивалентен типу []T. Если f вызывается без фактических аргументов для p, значение, передаваемое в p, равно nil. В противном случае передаваемое значение является новым фрагментом типа []T с новым базовым массивом, последовательные элементы которого являются фактическими аргументами, которые все должны быть присваиваемыми к T. Таким образом, длина и емкость фрагмента равны количество аргументов, связанных с p, и может отличаться для каждого места вызова.

Учитывая функцию и вызовы

func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", „Anna“, "Eileen")

внутри Greeting, who будет иметь значение nil в первом вызове и []string{"Joe", „Anna“, "Eileen"} во втором.

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

Учитывая слайс s и вызов

s := []string{"James", „Jasmine“}
Greeting("goodbye:", s...)

внутри Greeting, who будет иметь то же значение, что и s с тем же базовым массивом.

Инстанции

Генеральная функция или тип инстанциируются путем подстановки типовых аргументов вместо типовых параметров [Go 1.18]. Инстанциирование происходит в два этапа:

  1. Каждый аргумент типа заменяется соответствующим параметром типа в общем объявлении. Эта замена происходит во всем объявлении функции или типа, включая сам список параметров типа и любые типы в этом списке.
  2. После подстановки каждый аргумент типа должен удовлетворять ограничению ограничению (при необходимости, инстанциированное) соответствующего параметра типа. В противном случае инстанциирование завершается с ошибкой.

Инстанциирование типа приводит к созданию нового негенерального именованного типа; инстанциирование функции приводит к созданию новой негенеральной функции.

type parameter list    type arguments    after substitution
[P any]                int               int satisfies any
[S ~[]E, E any]        []int, int        []int satisfies ~[]int, int satisfies any
[P io.Writer]          string            illegal: string doesn't satisfy io.Writer
[P comparable]         any               any satisfies (but does not implement) comparable

При использовании общей функции аргументы типа могут быть указаны явно или они могут быть частично или полностью выведены из контекста, в котором используется функция. При условии, что они могут быть выведены, списки аргументов типа могут быть полностью опущены, если функция:

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

// sum возвращает сумму (конкатенацию, для строк) своих аргументов.
func sum[T ~int | ~float64 | ~string](x... T) T { … }
x := sum                       // недопустимо: тип x неизвестен
intSum := sum[int]             // intSum имеет тип func(x... int) int
a := intSum(2, 3)              // a имеет значение 5 типа int
b := sum[float64](2.0, 3)      // b имеет значение 5.0 типа float64
c := sum(b, -1)                // c имеет значение 4.0 типа float64
type sumFunc func(x... string) string
var f sumFunc = sum            // то же самое, что var f sumFunc = sum[string]
f = sum                        // то же самое, что f = sum[string]

Частичный список аргументов типа не может быть пустым; должен присутствовать хотя бы первый аргумент. Список является префиксом полного списка аргументов типа, остальные аргументы добавляются автоматически. Грубо говоря, аргументы типа могут быть опущены "справа налево".

func apply[S ~[]E, E any](s S, f func(E) E) S { … }
f0 := apply[]                  // недопустимо: список аргументов типа не может быть пустым
f1 := apply[[]int]             // аргумент типа для S явно указан, аргумент типа для E выводится
f2 := apply[[]string, string]  // оба аргумента типа явно указаны
var bytes []byte
r := apply(bytes, func(byte) byte { … })  // оба аргумента типа выведены из аргументов функции

Для обобщенного типа все аргументы типа всегда должны быть явно указаны.

Вывод типов

При использовании обобщенной функции можно опустить некоторые или все аргументы типа, если они могут быть выведены из контекста, в котором используется функция, включая ограничения параметров типа функции. Вывод типа удается, если он может вывести недостающие аргументы типа и инстанциизация проходит успешно с выведенными аргументами типа. В противном случае вывод типа не удается, и программа становится недействительной.

Вывод типа использует отношения между парами типов для вывода: Например, аргумент функции должен быть присваиваемым соответствующему параметру функции; это устанавливает связь между типом аргумента и типом параметра. Если один из этих двух типов содержит параметры типа, вывод типа ищет типовые аргументы для замены параметров типа такими, чтобы удовлетворить условию присваиваемости . Аналогично, вывод типов использует тот факт, что аргумент типа должен удовлетворять ограничению соответствующего параметра типа.

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

Например, при заданном

// dedup возвращает копию аргумента slice с удаленными дубликатами.
func dedup[S ~[]E, E comparable](S) S { … }
type Slice []int
var s Slice
s = dedup(s)   // то же самое, что s = dedup[Slice, int](s)

переменная s типа Slice должна быть приводимой к типу параметра функции S, чтобы программа была действительной. Чтобы уменьшить сложность, при выводе типов игнорируется направленность присваиваний, поэтому отношение типов между Slice и S может быть выражено с помощью (симметричного) уравнения типов Slice ≡A S (или S ≡A Slice в данном случае), где A в A указывает, что типы LHS и RHS должны совпадать в соответствии с правилами присваиваемости (подробности см. в разделе унификация типов ). Аналогично, параметр типа S должен удовлетворять своему ограничению ~[]E. Это можно выразить как S ≡C ~[]E, где X ≡C Y означает "X удовлетворяет ограничению Y". Эти наблюдения приводят к набору из двух уравнений

   Slice ≡A S      (1)
    S     ≡C ~[]E   (2)

которые теперь можно решить для типовых параметров S и E. Из (1) компилятор может сделать вывод, что типовым аргументом для S является Slice. Аналогично, поскольку базовым типом Slice является []int и []int должен соответствовать []E ограничения, компилятор может сделать вывод, что E должно быть int. Таким образом, для этих двух уравнений тип выводится следующим образом

   S ➞ Slice
    E ➞ int

При наличии набора уравнений типов, типовые параметры, которые необходимо решить, являются типовыми параметрами функций, которые необходимо инстанциировать и для которых не предоставлено явных типовых аргументов. Эти типовые параметры называются связанными типовыми параметрами. Например, в примере dedup выше типовые параметры S и E связаны с dedup. Аргументом вызова общей функции может быть сама общая функция. Типовые параметры этой функции включаются в набор связанных типовых параметров. Типы аргументов функции могут содержать параметры типа из других функций (таких как обобщенная функция, заключающая вызов функции). Эти параметры типа также могут появляться в уравнениях типов, но они не связаны в этом контексте. Уравнения типов всегда решаются только для связанных параметров типа.

Вывод типов поддерживает вызовы обобщенных функций и присваивания обобщенных функций переменным (явно типизированным как функции). Это включает передачу обобщенных функций в качестве аргументов другим (возможно, также обобщенным) функциям и возврат обобщенных функций в качестве результатов. Вывод типов работает с набором уравнений, специфичных для каждого из этих случаев. Уравнения выглядят следующим образом (списки аргументов типов опущены для ясности):

Кроме того, каждый параметр типа Pk и соответствующее ограничение типа Ck дают уравнение типа PkC Ck.

Тип-вывод отдает приоритет информации о типе, полученной из типизированных операндов , прежде чем рассматривать нетипизированные константы. Поэтому вывод проходит в два этапа:

  1. Уравнения типов решаются для связанных параметров типа с помощью унификации типов. Если унификация не удается, вывод типов завершается неудачей.

  2. Для каждого связанного параметра типа Pk, для которого еще не был выведен аргумент типа и для которого была собрана одна или несколько пар (cj, Pk) с тем же параметром типа , определяется тип константы констант cj во всех этих парах таким же образом, как и для постоянных выражений. Тип аргумента для Pk является типом по умолчанию для определенного вида константы. Если вид константы не может быть определен из-за конфликта видов констант, тип не может быть выведен.

Если после этих двух этапов не все аргументы типа были найдены, вывод типа завершается с ошибкой.

Если оба этапа завершились успешно, вывод типа определил аргумент типа для каждого связанного параметра типа:

   Pk ➞ Ak

Тип-аргумент Ak может быть составным типом, содержащим другие связанные типовые параметры Pk в качестве типов элементов (или даже быть просто еще одним связанным типовым параметром). В процессе повторного упрощения связанные типовые параметры в каждом типовом аргументе заменяются соответствующими типовыми аргументами для этих типовых параметров, пока каждый типовой аргумент не будет свободен от связанных типовых параметров.

Если типовые аргументы содержат циклические ссылки на самих себя через связанные типовые параметры, упрощение и, следовательно, вывод типов не удается. В противном случае вывод типов завершается успешно.

Унификация типов

Вывод типов решает уравнения типов посредством унификации типов. Унификация типов рекурсивно сравнивает типы LHS и RHS уравнения , где один или оба типа могут быть или содержать связанные параметры типов, и ищет аргументы типов для этих параметров типов, чтобы LHS и RHS совпадали (стали идентичными или совместимыми по присвоению, в зависимости от контекста). Для этого вывод типов поддерживает карту связанных типовых параметров к выведенным типовым аргументам; эта карта используется и обновляется во время унификации типов. Изначально связанные типовые параметры известны, но карта пуста. Во время унификации типов, если выводится новый типовой аргумент A, соответствующее отображение P ➞ A от типового параметра к аргументу . И наоборот, при сравнении типов известный аргумент типа (аргумент типа, для которого уже существует запись в карте) занимает место соответствующего ему параметра типа. По мере продвижения вывода типов карта заполняется все больше и больше, пока не будут рассмотрены все уравнения или пока унификация не завершится неудачей. Вывод типов завершается успешно, если ни один шаг унификации не завершился неудачей и карта имеет запись для каждого параметра типа.

Например, при заданном уравнении типов с связанным параметром типа P

   [10]struct{ elem P, list []P } ≡A [10]struct{ elem string; list []string }

вывод типов начинается с пустой карты. Унификация сначала сравнивает верхний уровень структуры типов LHS и RHS . Оба являются массивами одинаковой длины; они унифицируются, если унифицируются типы элементов. Оба типа элементов являются структурами; они унифицируются, если имеют одинаковое количество полей с одинаковыми именами и если типы полей унифицируются. Тип аргумента для P еще не известен (нет записи в карте), поэтому унификация P с string добавляет в карту сопоставление P ➞ string. Унификация типов поля list требует унифицировать []P и []string и, таким образом, P и string. Поскольку аргумент типа для P на данный момент известен (есть запись в карте для P), его аргумент типа string заменяет P. А поскольку string идентичен string, этот шаг унификации также завершается успешно. Унификация левой и правой частей уравнения завершена. Тип выводится успешно, поскольку есть только одно уравнение типов, ни один шаг унификации не завершился неудачно, а карта полностью заполнена.

Унификация использует комбинацию точной и неточной унификации в зависимости от того, должны ли два типа быть идентичными, совместимыми по присвоению или только структурно равными. Соответствующие правила унификации типов подробно изложены в Приложении.

Для уравнения вида X ≡A Y, где X и Y являются типами, участвующими в присваивании (включая передачу параметров и операторы возврата), структуры типов верхнего уровня могут унифицироваться нестрого, но типы элементов должны унифицироваться точно, в соответствии с правилами присваивания.

Для уравнения вида P ≡C C, где P является типовым параметром, а C — соответствующим ему ограничением, правила унификации немного сложнее:

При решении уравнений типов из ограничений типов, решение одного уравнения может привести к выводу дополнительных аргументов типов, что, в свою очередь, может позволить решить другие уравнения, которые зависят от этих аргументов типов. Вывод типов повторяет унификацию типов до тех пор, пока выводятся новые аргументы типов .

Операторы

Операторы объединяют операнды в выражения.

Выражение = UnaryExpr | Expression binary_op Expression .
UnaryExpr  = PrimaryExpr | unary_op UnaryExpr .
binary_op  = "||" | "&& amp;" | rel_op | add_op | mul_op .
rel_op     = "==" | "!=" | "<" | "<=" | ">" | ">=" .
add_op     = "+" | "-" | "|" | "^" .
mul_op     = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .
unary_op   = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .

Сравнения обсуждаются в другом месте. Для других бинарных операторов типы операндов должны быть идентичными , если операция не включает сдвиги или нетипизированные константы. Для операций, включающих только константы, см. раздел константные выражения.

За исключением операций сдвига, если один операнд является нетипизированной константой , а другой операнд — нет, константа неявно преобразуется в тип другого операнда.

Правый операнд в выражении сдвига должен иметь целочисленный тип [Go 1.13] или быть нетипизированной константой, представляемой значением типа uint. Если левый операнд неконстантного сдвигового выражения является константой без типа, он сначала неявно преобразуется в тип, который он принял бы, если бы сдвиговое выражение было заменено только левым операндом.

var a [1024]byte
var s uint = 33
// Результаты следующих примеров приведены для 64-разрядных целых чисел.
var i = 1<<s                   // 1 имеет тип int
var j int32 = 1<<s             // 1 имеет тип int32; j == 0
var k = uint64(1<<s)           // 1 имеет тип uint64; k == 1<<33
var m int = 1.0<<s             // 1.0 имеет тип int; m == 1<<33
var n = 1.0<<s == j            // 1.0 имеет тип int32; n == true
var o = 1<<s == 2<<s           // 1 и 2 имеют тип int; o == false
var p = 1<<s == 1<<33          // 1 имеет тип int; p == true
var u = 1.0<<s                 // недопустимо: 1.0 имеет тип float64, сдвиг невозможен
var u1 = 1.0<& lt;s != 0           // недопустимо: 1.0 имеет тип float64, сдвиг невозможен
var u2 = 1<<s != 1.0           // недопустимо: 1 имеет тип float64, сдвиг невозможен
var v1 float32 = 1<<s          // недопустимо: 1 имеет тип float32, сдвиг невозможен
var v2 = string(1<<s)          // недопустимо: 1 преобразуется в строку, сдвиг невозможен
var w int64 = 1.0<<33          // 1.0& lt;<33 является константным выражением сдвига; w == 1<<33
var x = a[1.0<<s]              // паника: 1.0 имеет тип int, но 1<<33 выходит за пределы массива
var b = make([]byte, 1.0<<s)   // 1.0 имеет тип int; len(b) == 1<<33
// Результаты следующих примеров приведены для 32-разрядных целых чисел,
// что означает, что сдвиги приведут к переполнению.
var mm int = 1.0<<s            // 1.0 имеет тип int; mm == 0
var oo = 1<<s == 2<& lt;s          // 1 и 2 имеют тип int; oo == true
var pp = 1<<s == 1<<33         // недопустимо: 1 имеет тип int, но 1<<33 выходит за пределы int
var xx = a[1.0<<s]             // 1.0 имеет тип int; xx == a[0]
var bb = make([]byte, 1.0<<s)  // 1.0 имеет тип int; len(bb) == 0

Приоритет операторов

Унарные операторы имеют наивысший приоритет. Поскольку операторы ++ и -- образуют операторы, а не выражения, они не входят в иерархию операторов. В результате оператор *p++ эквивалентен (*p)++.

Существует пять уровней приоритета для бинарных операторов. Операторы умножения имеют наибольшую связь, за ними следуют операторы сложения , операторы сравнения, && (логическое И), и, наконец, || (логическое ИЛИ):

Приоритет    Оператор
    5             *  /  %  <<  >>  &  &^
    4             +  -  |  ^
    3             ==  !=  <  <=  >  >=
    2             &&
    1             ||

Двоичные операторы с одинаковым приоритетом ассоциируются слева направо. Например, x / y * z равно (x / y) * z.

+x                         // x
42 + a - b                 // (42 + a) - b
23 + 3*x[i]                // 23 + (3 * x[i])
x <= f()                   // x <= f()
^a >> b                    // (^a) >> b
f() || g()                 // f() || g()
x == y+1 && <-chanInt > 0  // (x == (y+1)) && ((<-chanInt) > 0)

Арифметические операторы

Арифметические операторы применяются к числовым значениям и дают результат того же типа, что и первый операнд. Четыре стандартных арифметических оператора (+, -, *, /) применяются к целым, плавающей запятой и комплексных типов; + также применяется к строкам. Побитовые логические и сдвиговые операторы применяются только к целым числам.

+    сумма                    целые числа, числа с плавающей запятой, комплексные значения, строки
-    разность             целые числа, числа с плавающей запятой, комплексные значения
*    произведение                целые числа, числа с плавающей запятой, комплексные значения
/    частное               целые числа, числа с плавающей запятой, комплексные значения
%    остаток              целые числа
&    битовое И            целые числа
|    битовое ИЛИ             целые числа
^    битовое ИЛИ            целые числа
&^   сброс бита (И НЕ)    целые числа
<<   сдвиг влево             целое число << целое число >= 0
>>   сдвиг вправо            целое число >> целое число >= 0

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

func dotProduct[F ~float32|~float64](v1, v2 []F) F {
    var s F
    for i, x := range v1 {
       y := v2[i]
       s += x * y
    }
    return s
}

произведение x * y и сложение s += x * y вычисляются с точностью float32 или float64, соответственно, в зависимости от аргумента типа для F.

Целочисленные операторы

Для двух целочисленных значений x и y целочисленное частное q = x / y и остаток r = x % y удовлетворяют следующим отношения:

x = q*y + r  и  |r| < |y|

с x / y усеченным в сторону нуля ("усеченное деление").

 x     y     x / y     x % y
 5     3       1         2
-5     3      -1        -2
 5    -3      -1         2
-5    -3       1        -2

Единственным исключением из этого правила является случай, когда делимое x является наиболее отрицательным значением для типа int x, тогда частное q = x / -1 равно xr = 0) из-за переполнения целого числа с дополнением до двух integer overflow:

                         x, q
int8                     -128
int16                  -32768
int32             -2147483648
int64    -9223372036854775808

Если делитель является константой, он не должен быть равен нулю. Если делитель равен нулю во время выполнения, возникает паника во время выполнения. Если делимое число неотрицательно, а делитель является постоянной степенью числа 2, деление может быть заменено сдвигом вправо, а вычисление остатка может быть заменено битовой операцией AND:

 x     x / 4     x % 4     x >> 2     x & 3
11      2         3         2          3
-11     -2        -3        -3          1

Операторы сдвига сдвигают левый операнд на количество сдвигов, указанное правым операндом, который должен быть неотрицательным. Если количество сдвигов отрицательно во время выполнения, возникает паника во время выполнения. Операторы сдвига реализуют арифметический сдвиг, если левый операнд является целым числом со знаком, и логический сдвиг, если он является целым числом без знака. Верхний предел числа сдвигов не ограничен. Сдвиги работают так, как если бы левый операнд сдвигался n раз на 1 для числа сдвига n. В результате x << 1 равно x*2 , а x >> 1 равно x/2, но усечено в сторону отрицательной бесконечности.

Для целочисленных операндов унарные операторы +, - и ^ определяются следующим образом :

+x                          равно 0 + x
-x    отрицание              равно 0 - x
^x    битовое дополнение    равно m ^ x  где m = "все биты установлены в 1" для x без знака
и  m = -1 для x со знаком

Переполнение целого числа

Для значений целых чисел без знака операции +, -, * и << вычисляются по модулю 2n, где n — ширина бита типа целого числа без знака. Проще говоря, эти операции с целыми числами без знака отбрасывают старшие биты при переполнении, и программы могут полагаться на "обертывание".

Для целых чисел со знаком операции +, -, *, / и << могут законно переполняться, и результирующее значение существует и детерминированно определяется представлением целого числа со знаком, операцией и ее операндами. Переполнение не вызывает панику во время выполнения. Компилятор не может оптимизировать код, исходя из предположения, что переполнение не происходит. Например, он не может предполагать, что x < x + 1 всегда истинно.

Операторы с плавающей запятой

Для чисел с плавающей запятой и комплексных чисел +x равно x, а -x является отрицанием x. Результат деления числа с плавающей запятой или комплексного числа на ноль не определён за пределами стандарта IEEE 754; возникновение паники во время выполнения зависит от реализации.

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

Например, некоторые архитектуры предоставляют инструкцию "объединенное умножение и сложение" (FMA) , которая вычисляет x*y + z без округления промежуточного результата x*y. Эти примеры показывают, когда реализация Go может использовать эту инструкцию:

// FMA разрешена для вычисления r, поскольку x*y явно не округляется:
r  = x*y + z
r  = z;   r += x*y
t  = x*y; r = t + z
*p = x*y; r = *p + z
r  = x*y + float64(z)
// FMA не разрешена для вычисления r, поскольку она опустила бы округление x*y:
r  = float64(x*y) + z
r  = z; r += float64(x*y)
t  = float64(x*y); r = t + z

Конкатенация строк

Строки можно соединять с помощью оператора + или оператора присваивания +=:

s := "hi" + string(c)
s += " and good bye"

Сложение строк создает новую строку путем соединения операндов.

Операторы сравнения

Операторы сравнения сравнивают два операнда и возвращают нетипизированное булево значение.

==    равно
!=    не равно
<     меньше
<=    меньше или равно
>     больше
>=    больше или равно

В любом сравнении первый операнд должен быть присваиваемым типу второго операнда или наоборот.

Операторы равенства == и != применяются к операндам сопоставимых типов. Операторы упорядочения <, <=, > и >= применяются к операндам упорядоченных типов. Эти термины и результаты сравнений определяются следующим образом:

Сравнение двух значений интерфейса с идентичными динамическими типами вызывает панику во время выполнения, если этот тип не является сопоставимым. Это поведение применимо не только к прямому сравнению значений интерфейса, но и к сравнению массивов значений интерфейса или структур с полями, имеющими значения интерфейса.

Типы slice, map и function не сопоставимы. Однако, в качестве особого случая, значение slice, map или function может быть сопоставлено с заранее объявленным идентификатором nil. Сравнение значений указателя, канала и интерфейса с nil также допускается и следует из общих правил, приведенных выше.

const c = 3 < 4            // c — нетипизированная булева константа true
type MyBool bool
var x, y int
var (
    // Результат сравнения — нетипизированная булева величина.
    // Применяются обычные правила присваивания.
b3        = x == y // b3 имеет тип bool
    b4 bool   = x == y // b4 имеет тип bool
    b5 MyBool = x == y // b5 имеет тип MyBool
)

Тип является строго сопоставимым, если он сопоставим и не является типом интерфейса или не состоит из типов интерфейсов. В частности:

Логические операторы

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

&&    условное AND    p && q  —  "если p, то q, иначе false"
||    условное OR     p || q  —  "если p, то true, иначе q"
!     NOT                !p      —  "не p"

Адресные операторы

Для операнда x типа T операция адресации &x генерирует указатель типа * T на x. Операнд должен быть адресуемым, то есть либо переменной, либо косвенной ссылкой на указатель, либо операцией индексации фрагмента ; либо селектором поля адресуемого операнда структуры; или операцией индексации массива адресуемого массива. В качестве исключения из требования адресуемости, x также может быть (возможно, заключенным в скобки) составным литералом. Если вычисление x приводит к панике во время выполнения, то вычисление &x также приводит к ней.

Для операнда x типа указателя *T косвенная ссылка на указатель *x обозначает переменную типа T , на которую указывает x. Если x равно nil, попытка вычислить *x приведет к панике во время выполнения.

&x
&a[f(2)]
&Point{2, 3}
*p
*pf(x)
var x *int = nil
*x   // вызывает панику во время выполнения
&*x  // вызывает панику во время выполнения

Оператор receive

Для операнда ch типа канала, значение операции receive <-ch является значением, полученным из канала ch. Направление канала должно разрешать операции receive, а тип операции receive является типом элемента канала. Выражение блокируется до тех пор, пока не будет доступно значение. Прием из канала nil блокируется навсегда. Операция приема на закрытом канале всегда может выполняться немедленно, возвращая нулевое значение типа элемента после получения всех ранее отправленных значений.

v1 := <-ch
v2 = <-ch
f(<-ch)
<-strobe  // ждать до тактового импульса и отбросить полученное значение

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

Выражение приема, используемое в операторе присваивания или инициализации специальной формы

x, ok = <-ch
x, ok := <-ch
var x, ok = <-ch
var x, ok T = <-ch

дает дополнительный нетипизированный булевый результат, сообщающий, удалась ли коммуникация. Значение ok равно true , если полученное значение было доставлено успешной операцией отправки в канал, или false , если это нулевое значение, сгенерированное из-за того, что канал закрыт и пуст.

Преобразования

Преобразование изменяет тип выражения в тип, указанный преобразованием. Преобразование может появляться буквально в исходном коде или может быть подразумеваемым в контексте, в котором появляется выражение.

Явное преобразование — это выражение вида T(x) , где T — тип, а x — выражение , которое может быть преобразовано в тип T.

Преобразование = Тип "( " Выражение [ "," ] ")" .

Если тип начинается с оператора * или <-, или если тип начинается с ключевого слова func и не имеет списка результатов, он должен быть заключен в скобки, когда необходимо, чтобы избежать неоднозначности:

*Point(p)        // то же, что *(Point(p))
(*Point)(p)      // p преобразуется в *Point
<-chan int(c)    // то же, что <-(chan int(c))
(<-chan int)(c)  // c преобразуется в <-chan int
func()(x)        // сигнатура функции func() x
(func())(x)      // x преобразуется в func()
(func() int)(x)  // x преобразуется в func() int
func() int(x)    // x преобразуется в func() int (неоднозначно)

Константное значение x может быть преобразовано в тип T, если x может быть представлено значением T. В качестве особого случая целочисленная константа x может быть явно преобразована в тип строки с использованием того же правила , что и для неконстанты x.

Преобразование константы в тип, который не является типовым параметром , дает типизированную константу.

uint(iota)               // значение iota типа uint
float32(2.718281828)     // 2.718281828 типа float32
complex128(1)            // 1.0 + 0.0i типа complex128
float32 (0.49999999)      // 0.5 типа float32
float64(-1e-1000)        // 0.0 типа float64
string(„x“)              // "x" типа string
string (0x266c)           // "♬" типа string
myString("foo" + „bar“)  // "foobar" типа myString
string([]byte{„a“})      // не константа: []byte{„a“} не является константой
(*int)(nil)              // не является константой: nil не является константой, *int не является типом boolean, numeric или string
int(1.2)                 // недопустимо: 1.2 не может быть представлено как int
string(65.0)             // недопустимо: 65.0 не является целочисленной константой

Преобразование константы в параметр типа дает неконстантное значение этого типа, где значение представлено как значение аргумента типа, с которым параметр типа инстанциируется. Например, для функции:

func f[P ~float32|~float64]() {
	… P(1.1) …
}

преобразование P(1.1) дает неконстантное значение типа P , а значение 1.1 представляется как float32 или float64 в зависимости от аргумента типа для f. Соответственно, если f инстанциируется с типом float32 , числовое значение выражения P(1.1) + 1.2 будет вычислено с той же точностью, что и соответствующее неконстантное float32 сложение.

Неконстантное значение x может быть преобразовано в тип T в любом из следующих случаев:

Кроме того, если T или тип x V являются типами параметров, x также может быть преобразован в тип T, если выполняется одно из следующих условий:

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

type Person struct {
	Name    string
	Address *struct {
		Street string
		City   string
	}
}
var data *struct {
    Name    string `json:"name"`
    Address *struct {
       Street string `json:"street"`
       City   string `json:„city“`
} `json:"address"`
}
var person = (*Person)(data)  // игнорируя теги, базовые типы идентичны

К (неконстантным) преобразованиям между числовыми типами или в и из строкового типа применяются особые правила. Эти преобразования могут изменить представление x и повлечь за собой затраты времени выполнения. Все остальные преобразования изменяют только тип, но не представление x.

Не существует лингвистического механизма для преобразования между указателями и целыми числами. Пакет unsafe реализует эту функциональность в ограниченных условиях.

Преобразования между числовыми типами

Для преобразования неконстантных числовых значений применяются следующие правила:

  1. При преобразовании между целочисленными типами, если значение является целым числом со знаком , оно расширяется по знаку до неявной бесконечной точности; в противном случае оно расширяется по нулю. Затем оно усекается, чтобы вписаться в размер типа результата. Например, если v := uint16(0x10F0), то uint32(int8(v)) == 0xFFFFFFF0. Преобразование всегда дает действительное значение; нет никаких признаков переполнения.
  2. При преобразовании числа с плавающей запятой в целое число дробная часть отбрасывается (усечение в сторону нуля).
  3. При преобразовании целого числа или числа с плавающей запятой в тип с плавающей запятой, или комплексного числа в другой комплексный тип, значение результата округляется с точностью, указанной типом назначения. Например, значение переменной x типа float32 может храниться с использованием дополнительной точности, превышающей точность 32-разрядного числа IEEE 754, но float32(x) представляет результат округления значения x до 32-разрядной точности. Аналогично, x + 0.1 может использовать более 32 разрядов точности, но float32 (x + 0.1) — нет.

Во всех неконстантных преобразованиях, включающих значения с плавающей запятой или комплексные значения, если тип результата не может представить значение, преобразование удается, но значение результата зависит от реализации.

Преобразования в и из строкового типа

  1. Преобразование фрагмента байтов в строковый тип дает строку, последовательные байты которой являются элементами фрагмента.
    string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'})   // "hellø"
    string([]byte{})                                     // ""
    string([]byte(nil))                                  // ""
    type bytes []byte
    string(bytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'})    // "hellø"
    type myByte byte
    string([]myByte{'w', 'o', 'r', 'l', 'd', '!'})       // "world!"
    myString([]myByte{'\xf0', '\x9f', '\x8c', '\x8d'})   // "🌍"
    
  2. Преобразование фрагмента рун в строку дает строку, которая является конкатенацией отдельных значений рун , преобразованных в строки.
    string([]rune{0x767d, 0x9d6c, 0x7fd4})   // "\u767d\u9d6c\u7fd4" == "白鵬翔"
    string([]rune{})                         // ""
    string([]rune(nil))                      // ""
    type runes []rune
    string(runes{0x767d, 0x9d6c, 0x7fd4})    // "\u767d\u9d6c\u7fd4" == "白鵬翔"
    type myRune rune
    string([]myRune{0x266b, 0x266c})         // "\u266b\u266c" == "♫♬"
    myString([]myRune{0x1f30e})              // "\U0001f30e" == "🌎"
    
  3. Преобразование значения типа string в тип slice of bytes дает не нулевой slice, последовательные элементы которого являются байтами строки. Емкость результирующего slice зависит от реализации и может быть больше длины slice.
    []byte("hellø")             // []byte{„h“, „e“, „l“, „l“, „\xc3“, „\xb8“}
    []byte("")                  // []byte{}
    bytes("hellø")              // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
    []myByte("world!")          // []myByte{'w', 'o', 'r', 'l', 'd', '!'}
    []myByte(myString("🌏"))    // []myByte{'\xf0', '\x9f', '\x8c', '\x8f'}
    
  4. Преобразование значения типа string в срез типа runes дает срез, содержащий отдельные кодовые точки Unicode строки. Емкость полученного среза зависит от реализации и может быть больше длины среза.
    []rune(myString("白鵬翔"))   // []rune{0x767d, 0x9d6c, 0x7fd4}
    []rune("")                  // []rune{}
    runes("白鵬翔")              // []rune{0x767d, 0x9d6c, 0x7fd4}
    []myRune("♫♬")              // []myRune{0x266b, 0x266c}
    []myRune(myString("🌐"))    // []myRune{0x1f310}
    
  5. Наконец, по историческим причинам целое значение может быть преобразовано в строковый тип. Эта форма преобразования дает строку, содержащую (возможно многобайтовое) UTF-8 представление кодовой точки Unicode с заданным целочисленным значением. Значения, выходящие за пределы диапазона допустимых кодовых точек Unicode, преобразуются в "\uFFFD".
    string(„a“)          // "a"
    string(65)           // "A"
    string('\xf8')       // "\u00f8" == "ø" == "\xc3\xb8"
    string(-1)           // "\ufffd" == "\xef\xbf\xbd"
    type myString string
    myString('\u65e5')   // "\u65e5" == "日" == "\xe6\x97\xa5"
    
    Примечание: эта форма преобразования может быть в конечном итоге удалена из языка. Инструмент go vet помечает определенные преобразования целых чисел в строки как потенциальные ошибки. Вместо этого следует использовать библиотечные функции, такие как utf8.AppendRune или utf8.EncodeRune .

Преобразования из слайса в массив или указатель на массив

Преобразование среза в массив дает массив, содержащий элементы базового массива среза. Аналогично, преобразование фрагмента в указатель массива дает указатель на базовый массив фрагмента. В обоих случаях, если длина фрагмента меньше длины массива, возникает паника во время выполнения.

s := make([]byte, 2, 4)
a0 := [0]byte(s)
a1 := [1]byte(s[1:])     // a1[0] == s[1]
a2 := [2]byte(s)         // a2[0] == s[0]
a4 := [4]byte(s)         // panics: len([4]byte) > len(s)
s0 := (*[0]byte)(s)      // s0 != nil
s1 := (*[1]byte)(s[1:])  // &s1[0] == &s[1]
s2 := (*[2]byte)(s)      // &s2[0] == &s[0]
s4 := (*[4]byte)(s)      // panics: len([4]byte) > len(s)
var t []string
t0 := [0]string(t)       // ok для nil slice t
t1 := (*[0]string)(t)    // t1 == nil
t2 := (*[1]string)(t)    // паника: len([1]string) > len(t)
u := make([]byte, 0)
u0 := (*[0]byte)(u)      // u0 != nil

Константные выражения

Константные выражения могут содержать только константные операнды и вычисляются во время компиляции.

Нетипизированные булевые, числовые и строковые константы могут использоваться в качестве операндов везде, где допустимо использовать операнд типа булевого, числового или строкового, соответственно.

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

Любая другая операция над константами без типа дает константу без типа того же типа, что и источник; то есть булева, целочисленная, с плавающей запятой, комплексная или строковая константа. Если нетипизированные операнды двоичной операции (кроме сдвига) имеют разные типы, результатом является тип операнда, который идет позже в этом списке: целое число, руна, число с плавающей запятой, комплексное число. Например, нетипизированная целочисленная константа, деленная на нетипизированную комплексную константу, дает нетипизированную комплексную константу.

const a = 2 + 3.0          // a == 5.0   (нетипизированная константа с плавающей запятой)
const b = 15 / 4           // b == 3     (нетипизированная целочисленная константа)
const c = 15 / 4.0         // c == 3.75  (нетипизированная константа с плавающей запятой)
const Θ float64 = 3/2      // Θ == 1.0   (тип float64, 3/2 — целочисленное деление)
const Π float64 = 3/2.     // Π == 1.5   (тип float64, 3/2. — деление с плавающей запятой)
const d = 1 << 3.0         // d == 8     (нетипизированная целочисленная константа)
const e = 1.0 << 3         // e == 8     (нетипизированная целочисленная константа)
const f = int32(1) << 33   // недопустимо    (константа 8589934592 выходит за пределы int32)
const g = float64(2) >> 1  // недопустимо    (float64(2) — константа с плавающей запятой с типом)
const h = "foo" > "bar"    // h == true  (нетипизированная булева константа)
const j = true             // j == true  (нетипизированная булева константа)
const k = „w“ + 1          // k == „x“   (нетипизированная константа rune)
const l = "hi"             // l == „hi“  (нетипизированная константа string)
const m = string(k)        // m == "x"   (тип string)
const Σ = 1 - 0.707i       //            (нетипизированная комплексная константа)
const Δ = Σ + 2.0e-4       //            (нетипизированная комплексная константа)
const Φ = iota*1i - 1/1i   //            (нетипизированная комплексная константа)

Применение встроенной функции complex к нетипизированным целым, рунным или плавающим константам дает нетипизированную комплексную константу.

const ic = complex(0, c)   // ic == 3.75i  (нетипизированная комплексная константа)
const iΘ = complex(0, Θ)   // iΘ == 1i     (тип complex128)

Константные выражения всегда вычисляются точно; промежуточные значения и сами константы могут требовать точности, значительно превышающей поддерживаемую любым заранее объявленным типом в языке. Следующие объявления являются допустимыми:

const Huge = 1 << 100         // Huge == 1267650600228229401496703205376  (untyped integer constant)
const Four int8 = Huge >> 98  // Four == 4                                (type int8)

Делитель в операции деления константы или операции с остатком не должен быть равен нулю:

3.14 / 0.0   // недопустимо: деление на ноль

Значения типизированных констант всегда должны быть точно представлены значениями типа константы. Следующие выражения констант являются недопустимыми:

uint(-1)     // -1 не может быть представлено как uint
int(3.14)    // 3.14 не может быть представлено как int
int64(Huge)  // 1267650600228229401496703205376 не может быть представлено как int64
Four * 300   // операнд 300 не может быть представлен как int8 (тип Four)
Four * 100   // произведение 400 не может быть представлено в виде int8 (тип Four)

Маска, используемая унарным битовым оператором дополнения ^, соответствует правилу для неконстант: маска состоит из одних единиц для констант без знака и из -1 для констант со знаком и без типа.

^1         // нетипизированная целочисленная константа, равная -2
uint8(^1)  // недопустимо: то же самое, что uint8(-2), -2 не может быть представлено в виде uint8
^uint8(1)  // типизированная константа uint8, то же самое, что 0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // то же, что int8(-2)
^int8(1)   // то же, что -1 ^ int8(1) = -2

Ограничение реализации: компилятор может использовать округление при вычислении нетипизированных констант с плавающей запятой или комплексных констант; см. ограничение реализации в разделе о константах. Это округление может привести к тому, что выражение с плавающей запятой будет недействительным в целочисленном контексте, даже если оно будет целочисленным при вычислении с использованием бесконечной точности, и наоборот.

Порядок вычисления

На уровне пакета зависимости инициализации определяют порядок вычисления отдельных инициализирующих выражений в объявлениях переменных. В остальных случаях, при вычислении операндов выражения, присваивания или оператора return, все вызовы функций, вызовы методов, операции приёма и бинарные логические операции вычисляются в лексическом порядке слева направо.

Например, в присваивании (локальном для функции)

y[f()], ok = g(z || h(), i()+x[j()], <-c), k()

вызовы функций и коммуникация происходят в порядке f(), h() (если z вычисляется в false), i(), j(), <-c, g() и k(). Однако порядок этих событий по сравнению с вычислением и индексацией x и вычислением y и z не определён, за исключением требований лексического порядка. Например, g не может быть вызвана до вычисления её аргументов.

a := 1
f := func() int { a++; return a }
x := []int{a, f()}            // x может быть [1, 2] или [2, 2]: порядок вычисления между a и f() не определён
m := map[int]int{a: 1, a: 2}  // m может быть {2: 1} или {2: 2}: порядок вычисления между двумя присваиваниями в map не определён
n := map[int]int{a: f()}      // n может быть {2: 3} или {3: 3}: порядок вычисления между ключом и значением не определён

На уровне пакета зависимости инициализации переопределяют правило слева направо для отдельных инициализирующих выражений, но не для операндов внутри каждого выражения:

var a, b, c = f() + v(), g(), sqr(u()) + v()
func f() int        { return c }
func g() int        { return a }
func sqr(x int) int { return x*x }
// функции u и v независимы от всех других переменных и функций

Вызовы функций происходят в порядке u(), sqr(), v(), f(), v() и g().

Операции с плавающей точкой внутри одного выражения вычисляются в соответствии с ассоциативностью операторов. Явные скобки влияют на вычисление, переопределяя стандартную ассоциативность. В выражении x + (y + z) сложение y + z выполняется перед добавлением x.

Операторы

Операторы управляют выполнением программы.

Statement  = Declaration |
            LabeledStmt | SimpleStmt |
             GoStmt | ReturnStmt |
             BreakStmt | ContinueStmt |
             GotoStmt |
             FallthroughStmt | Block |
             IfStmt | SwitchStmt |
             SelectStmt | ForStmt |
             DeferStmt .
SimpleStmt = EmptyStmt |
             ExpressionStmt | SendStmt |
             IncDecStmt | Assignment |
             ShortVarDecl .

Завершающие операторы

Завершающий оператор прерывает обычный поток управления в блоке. Следующие операторы являются завершающими:

  1. Оператор "return" или "goto".
    • Вызов встроенной функции panic.
      • Блок, в котором список операторов заканчивается завершающим оператором.
        • Оператор "if", в котором:
          • присутствует ветвь "else", и
          • обе ветви являются завершающими операторами.
        • Оператор "for", в котором:
          • отсутствуют операторы "break", ссылающиеся на оператор "for", и
          • условие цикла отсутствует, и
          • оператор "for" не использует конструкцию range.
        • Оператор "switch", в котором:
          • отсутствуют операторы "break", ссылающиеся на оператор "switch",
          • присутствует ветвь default, и
          • списки операторов в каждой ветви case, включая default, заканчиваются завершающим оператором или, возможно, помеченным оператором "fallthrough".
        • Оператор "select", в котором:
          • отсутствуют операторы "break", ссылающиеся на оператор "select", и
          • списки операторов в каждой ветви case, включая default, если он присутствует, заканчиваются завершающим оператором.
        • Помеченный оператор, помечающий завершающий оператор.

        Все остальные операторы не являются завершающими.

        Список операторов заканчивается завершающим оператором, если список не пуст и его последний непустой оператор является завершающим.

        Пустые операторы

        Пустой оператор ничего не делает.

        EmptyStmt = .
        

        Помеченные операторы

        Помеченный оператор может быть целью оператора goto, break или continue.

        LabeledStmt = Label ":"
                   Statement .
        Label       = identifier .
        
        Error: log.Panic("error encountered")
        

        Операторы-выражения

        За исключением определённых встроенных функций, вызовы функций и методов, а также операции приёма могут использоваться в контексте оператора. Такие операторы могут быть заключены в скобки.

        ExpressionStmt = Expression .
        

        Следующие встроенные функции не разрешены в контексте оператора:

        append cap complex imag len make new real
        unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice unsafe.SliceData unsafe.String unsafe.StringData
        
        h(x+y)
        f.Close()
        <-ch
        (<-ch)
        len("foo")  // недопустимо, если len является встроенной функцией
        

        Операторы отправки

        Оператор отправки отправляет значение в канал. Выражение канала должно быть типа канала, направление канала должно разрешать операции отправки, и тип отправляемого значения должен быть присваиваемым типу элемента канала.

        SendStmt = Channel "<-"
                    Expression .
        Channel  = Expression .
        

        И канал, и выражение значения вычисляются до начала коммуникации. Коммуникация блокируется до тех пор, пока отправка не станет возможной. Отправка в небуферизованный канал может быть выполнена, если готов приёмник. Отправка в буферизованный канал может быть выполнена, если в буфере есть место. Отправка в закрытый канал выполняется, вызывая паническую ситуацию во время выполнения. Отправка в канал nil блокируется навсегда.

        ch <- 3  // отправить значение 3 в канал ch
        

        Если тип выражения канала является параметром типа, все типы в его множестве типов должны быть типами каналов, разрешающими операции отправки, все они должны иметь один и тот же тип элемента, и тип отправляемого значения должен быть присваиваемым этому типу элемента.

        Операторы инкремента и декремента

        Операторы "++" и "--" увеличивают или уменьшают свои операнды на нетипизированную константу 1. Как и при присваивании, операнд должен быть адресуемым или выражением индекса map.

        IncDecStmt = Expression ( "++" | "--" ) .
        

        Следующие операторы присваивания семантически эквивалентны:

        Оператор IncDec    Присваивание
        x++                 x += 1
        x--                 x -= 1
        

        Операторы присваивания

        Присваивание заменяет текущее значение, хранящееся в переменной, на новое значение, указанное выражением. Оператор присваивания может присвоить одно значение одной переменной или несколько значений соответствующему числу переменных.

        Assignment = ExpressionList
                    assign_op ExpressionList .
        assign_op  = [ add_op | mul_op ] "=" .
        

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

        x = 1
        *p = f()
        a[i] = 23
        (k) = <-ch  // то же, что: k = <-ch
        

        Операция присваивания x op= y, где op — это бинарный арифметический оператор, эквивалентна x = x op (y), но вычисляет x только один раз. Конструкция op= является единой лексемой. В операциях присваивания как левая, так и правая части списка выражений должны содержать ровно одно однозначное выражение, и выражение левой части не должно быть пустым идентификатором.

        a[i] <<= 2
        i &^= 1<<n
        

        Кортежное присваивание присваивает отдельные элементы многозначной операции списку переменных. Существует две формы. В первой форме операнд правой части — это одно многозначное выражение, такое как вызов функции, операция с каналом или map, или утверждение типа. Количество операндов в левой части должно соответствовать количеству значений. Например, если f — это функция, возвращающая два значения,

        x, y = f()
        

        присваивает первое значение переменной x, а второе — y. Во второй форме количество операндов слева должно быть равно количеству выражений справа, каждое из которых должно быть однозначным, и n-е выражение справа присваивается n-му операнду слева:

        one, two, three = '一', '二', '三'
        

        Пустой идентификатор позволяет игнорировать значения правой части при присваивании:

        _ = x       // вычислить x, но проигнорировать результат
        x, _ = f()  // вычислить f(), но проигнорировать второе возвращаемое значение
        

        Присваивание выполняется в две фазы. Сначала операнды выражений индексации и разыменований указателей (включая неявные разыменования указателей в селекторах) слева и выражения справа вычисляются в обычном порядке. Затем присваивания выполняются в порядке слева направо.

        a, b = b, a  // обменять a и b
        x := []int{1, 2, 3}
        i := 0
        i, x[i] = 1, 2  // установить i = 1, x[0] = 2
        i = 0
        x[i], i = 2, 1  // установить x[0] = 2, i = 1
        x[0], x[0] = 1, 2  // установить x[0] = 1, затем x[0] = 2 (так что x[0] == 2 в конце)
        x[1], x[3] = 4, 5  // установить x[1] = 4, затем паника при установке x[3] = 5.
        type Point struct { x, y int }
        var p *Point
        x[2], p.x = 6, 7  // установить x[2] = 6, затем паника при установке p.x = 7
        i = 2
        x = []int{3, 5, 7}
        for i, x[i] = range x {  // установить i, x[2] = 0, x[0]
        	break
        }
        // после этого цикла i == 0 и x равен []int{3, 5, 3}
        

        При присваивании каждое значение должно быть присваиваемым типу операнда, которому оно присваивается, со следующими особыми случаями:

        1. Любое типизированное значение может быть присвоено пустому идентификатору.
        2. Если нетипизированная константа присваивается переменной интерфейсного типа или пустому идентификатору, константа сначала неявно преобразуется в свой тип по умолчанию.
        3. Если нетипизированное булево значение присваивается переменной интерфейсного типа или пустому идентификатору, оно сначала неявно преобразуется в тип bool.

        Когда значение присваивается переменной, заменяются только данные, которые хранятся в переменной. Если значение содержит ссылку, присваивание копирует ссылку, но не создаёт копию данных, на которые ссылается (например, базового массива среза).

        var s1 = []int{1, 2, 3}
        var s2 = s1                    // s2 хранит дескриптор среза s1
        s1 = s1[:1]                    // длина s1 равна 1, но он всё ещё разделяет свой базовый массив с s2
        s2[0] = 42                     // установка s2[0] также изменяет s1[0]
        fmt.Println(s1, s2)            // выводит [42] [42 2 3]
        var m1 = make(map[string]int)
        var m2 = m1                    // m2 хранит дескриптор map m1
        m1["foo"] = 42                 // установка m1["foo"] также изменяет m2["foo"]
        fmt.Println(m2["foo"])         // выводит 42
        

        Операторы if

        Операторы "if" определяют условное выполнение двух ветвей в зависимости от значения булева выражения. Если выражение вычисляется в true, выполняется ветвь "if", в противном случае, если она присутствует, выполняется ветвь "else".

        IfStmt = "if" [ SimpleStmt ";" ]
                    Expression Block [ "else" (
                    IfStmt | Block ) ] .
        
        if x > max {
        	x = max
        }
        

        Перед выражением может быть простой оператор, который выполняется до вычисления выражения.

        if x := f(); x < y {
        	return x
        } else if x > z {
        	return z
        } else {
        	return y
        }
        

        Операторы switch

        Операторы "switch" обеспечивают многовариантное выполнение. Выражение или тип сравнивается с "case" внутри "switch", чтобы определить, какую ветвь выполнить.

        SwitchStmt = ExprSwitchStmt |
                    TypeSwitchStmt .
        

        Существует две формы: переключатели по выражениям и переключатели по типам. В переключателе по выражениям ветви case содержат выражения, которые сравниваются со значением выражения switch. В переключателе по типам ветви case содержат типы, которые сравниваются с типом специально аннотированного выражения switch. Выражение switch вычисляется ровно один раз в операторе switch.

        Переключатели по выражениям

        В переключателе по выражениям вычисляется выражение switch, и выражения case, которые не обязаны быть константами, вычисляются слева направо и сверху вниз; первое, которое равно выражению switch, запускает выполнение операторов связанной ветви case; остальные ветви пропускаются. Если ни одна ветвь case не совпадает и есть ветвь "default", выполняются её операторы. Может быть не более одной ветви default, и она может появляться в любом месте оператора "switch". Отсутствующее выражение switch эквивалентно булеву значению true.

        ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [
                    Expression ] "{" {
                    ExprCaseClause } "}" .
        ExprCaseClause = ExprSwitchCase ":"
                        StatementList .
        ExprSwitchCase = "case" ExpressionList | "default" .
        

        Если выражение switch вычисляется в нетипизированную константу, оно сначала неявно преобразуется в свой тип по умолчанию. Предварительно объявленное нетипизированное значение nil нельзя использовать в качестве выражения switch. Тип выражения switch должен быть сравнимым.

        Если выражение case нетипизировано, оно сначала неявно преобразуется в тип выражения switch. Для каждого (возможно, преобразованного) выражения case x и значения t выражения switch, x == t должно быть допустимым сравнением.

        Другими словами, выражение switch рассматривается так, как если бы оно использовалось для объявления и инициализации временной переменной t без явного типа; именно со значением t каждое выражение case x проверяется на равенство.

        В ветви case или default последний непустой оператор может быть (возможно, помеченным) оператором "fallthrough", чтобы указать, что управление должно перейти от конца этой ветви к первому оператору следующей ветви. В противном случае управление переходит к концу оператора "switch". Оператор "fallthrough" может появляться в качестве последнего оператора во всех ветвях переключателя по выражениям, кроме последней.

        Перед выражением switch может быть простой оператор, который выполняется до вычисления выражения.

        switch tag {
        default: s3()
        case 0, 1, 2, 3: s1()
        case 4, 5, 6, 7: s2()
        }
        switch x := f(); {  // отсутствующее выражение switch означает "true"
        case x < 0: return -x
        default: return x
        }
        switch {
        case x < y: f1()
        case x < z: f2()
        case x == 4: f3()
        }
        

        Ограничение реализации: Компилятор может запретить несколько выражений case, вычисляющихся в одну и ту же константу. Например, текущие компиляторы запрещают дублирующиеся целочисленные, константы с плавающей точкой или строковые константы в выражениях case.

        Переключатели по типам

        Переключатель по типам сравнивает типы, а не значения. В остальном он похож на переключатель по выражениям. Он обозначается специальным выражением switch в форме утверждения типа, использующим ключевое слово type вместо фактического типа:

        switch x.(type) {
        // cases
        }
        

        Ветви case затем сопоставляют фактические типы T с динамическим типом выражения x. Как и при утверждениях типа, x должен быть интерфейсного типа, но не параметром типа, и каждый неинтерфейсный тип T, перечисленный в case, должен реализовывать тип x. Типы, перечисленные в ветвях case переключателя по типам, должны быть все различными.

        TypeSwitchStmt  = "switch" [ SimpleStmt ";" ]
                    TypeSwitchGuard "{" {
                    TypeCaseClause } "}" .
        TypeSwitchGuard = [ identifier ":=" ]
                        PrimaryExpr "." "(" "type" ")" .
        TypeCaseClause  = TypeSwitchCase ":"
                        StatementList .
        TypeSwitchCase  = "case" TypeList | "default" .
        

        TypeSwitchGuard может включать краткое объявление переменной. Когда используется эта форма, переменная объявляется в конце TypeSwitchCase в неявном блоке каждой ветви. В ветвях с case, перечисляющим ровно один тип, переменная имеет этот тип; в противном случае переменная имеет тип выражения в TypeSwitchGuard.

        Вместо типа case может использовать предварительно объявленный идентификатор nil; эта ветвь выбирается, когда выражение в TypeSwitchGuard является nil значением интерфейса. Может быть не более одной ветви nil.

        Для выражения x типа interface{} следующий переключатель по типам:

        switch i := x.(type) {
        case nil:
        	printString("x is nil")                // тип i — это тип x (interface{})
        case int:
        	printInt(i)                            // тип i — int
        case float64:
        	printFloat64(i)                        // тип i — float64
        case func(int) float64:
        	printFunction(i)                       // тип i — func(int) float64
        case bool, string:
        	printString("type is bool or string")  // тип i — это тип x (interface{})
        default:
        	printString("don't know the type")     // тип i — это тип x (interface{})
        }
        

        может быть переписан как:

        v := x  // x вычисляется ровно один раз
        if v == nil {
        	i := v                                 // тип i — это тип x (interface{})
        	printString("x is nil")
        } else if i, isInt := v.(int); isInt {
        	printInt(i)                            // тип i — int
        } else if i, isFloat64 := v.(float64); isFloat64 {
        	printFloat64(i)                        // тип i — float64
        } else if i, isFunc := v.(func(int) float64); isFunc {
        	printFunction(i)                       // тип i — func(int) float64
        } else {
        	_, isBool := v.(bool)
        	_, isString := v.(string)
        	if isBool || isString {
        		i := v                         // тип i — это тип x (interface{})
        		printString("type is bool or string")
        	} else {
        		i := v                         // тип i — это тип x (interface{})
        		printString("don't know the type")
        	}
        }
        

        Параметр типа или обобщённый тип может использоваться в качестве типа в case. Если при инстанцировании этот тип окажется дублирующим другую запись в switch, выбирается первая совпадающая ветвь case.

        func f[P any](x any) int {
        	switch x.(type) {
        	case P:
        		return 0
        	case string:
        		return 1
        	case []P:
        		return 2
        	case []byte:
        		return 3
        	default:
        		return 4
        	}
        }
        var v1 = f[string]("foo")   // v1 == 0
        var v2 = f[byte]([]byte{})  // v2 == 2
        

        Перед защитой переключателя по типам может быть простой оператор, который выполняется до вычисления защиты.

        Оператор "fallthrough" не разрешён в переключателе по типам.

        Операторы for

        Оператор "for" задаёт повторяющееся выполнение блока. Существует три формы: Итерация может управляться одним условием, конструкцией "for" или конструкцией "range".

        ForStmt   = "for" [ Condition |
                    ForClause | RangeClause ]
                    Block .
        Condition = Expression .
        

        Операторы for с одним условием

        В простейшей форме оператор "for" задаёт повторяющееся выполнение блока до тех пор, пока булево условие вычисляется в true. Условие вычисляется перед каждой итерацией. Если условие отсутствует, оно эквивалентно булеву значению true.

        for a < b {
        	a *= 2
        }
        

        Операторы for с конструкцией for

        Оператор "for" с ForClause также управляется своим условием, но дополнительно может определять оператор инициализации и пост-оператор, такой как присваивание, оператор инкремента или декремента. Оператор инициализации может быть кратким объявлением переменной, но пост-оператор не может.

        ForClause = [ InitStmt ] ";" [
                    Condition ] ";" [ PostStmt ] .
        InitStmt  = SimpleStmt .
        PostStmt  = SimpleStmt .
        
        for i := 0; i < 10; i++ {
        	f(i)
        }
        

        Если оператор инициализации не пуст, он выполняется один раз перед вычислением условия для первой итерации; пост-оператор выполняется после каждого выполнения блока (и только если блок был выполнен). Любой элемент ForClause может быть пустым, но точки с запятой обязательны, если присутствует не только условие. Если условие отсутствует, оно эквивалентно булеву значению true.

        for cond { S() }    то же, что    for ; cond ; { S() }
        for      { S() }    то же, что    for true     { S() }
        

        Каждая итерация имеет свою отдельную объявленную переменную (или переменные) [Go 1.22]. Переменная, используемая первой итерацией, объявляется оператором инициализации. Переменная, используемая каждой последующей итерацией, объявляется неявно перед выполнением пост-оператора и инициализируется значением переменной предыдущей итерации в этот момент.

        var prints []func()
        for i := 0; i < 5; i++ {
        	prints = append(prints, func() { println(i) })
        	i++
        }
        for _, p := range prints {
        	p()
        }
        

        выводит

        1
        3
        5
        

        До [Go 1.22] итерации используют один набор переменных вместо собственных отдельных переменных. В этом случае приведённый выше пример выводит

        6
        6
        6
        

        Операторы for с конструкцией range

        Оператор "for" с конструкцией "range" итерирует по всем элементам массива, среза, строки или map, значениям, полученным из канала, целочисленным значениям от нуля до верхней границы [Go 1.22], или значениям, переданным функции yield функции-итератора [Go 1.23]. Для каждого элемента он присваивает значения итерации соответствующим переменным итерации, если они присутствуют, а затем выполняет блок.

        RangeClause = [ ExpressionList "=" |
                    IdentifierList ":=" ] "range"
                    Expression .
        

        Выражение справа в конструкции "range" называется выражением range, которое может быть массивом, указателем на массив, срезом, строкой, map, каналом, разрешающим операции приёма, целым числом или функцией с определённой сигнатурой (см. ниже). Как и при присваивании, если они присутствуют, операнды слева должны быть адресуемыми или выражениями индекса map; они обозначают переменные итерации. Если выражение range является функцией, максимальное количество переменных итерации зависит от сигнатуры функции. Если выражение range является каналом или целым числом, допускается не более одной переменной итерации; в противном случае может быть до двух. Если последняя переменная итерации является пустым идентификатором, конструкция range эквивалентна той же конструкции без этого идентификатора.

        Выражение range x вычисляется перед началом цикла, за одним исключением: если присутствует не более одной переменной итерации и x или len(x) является константой, выражение range не вычисляется.

        Вызовы функций слева вычисляются один раз за итерацию. Для каждой итерации значения итерации создаются следующим образом, если присутствуют соответствующие переменные итерации:

        Выражение Range                                       1-е значение             2-е значение
        массив или срез     a  [n]E, *[n]E, или []E          индекс    i  int         a[i]       E
        строка              s  тип string                    индекс    i  int         см. ниже   rune
        map                 m  map[K]V                       ключ      k  K           m[k]       V
        канал               c  chan E, <-chan E             элемент   e  E
        целое значение      n  целочисленный тип или         значение  i  см. ниже
                               нетипизированный int
        функция, 0 значений f  func(func() bool)
        функция, 1 значение f  func(func(V) bool)            значение  v  V
        функция, 2 значения f  func(func(K, V) bool)         ключ      k  K           v          V
        
        1. Для массива, указателя на массив или среза a значения индекса итерации создаются в возрастающем порядке, начиная с индекса элемента 0. Если присутствует не более одной переменной итерации, цикл range создаёт значения итерации от 0 до len(a)-1 и не индексирует сам массив или срез. Для nil среза количество итераций равно 0.
        2. Для строкового значения конструкция "range" итерирует по кодовым точкам Unicode в строке, начиная с байтового индекса 0. При последовательных итерациях значение индекса будет индексом первого байта последовательных UTF-8-кодированных кодовых точек в строке, а второе значение типа rune будет значением соответствующей кодовой точки. Если итерация встречает недопустимую последовательность UTF-8, второе значение будет 0xFFFD, символом замены Unicode, и следующая итерация продвинется на один байт в строке.
        3. Порядок итерации по map не определён и не гарантируется одинаковым от одной итерации к другой. Если элемент map, который ещё не был достигнут, удаляется во время итерации, соответствующее значение итерации не будет создано. Если элемент map создаётся во время итерации, этот элемент может быть создан во время итерации или может быть пропущен. Выбор может отличаться для каждого созданного элемента и от одной итерации к другой. Если map равен nil, количество итераций равно 0.
        4. Для каналов создаваемые значения итерации — это последовательные значения, отправленные в канал, до тех пор, пока канал не будет закрыт. Если канал равен nil, выражение range блокируется навсегда.
        5. Для целочисленного значения n, где n является целочисленным типом или нетипизированной целочисленной константой, значения итерации от 0 до n-1 создаются в возрастающем порядке. Если n целочисленного типа, значения итерации имеют тот же тип. В противном случае тип n определяется так, как если бы он был присвоен переменной итерации. А именно: если переменная итерации существует заранее, тип значений итерации — это тип переменной итерации, который должен быть целочисленным типом. В противном случае, если переменная итерации объявляется конструкцией "range" или отсутствует, тип значений итерации — это тип по умолчанию для n. Если n <= 0, цикл не выполняет ни одной итерации.
        6. Для функции f итерация выполняется путём вызова f с новой синтезированной функцией yield в качестве аргумента. Если yield вызывается до возврата f, аргументы yield становятся значениями итерации для однократного выполнения тела цикла. После каждой последующей итерации цикла yield возвращает true и может быть вызвана снова для продолжения цикла. Пока тело цикла не завершается, конструкция "range" будет продолжать генерировать значения итерации таким образом для каждого вызова yield, пока f не вернётся. Если тело цикла завершается (например, оператором break), yield возвращает false и не должна вызываться снова.

        Переменные итерации могут быть объявлены конструкцией "range" с использованием формы краткого объявления переменной (:=). В этом случае их область видимости — это блок оператора "for", и каждая итерация имеет свои новые переменные [Go 1.22] (см. также операторы "for" с ForClause). Переменные имеют типы своих соответствующих значений итерации.

        Если переменные итерации не объявлены явно конструкцией "range", они должны существовать заранее. В этом случае значения итерации присваиваются соответствующим переменным как в операторе присваивания.

        var testdata *struct {
        	a *[7]int
        }
        for i, _ := range testdata.a {
        	// testdata.a никогда не вычисляется; len(testdata.a) — константа
        	// i варьируется от 0 до 6
        	f(i)
        }
        var a [10]string
        for i, s := range a {
        	// тип i — int
        	// тип s — string
        	// s == a[i]
        	g(i, s)
        }
        var key string
        var val interface{}  // тип элемента m присваиваем val
        m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
        for key, val = range m {
        	h(key, val)
        }
        // key == последний ключ map, встреченный в итерации
        // val == map[key]
        var ch chan Work = producer()
        for w := range ch {
        	doWork(w)
        }
        // опустошить канал
        for range ch {}
        // вызвать f(0), f(1), ... f(9)
        for i := range 10 {
        	// тип i — int (тип по умолчанию для нетипизированной константы 10)
        	f(i)
        }
        // недопустимо: 256 нельзя присвоить uint8
        var u uint8
        for u = range 256 {
        }
        // недопустимо: 1e3 — константа с плавающей точкой
        for range 1e3 {
        }
        // fibo генерирует последовательность Фибоначчи
        fibo := func(yield func(x int) bool) {
        	f0, f1 := 0, 1
        	for yield(f0) {
        		f0, f1 = f1, f0+f1
        	}
        }
        // вывести числа Фибоначчи меньше 1000:
        for x := range fibo {
        	if x >= 1000 {
        		break
        	}
        	fmt.Printf("%d ", x)
        }
        // вывод: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
        // поддержка итерации для рекурсивной структуры данных дерева
        type Tree[K cmp.Ordered, V any] struct {
        	left, right *Tree[K, V]
        	key         K
        	value       V
        }
        func (t *Tree[K, V]) walk(yield func(key K, val V) bool) bool {
        	return t == nil || t.left.walk(yield) && yield(t.key, t.value) && t.right.walk(yield)
        }
        func (t *Tree[K, V]) Walk(yield func(key K, val V) bool) {
        	t.walk(yield)
        }
        // обход дерева t в порядке возрастания
        var t Tree[string, int]
        for k, v := range t.Walk {
        	// обработать k, v
        }
        

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

        Операторы go

        Оператор "go" запускает выполнение вызова функции как независимый параллельный поток управления, или горутину, в том же адресном пространстве.

        GoStmt = "go" Expression .
        

        Выражение должно быть вызовом функции или метода; оно не может быть заключено в скобки. Вызовы встроенных функций ограничены так же, как для операторов-выражений.

        Значение функции и параметры вычисляются как обычно в вызывающей горутине, но в отличие от обычного вызова, выполнение программы не ждёт завершения вызванной функции. Вместо этого функция начинает выполняться независимо в новой горутине. Когда функция завершается, её горутина также завершается. Если функция имеет какие-либо возвращаемые значения, они отбрасываются при завершении функции.

        go Server()
        go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)
        

        Операторы select

        Оператор "select" выбирает, какая из набора возможных операций отправки или приёма будет выполнена. Он выглядит похожим на оператор "switch", но со всеми ветвями case, относящимися к операциям коммуникации.

        SelectStmt = "select" "{" { CommClause } "}" .
        CommClause = CommCase ":"
                        StatementList .
        CommCase   = "case" ( SendStmt |
                        RecvStmt ) | "default" .
        RecvStmt   = [ ExpressionList "=" |
                        IdentifierList ":=" ] RecvExpr .
        RecvExpr   = Expression .
        

        Ветвь case с RecvStmt может присвоить результат RecvExpr одной или двум переменным, которые могут быть объявлены с использованием краткого объявления переменной. RecvExpr должно быть (возможно, заключённой в скобки) операцией приёма. Может быть не более одной ветви default, и она может появляться в любом месте списка ветвей case.

        Выполнение оператора "select" происходит в несколько шагов:

        1. Для всех ветвей case в операторе операнды канала операций приёма и выражения канала и правой части операторов отправки вычисляются ровно один раз, в исходном порядке, при входе в оператор "select". Результатом является набор каналов для приёма или отправки и соответствующие значения для отправки. Любые побочные эффекты этого вычисления произойдут независимо от того, какая (если таковая есть) операция коммуникации выбрана для выполнения. Выражения левой части RecvStmt с кратким объявлением переменной или присваиванием ещё не вычисляются.
        2. Если одна или более коммуникаций могут быть выполнены, одна из них, которая может быть выполнена, выбирается посредством равномерного псевдослучайного выбора. В противном случае, если есть ветвь default, выбирается эта ветвь. Если ветви default нет, оператор "select" блокируется до тех пор, пока хотя бы одна из коммуникаций не сможет быть выполнена.
        3. Если выбранная ветвь не является ветвью default, выполняется соответствующая операция коммуникации.
        4. Если выбранная ветвь — это RecvStmt с кратким объявлением переменной или присваиванием, выражения левой части вычисляются, и полученное значение (или значения) присваивается.
        5. Выполняется список операторов выбранной ветви case.

        Поскольку коммуникация по каналам nil никогда не может быть выполнена, select только с каналами nil и без ветви default блокируется навсегда.

        var a []int
        var c, c1, c2, c3, c4 chan int
        var i1, i2 int
        select {
        case i1 = <-c1:
        	print("received ", i1, " from c1\n")
        case c2 <- i2:
        	print("sent ", i2, " to c2\n")
        case i3, ok := (<-c3):  // то же, что: i3, ok := <-c3
        	if ok {
        		print("received ", i3, " from c3\n")
        	} else {
        		print("c3 is closed\n")
        	}
        case a[f()] = <-c4:
        	// то же, что:
        	// case t := <-c4
        	//	a[f()] = t
        default:
        	print("no communication\n")
        }
        for {  // отправить случайную последовательность битов в c
        	select {
        	case c <- 0:  // примечание: нет оператора, нет fallthrough, нет сворачивания ветвей case
        	case c <- 1:
        	}
        }
        select {}  // блокироваться навсегда
        

        Операторы return

        Оператор "return" в функции F завершает выполнение F и необязательно предоставляет одно или более возвращаемых значений. Любые функции, отложенные с помощью F, выполняются до того, как F вернёт управление своему вызывающему коду.

        ReturnStmt = "return" [ ExpressionList ] .
        

        В функции без типа результата оператор "return" не должен указывать никаких возвращаемых значений.

        func noResult() {
        	return
        }
        

        Существует три способа возврата значений из функции с типом результата:

        1. Возвращаемое значение или значения могут быть явно перечислены в операторе "return". Каждое выражение должно быть однозначным и присваиваемым соответствующему элементу типа результата функции.
          func simpleF() int {
          	return 2
          }
          func complexF1() (re float64, im float64) {
          	return -7.0, -4.0
          }
          
        2. Список выражений в операторе "return" может быть одним вызовом многозначной функции. Эффект такой, как если бы каждое значение, возвращённое из этой функции, было присвоено временной переменной с типом соответствующего значения, за которым следует оператор "return", перечисляющий эти переменные, и в этот момент применяются правила предыдущего случая.
          func complexF2() (re float64, im float64) {
          	return complexF1()
          }
          
        3. Список выражений может быть пустым, если тип результата функции указывает имена для своих параметров результата. Параметры результата действуют как обычные локальные переменные, и функция может присваивать им значения по мере необходимости. Оператор "return" возвращает значения этих переменных.
          func complexF3() (re float64, im float64) {
          	re = 7.0
          	im = 4.0
          	return
          }
          func (devnull) Write(p []byte) (n int, _ error) {
          	n = len(p)
          	return
          }
          

        Независимо от того, как они объявлены, все возвращаемые значения инициализируются нулевыми значениями для своего типа при входе в функцию. Оператор "return", который указывает результаты, устанавливает параметры результата до выполнения любых отложенных функций.

        Ограничение реализации: Компилятор может запретить пустой список выражений в операторе "return", если другая сущность (константа, тип или переменная) с тем же именем, что и параметр результата, находится в области видимости в месте return.

        func f(n int) (res int, err error) {
        	if _, err := f(n-1); err != nil {
        		return  // недопустимый оператор return: err затенён
        	}
        	return
        }
        

        Операторы break

        Оператор "break" завершает выполнение самого внутреннего оператора "for", "switch" или "select" внутри той же функции.

        BreakStmt = "break" [ Label ] .
        

        Если указана метка, она должна принадлежать внешнему оператору "for", "switch" или "select", и именно его выполнение завершается.

        OuterLoop:
        	for i = 0; i < n; i++ {
        		for j = 0; j < m; j++ {
        			switch a[i][j] {
        			case nil:
        				state = Error
        				break OuterLoop
        			case item:
        				state = Found
        				break OuterLoop
        			}
        		}
        	}
        

        Операторы continue

        Оператор "continue" начинает следующую итерацию самого внутреннего внешнего цикла "for", передавая управление в конец блока цикла. Цикл "for" должен находиться внутри той же функции.

        ContinueStmt = "continue" [ Label ] .
        

        Если указана метка, она должна принадлежать внешнему оператору "for", и именно его выполнение продолжается.

        RowLoop:
        	for y, row := range rows {
        		for x, data := range row {
        			if data == endOfRow {
        				continue RowLoop
        			}
        			row[x] = data + bias(x, y)
        		}
        	}
        

        Операторы goto

        Оператор "goto" передаёт управление оператору с соответствующей меткой внутри той же функции.

        GotoStmt = "goto" Label .
        
        goto Error
        

        Выполнение оператора "goto" не должно приводить к тому, чтобы какие-либо переменные попали в область видимости, которые не были уже в области видимости в точке goto. Например, этот пример:

        	goto L  // ПЛОХО
        	v := 3
        L:
        

        является ошибочным, потому что переход к метке L пропускает создание v.

        Оператор "goto" вне блока не может перейти к метке внутри этого блока. Например, этот пример:

        if n%2 == 1 {
        	goto L1
        }
        for n > 0 {
        	f()
        	n--
        L1:
        	f()
        	n--
        }
        

        является ошибочным, потому что метка L1 находится внутри блока оператора "for", а goto — нет.

        Операторы fallthrough

        Оператор "fallthrough" передаёт управление первому оператору следующей ветви case в операторе "switch" по выражению. Он может использоваться только как последний непустой оператор в такой ветви.

        FallthroughStmt = "fallthrough" .
        

        Операторы defer

        Оператор "defer" вызывает функцию, выполнение которой откладывается до момента возврата внешней функции либо потому, что внешняя функция выполнила оператор return, достигла конца своего тела функции, либо потому, что соответствующая горутина паникует.

        DeferStmt = "defer" Expression .
        

        Выражение должно быть вызовом функции или метода; оно не может быть заключено в скобки. Вызовы встроенных функций ограничены так же, как для операторов-выражений.

        Каждый раз, когда выполняется оператор "defer", значение функции и параметры вызова вычисляются как обычно и сохраняются заново, но фактическая функция не вызывается. Вместо этого отложенные функции вызываются непосредственно перед возвратом внешней функции, в обратном порядке их откладывания. То есть, если внешняя функция возвращается через явный оператор return, отложенные функции выполняются после того, как любые параметры результата установлены этим оператором return, но до того, как функция вернёт управление своему вызывающему коду. Если значение отложенной функции вычисляется в nil, выполнение паникует, когда функция вызывается, а не когда выполняется оператор "defer".

        Например, если отложенная функция является литералом функции, а внешняя функция имеет именованные параметры результата, которые находятся в области видимости внутри литерала, отложенная функция может получить доступ и изменить параметры результата до их возврата. Если отложенная функция имеет какие-либо возвращаемые значения, они отбрасываются при завершении функции. (См. также раздел об обработке паник.)

        lock(l)
        defer unlock(l)  // разблокировка происходит перед возвратом внешней функции
        // выводит 3 2 1 0 перед возвратом внешней функции
        for i := 0; i <= 3; i++ {
        	defer fmt.Print(i)
        }
        // f возвращает 42
        func f() (result int) {
        	defer func() {
        		// result доступен после того, как он был установлен в 6 оператором return
        		result *= 7
        	}()
        	return 6
        }
        

        Встроенные функции

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

        Встроенные функции не имеют стандартных типов Go, поэтому они могут появляться только в выражениях вызова; они не могут использоваться как значения функций.

        Добавление в срезы и копирование срезов

        Встроенные функции append и copy помогают в обычных операциях со срезами. Для обеих функций результат не зависит от того, перекрывается ли память, на которую ссылаются аргументы.

        Вариативная функция append добавляет ноль или более значений x к срезу s типа S и возвращает результирующий срез, также типа S. Значения x передаются параметру типа ...E, где E — это тип элемента S, и применяются соответствующие правила передачи параметров. В качестве особого случая append также принимает первый аргумент, присваиваемый типу []byte, со вторым аргументом типа string, за которым следует .... Эта форма добавляет байты строки.

        append(s S, x ...E) S  // E — это тип элемента S
        

        Если S является параметром типа, все типы в его множестве типов должны иметь один и тот же базовый тип среза []E.

        Если ёмкость s недостаточно велика для размещения дополнительных значений, append выделяет новый, достаточно большой базовый массив, который подходит как для существующих элементов среза, так и для дополнительных значений. В противном случае append повторно использует базовый массив.

        s0 := []int{0, 0}
        s1 := append(s0, 2)                // добавить один элемент        s1 равен []int{0, 0, 2}
        s2 := append(s1, 3, 5, 7)          // добавить несколько элементов s2 равен []int{0, 0, 2, 3, 5, 7}
        s3 := append(s2, s0...)            // добавить срез               s3 равен []int{0, 0, 2, 3, 5, 7, 0, 0}
        s4 := append(s3[3:6], s3[2:]...)   // добавить перекрывающийся срез s4 равен []int{3, 5, 7, 2, 3, 5, 7, 0, 0}
        var t []interface{}
        t = append(t, 42, 3.1415, "foo")   //                             t равен []interface{}{42, 3.1415, "foo"}
        var b []byte
        b = append(b, "bar"...)            // добавить содержимое строки  b равен []byte{'b', 'a', 'r' }
        

        Функция copy копирует элементы среза из источника src в назначение dst и возвращает количество скопированных элементов. Оба аргумента должны иметь идентичный тип элемента E и должны быть присваиваемыми срезу типа []E. Количество скопированных элементов — это минимум из len(src) и len(dst). В качестве особого случая copy также принимает аргумент назначения, присваиваемый типу []byte, с аргументом источника типа string. Эта форма копирует байты из строки в срез байтов.

        copy(dst, src []T) int
        copy(dst []byte, src string) int
        

        Если тип одного или обоих аргументов является параметром типа, все типы в их соответствующих множествах типов должны иметь один и тот же базовый тип среза []E.

        Примеры:

        var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
        var s = make([]int, 6)
        var b = make([]byte, 5)
        n1 := copy(s, a[0:])            // n1 == 6, s равен []int{0, 1, 2, 3, 4, 5}
        n2 := copy(s, s[2:])            // n2 == 4, s равен []int{2, 3, 4, 5, 4, 5}
        n3 := copy(b, "Hello, World!")  // n3 == 5, b равен []byte("Hello")
        

        Clear

        Встроенная функция clear принимает аргумент типа map, среза или параметра типа и удаляет или обнуляет все элементы [Go 1.21].

        Вызов       Тип аргумента     Результат
        clear(m)    map[K]T           удаляет все записи, в результате получается
                                      пустая map (len(m) == 0)
        clear(s)    []T               устанавливает все элементы до длины
                                      s в нулевое значение T
        clear(t)    параметр типа     см. ниже
        

        Если тип аргумента clear является параметром типа, все типы в его множестве типов должны быть map или срезами, и clear выполняет операцию, соответствующую фактическому аргументу типа.

        Если map или срез равен nil, clear не выполняет никаких действий.

        Close

        Для канала ch встроенная функция close(ch) фиксирует, что в канал больше не будет отправлено значений. Это ошибка, если ch является каналом только для приёма. Отправка в закрытый канал или закрытие закрытого канала вызывает паническую ситуацию во время выполнения. Закрытие nil канала также вызывает паническую ситуацию во время выполнения. После вызова close и после того, как все ранее отправленные значения были получены, операции приёма будут возвращать нулевое значение для типа канала без блокировки. Многозначная операция приёма возвращает полученное значение вместе с указанием того, закрыт ли канал.

        Если тип аргумента close является параметром типа, все типы в его множестве типов должны быть каналами с одним и тем же типом элемента. Это ошибка, если любой из этих каналов является каналом только для приёма.

        Манипулирование комплексными числами

        Три функции собирают и разбирают комплексные числа. Встроенная функция complex конструирует комплексное значение из вещественной и мнимой частей с плавающей точкой, в то время как real и imag извлекают вещественную и мнимую части комплексного значения.

        complex(realPart, imaginaryPart floatT) complexT
        real(complexT) floatT
        imag(complexT) floatT
        

        Тип аргументов и возвращаемого значения соответствуют. Для complex два аргумента должны быть одного и того же типа с плавающей точкой, и тип возвращаемого значения — комплексный тип с соответствующими составляющими с плавающей точкой: complex64 для аргументов float32 и complex128 для аргументов float64. Если один из аргументов вычисляется в нетипизированную константу, он сначала неявно преобразуется в тип другого аргумента. Если оба аргумента вычисляются в нетипизированные константы, они должны быть не комплексными числами, или их мнимые части должны быть равны нулю, и возвращаемое значение функции является нетипизированной комплексной константой.

        Для real и imag аргумент должен быть комплексного типа, а тип возвращаемого значения — соответствующий тип с плавающей точкой: float32 для аргумента complex64 и float64 для аргумента complex128. Если аргумент вычисляется в нетипизированную константу, он должен быть числом, и возвращаемое значение функции является нетипизированной константой с плавающей точкой.

        Функции real и imag вместе образуют обратную функцию к complex, поэтому для значения z комплексного типа Z z == Z(complex(real(z), imag(z))).

        Если операнды этих функций являются константами, возвращаемое значение является константой.

        var a = complex(2, -2)             // complex128
        const b = complex(1.0, -1.4)       // нетипизированная комплексная константа 1 - 1.4i
        x := float32(math.Cos(math.Pi/2))  // float32
        var c64 = complex(5, -x)           // complex64
        var s int = complex(1, 0)          // нетипизированная комплексная константа 1 + 0i может быть преобразована в int
        _ = complex(1, 2<<s)               // недопустимо: 2 предполагает тип с плавающей точкой, нельзя сдвигать
        var rl = real(c64)                 // float32
        var im = imag(a)                   // float64
        const c = imag(b)                  // нетипизированная константа -1.4
        _ = imag(3 << s)                   // недопустимо: 3 предполагает комплексный тип, нельзя сдвигать
        

        Аргументы типа параметра типа не допускаются.

        Удаление элементов map

        Встроенная функция delete удаляет элемент с ключом k из map m. Значение k должно быть присваиваемым типу ключа m.

        delete(m, k)  // удалить элемент m[k] из map m
        

        Если тип m является параметром типа, все типы в этом множестве типов должны быть map, и все они должны иметь идентичные типы ключей.

        Если map m равен nil или элемент m[k] не существует, delete не выполняет никаких действий.

        Длина и ёмкость

        Встроенные функции len и cap принимают аргументы различных типов и возвращают результат типа int. Реализация гарантирует, что результат всегда помещается в int.

        Вызов     Тип аргумента    Результат
        len(s)    тип string       длина строки в байтах
                  [n]T, *[n]T      длина массива (== n)
                  []T              длина среза
                  map[K]T          длина map (количество определённых ключей)
                  chan T           количество элементов в буфере канала
                  параметр типа    см. ниже
        cap(s)    [n]T, *[n]T      длина массива (== n)
                  []T              ёмкость среза
                  chan T           ёмкость буфера канала
                  параметр типа    см. ниже
        

        Если тип аргумента является параметром типа P, вызов len(e) (или cap(e) соответственно) должен быть допустимым для каждого типа в множестве типов P. Результатом является длина (или ёмкость соответственно) аргумента, тип которого соответствует аргументу типа, с которым был инстанцирован P.

        Ёмкость среза — это количество элементов, для которых выделено место в базовом массиве. В любой момент выполняется следующее соотношение:

        0 <= len(s) <= cap(s)
        

        Длина nil среза, map или канала равна 0. Ёмкость nil среза или канала равна 0.

        Выражение len(s) является константой, если s является строковой константой. Выражения len(s) и cap(s) являются константами, если тип s — массив или указатель на массив, и выражение s не содержит операций приёма из канала или (не константных) вызовов функций; в этом случае s не вычисляется. В противном случае вызовы len и cap не являются константами, и s вычисляется.

        const (
        	c1 = imag(2i)                    // imag(2i) = 2.0 является константой
        	c2 = len([10]float64{2})         // [10]float64{2} не содержит вызовов функций
        	c3 = len([10]float64{c1})        // [10]float64{c1} не содержит вызовов функций
        	c4 = len([10]float64{imag(2i)})  // imag(2i) — константа, и вызов функции не выполняется
        	c5 = len([10]float64{imag(z)})   // недопустимо: imag(z) — (не константный) вызов функции
        )
        var z complex128
        

        Создание срезов, map и каналов

        Встроенная функция make принимает тип T, который должен быть типом среза, map или канала, или параметром типа, необязательно за которым следует специфичный для типа список выражений. Она возвращает значение типа T (не *T). Память инициализируется, как описано в разделе о начальных значениях.

        Вызов            Тип T             Результат
        make(T, n)       срез              срез типа T с длиной n и ёмкостью n
        make(T, n, m)    срез              срез типа T с длиной n и ёмкостью m
        make(T)          map               map типа T
        make(T, n)       map               map типа T с начальным пространством примерно для n элементов
        make(T)          канал             небуферизованный канал типа T
        make(T, n)       канал             буферизованный канал типа T с размером буфера n
        make(T, n)       параметр типа     см. ниже
        make(T, n, m)    параметр типа     см. ниже
        

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

        Каждый из аргументов размера n и m должен быть целочисленного типа, иметь множество типов, содержащее только целочисленные типы, или быть нетипизированной константой. Аргумент константного размера должен быть неотрицательным и представимым значением типа int; если это нетипизированная константа, ей присваивается тип int. Если указаны и n, и m, и они являются константами, то n не должно быть больше m. Для срезов и каналов, если n отрицательно или больше m во время выполнения, возникает паническая ситуация во время выполнения.

        s := make([]int, 10, 100)       // срез с len(s) == 10, cap(s) == 100
        s := make([]int, 1e3)           // срез с len(s) == cap(s) == 1000
        s := make([]int, 1<<63)         // недопустимо: len(s) не представимо значением типа int
        s := make([]int, 10, 0)         // недопустимо: len(s) > cap(s)
        c := make(chan int, 10)         // канал с размером буфера 10
        m := make(map[string]int, 100)  // map с начальным пространством примерно для 100 элементов
        

        Вызов make с типом map и подсказкой размера n создаст map с начальным пространством для размещения n элементов map. Точное поведение зависит от реализации.

        Min и max

        Встроенные функции min и max вычисляют наименьшее или наибольшее соответственно значение из фиксированного числа аргументов упорядоченных типов. Должен быть как минимум один аргумент [Go 1.21].

        Применяются те же правила для типов, что и для операторов: для упорядоченных аргументов x и y, min(x, y) допустимо, если допустимо x + y, и тип min(x, y) — это тип x + y (и аналогично для max). Если все аргументы являются константами, результат является константой.

        var x, y int
        m := min(x)                 // m == x
        m := min(x, y)              // m — меньшее из x и y
        m := max(x, y, 10)          // m — большее из x и y, но не менее 10
        c := max(1, 2.0, 10)        // c == 10.0 (вид с плавающей точкой)
        f := max(0, float32(x))     // тип f — float32
        var s []string
        _ = min(s...)               // недопустимо: аргументы-срезы не разрешены
        t := max("", "foo", "bar")  // t == "foo" (строковый вид)
        

        Для числовых аргументов, предполагая, что все NaN равны, min и max коммутативны и ассоциативны:

        min(x, y)    == min(y, x)
        min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))
        

        Для аргументов с плавающей точкой отрицательный ноль, NaN и бесконечность применяются следующие правила:

           x        y    min(x, y)    max(x, y)
          -0.0    0.0         -0.0          0.0    // отрицательный ноль меньше (неотрицательного) нуля
          -Inf      y         -Inf            y    // отрицательная бесконечность меньше любого другого числа
          +Inf      y            y         +Inf    // положительная бесконечность больше любого другого числа
           NaN      y          NaN          NaN    // если любой аргумент — NaN, результат — NaN
        

        Для строковых аргументов результатом для min является первый аргумент с наименьшим (или для max наибольшим) значением, сравниваемым лексически побайтово:

        min(x, y)    == if x <= y then x else y
        min(x, y, z) == min(min(x, y), z)
        

        Выделение памяти

        Встроенная функция new принимает тип T, выделяет память для переменной этого типа во время выполнения и возвращает значение типа *T, указывающее на неё. Переменная инициализируется, как описано в разделе о начальных значениях.

        new(T)
        

        Например

        type S struct { a int; b float64 }
        new(S)
        

        выделяет память для переменной типа S, инициализирует её (a=0, b=0.0) и возвращает значение типа *S, содержащее адрес местоположения.

        Обработка паник

        Две встроенные функции, panic и recover, помогают в сообщении и обработке паник во время выполнения и определённых программой условий ошибок.

        func panic(interface{})
        func recover() interface{}
        

        При выполнении функции F явный вызов panic или паника во время выполнения завершает выполнение F. Любые функции, отложенные с помощью F, затем выполняются как обычно. Далее выполняются любые отложенные функции вызывающим F, и так далее вплоть до любых отложенных функцией верхнего уровня в выполняющейся горутине. В этот момент программа завершается, и условие ошибки сообщается, включая значение аргумента panic. Эта последовательность завершения называется паникованием.

        panic(42)
        panic("unreachable")
        panic(Error("cannot parse"))
        

        Функция recover позволяет программе управлять поведением паникующей горутины. Предположим, функция G откладывает функцию D, которая вызывает recover, и паника происходит в функции на той же горутине, в которой выполняется G. Когда выполнение отложенных функций достигает D, возвращаемое значение вызова recover функцией D будет значением, переданным в вызов panic. Если D возвращается нормально, без запуска новой panic, последовательность паникования останавливается. В этом случае состояние функций, вызванных между G и вызовом panic, отбрасывается, и нормальное выполнение возобновляется. Любые функции, отложенные G до D, затем выполняются, и выполнение G завершается возвратом к своему вызывающему коду.

        Возвращаемое значение recover равно nil, когда горутина не паникует или recover не был вызван напрямую отложенной функцией. И наоборот, если горутина паникует и recover был вызван напрямую отложенной функцией, гарантируется, что возвращаемое значение recover не будет nil. Чтобы обеспечить это, вызов panic со значением интерфейса nil (или нетипизированным nil) вызывает паническую ситуацию во время выполнения.

        Функция protect в примере ниже вызывает аргумент функции g и защищает вызывающий код от паник во время выполнения, вызванных g.

        func protect(g func()) {
        	defer func() {
        		log.Println("done")  // Println выполняется нормально, даже если есть паника
        		if x := recover(); x != nil {
        			log.Printf("run time panic: %v", x)
        		}
        	}()
        	log.Println("start")
        	g()
        }
        

        Начальная загрузка

        Текущие реализации предоставляют несколько встроенных функций, полезных во время начальной загрузки. Эти функции задокументированы для полноты, но не гарантируется, что они останутся в языке. Они не возвращают результат.

        Функция    Поведение
        print      выводит все аргументы; форматирование аргументов зависит от реализации
        println    как print, но выводит пробелы между аргументами и новую строку в конце
        

        Ограничение реализации: print и println не обязаны принимать произвольные типы аргументов, но вывод булевых, числовых и строковых типов должен поддерживаться.

        Пакеты

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

        Организация исходного файла

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

        SourceFile =
        PackageClause ";"
        { ImportDecl ";" }
        { TopLevelDecl ";" } .
        

        Объявление пакета

        Каждое исходное объявление начинается с инструкции пакета, которая определяет, к какому пакету принадлежит данный файл.

        PackageClause = "package" PackageName .
        PackageName   = identifier .
        

        Имя пакета (PackageName) не должно быть пустым идентификатором.

        package math
        

        Набор файлов, использующих одно и то же имя пакета (PackageName), образует реализацию этого пакета. Реализация может требовать, чтобы все исходные файлы пакета находились в одном каталоге.

        Импорт пакетов

        Инструкция import указывает, что исходный файл, содержащий это объявление, зависит от функциональности импортируемого пакета (§Инициализация и выполнение программы) и предоставляет доступ к экспортированным идентификаторам этого пакета. Импорт задаёт имя идентификатора (PackageName), используемого для доступа, и путь импорта (ImportPath), определяющий, какой пакет импортировать.

        ImportDecl = "import" (
            ImportSpec
            | "(" { ImportSpec ";" } ")"
        ) .
        ImportSpec = [ "." | PackageName ]
                          ImportPath .
        ImportPath = string_lit .
        

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

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

        Ограничение реализации: компилятор может ограничивать пути импорта непустыми строками, состоящими только из символов категорий Unicode L, M, N, P и S (графические символы без пробелов), а также исключать символы !"#$%&'()*,:;<=>?[\]^`{|} и символ замены Unicode U+FFFD.

        Рассмотрим скомпилированный пакет, содержащий объявление package math, экспортирующий функцию Sin, и установленный в файл с именем "lib/math". В следующей таблице показано, как обращаться к Sin в исходных файлах, импортирующих этот пакет разными способами:

        Импорт                         Локальное имя Sin
        import   "lib/math"             math.Sin
        import m "lib/math"             m.Sin
        import . "lib/math"             Sin
        

        Объявление import задаёт зависимость между импортирующим и импортируемым пакетом. Пакет не может импортировать сам себя — ни напрямую, ни косвенно. Также недопустимо импортировать пакет, не используя ни один из его экспортированных идентификаторов. Чтобы импортировать пакет только ради его побочных эффектов (инициализации), используйте пустой идентификатор в качестве имени пакета:

        import _ "lib/math"

        Пример пакета

        Ниже приведён полный пример пакета на Go, реализующего конкурентное решето Эратосфена для поиска простых чисел.

        package main
        import "fmt"
        
        // Отправляет последовательность 2, 3, 4, … в канал 'ch'.
        func generate(ch chan<- int) {
        	for i := 2; ; i++ {
        		ch <- i  // Отправить 'i' в канал 'ch'.
        	}
        }
        
        // Копирует значения из канала 'src' в 'dst',
        // удаляя числа, кратные 'prime'.
        func filter(src <-chan int, dst chan<- int, prime int) {
        	for i := range src {  // Перебор значений, полученных из 'src'.
        		if i%prime != 0 {
        			dst <- i  // Отправить 'i' в канал 'dst'.
        		}
        	}
        }
        
        // Решето Эратосфена: последовательная цепочка фильтров.
        func sieve() {
        	ch := make(chan int)  // Создать новый канал.
        	go generate(ch)       // Запустить generate() как отдельную горутину.
        	for {
        		prime := <-ch
        		fmt.Print(prime, "\n")
        		ch1 := make(chan int)
        		go filter(ch, ch1, prime)
        		ch = ch1
        	}
        }
        
        func main() {
        	sieve()
        }
        

        Инициализация и выполнение программы

        Нулевое значение

        Когда для переменной выделяется память — через объявление или вызов new, либо когда создаётся новое значение через составной литерал или вызов make — и не указана явная инициализация, переменной или значению присваивается значение по умолчанию. Каждый элемент такой переменной или значения устанавливается в нулевое значение своего типа: false для булевых типов, 0 для числовых, "" для строк, и nil — для указателей, функций, интерфейсов, срезов, каналов и карт. Эта инициализация выполняется рекурсивно — например, если массив содержит структуры, то все их поля также будут установлены в нулевые значения, если явно не указано иное.

        Следующие два простых объявления эквивалентны:

        var i int
        var i int = 0
        

        После выполнения:

        type T struct { i int; f float64; next *T }
        t := new(T)
        

        будет верно следующее:

        t.i == 0
        t.f == 0.0
        t.next == nil
        

        То же самое будет справедливо и после:

        var t T
        

        Инициализация пакета

        Внутри пакета инициализация переменных уровня пакета выполняется поэтапно: на каждом шаге выбирается переменная, которая расположена раньше других в порядке объявления и не зависит от ещё неинициализированных переменных.

        Более точно: переменная уровня пакета считается готовой к инициализации, если она ещё не инициализирована и при этом либо не имеет инициализирующего выражения, либо её инициализирующее выражение не содержит зависимостей от неинициализированных переменных. Инициализация выполняется путём повторного выбора следующей переменной уровня пакета, расположенной первой по порядку объявления и готовой к инициализации, до тех пор, пока таких переменных не останется.

        Если после завершения этого процесса остаются неинициализированные переменные, они принадлежат к одной или нескольким циклическим зависимостям, и программа считается недопустимой.

        Если в объявлении переменных слева несколько имён, а справа одно (многозначное) выражение, то все такие переменные инициализируются одновременно: если инициализируется хотя бы одна из них, инициализируются все.

        var x = a
        var a, b = f() // a и b инициализируются одновременно, до инициализации x
        

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

        Порядок объявления переменных, находящихся в разных файлах одного пакета, определяется порядком, в котором файлы передаются компилятору: переменные из первого файла считаются объявленными раньше, чем переменные из второго, и так далее. Чтобы обеспечить воспроизводимое поведение инициализации, системам сборки рекомендуется передавать файлы одного пакета компилятору в лексикографическом порядке имён файлов.

        Анализ зависимостей выполняется на уровне пакета и основывается не на фактических значениях переменных, а на их лексических ссылках

        Инициализация программы

        Пакеты полной программы инициализируются пошагово — по одному пакету за раз. Если пакет имеет импорты, то импортируемые пакеты инициализируются прежде, чем будет инициализирован сам пакет. Если несколько пакетов импортируют один и тот же пакет, этот импортируемый пакет будет инициализирован только один раз. Конструкция импорта пакетов гарантирует отсутствие циклических зависимостей при инициализации. Более точно:

        Если взять список всех пакетов, отсортированных по пути импорта, то на каждом шаге выбирается первый неинициализированный пакет, все импортируемые пакеты которого (если есть) уже инициализированы. Этот пакет инициализируется. Процесс повторяется, пока все пакеты не будут инициализированы.

        Инициализация пакетов — то есть инициализация переменных и вызовы функций init — выполняется в одной горутине последовательно, по одному пакету за раз. Функция init может запускать другие горутины, которые будут выполняться параллельно с инициализационным кодом. Однако последовательность инициализации всегда сохраняется: следующая функция init не будет вызвана, пока не завершится предыдущая.

        Выполнение программы

        Полная программа создаётся путём компоновки единственного, не импортируемого пакета, называемого главным пакетом (main package), вместе со всеми пакетами, которые он импортирует (транзитивно). Главный пакет должен иметь имя main и определять функцию main, не принимающую аргументов и не возвращающую значения.

        func main() { … }
        

        Выполнение программы начинается с инициализации программы, после чего вызывается функция main из пакета main. Когда этот вызов завершается, программа завершает работу. Она не ожидает завершения других (не-main) горутин.

        Ошибки

        Предопределённый тип error определяется следующим образом:

        type error interface {
            Error() string
        }
        

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

        func Read(f *File, b []byte) (n int, err error)
        

        Ошибки времени выполнения (panic)

        Ошибки выполнения, такие как попытка обратиться к элементу массива за пределами его длины, вызывают ошибку времени выполнения (run-time panic), эквивалентную вызову встроенной функции panic со значением интерфейсного типа runtime.Error, определённого реализацией. Этот тип удовлетворяет предопределённому интерфейсу error. Конкретные значения ошибок, соответствующие различным ситуациям, не определены стандартом.

        package runtime
        type Error interface {
            error
            // и, возможно, другие методы
        }
        

        Системные особенности

        Пакет unsafe

        Встроенный пакет unsafe, известный компилятору и доступный через путь импорта "unsafe", предоставляет средства для низкоуровневого программирования, включая операции, нарушающие систему типов. Пакеты, использующие unsafe, должны быть проверены вручную на безопасность типов и могут быть непереносимыми. Пакет предоставляет следующий интерфейс:

        package unsafe
        type ArbitraryType int  // псевдоним для произвольного типа Go; не является реальным типом
        type Pointer *ArbitraryType
        func Alignof(variable ArbitraryType) uintptr
        func Offsetof(selector ArbitraryType) uintptr
        func Sizeof(variable ArbitraryType) uintptr
        type IntegerType int  // псевдоним для целого типа; не является реальным типом
        func Add(ptr Pointer, len IntegerType) Pointer
        func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
        func SliceData(slice []ArbitraryType) *ArbitraryType
        func String(ptr *byte, len IntegerType) string
        func StringData(str string) *byte
        

        Тип Pointer является указательным типом, однако значение Pointer нельзя разыменовать. Любой указатель или значение с базовым типом uintptr может быть преобразовано в тип с базовым типом Pointer и обратно. Если соответствующие типы являются параметрами типа, то все типы в их множествах должны иметь одинаковый базовый тип — соответственно uintptr и Pointer. Эффект преобразования между Pointer и uintptr определяется реализацией.

        var f float64
        bits = *(*uint64)(unsafe.Pointer(&f))
        type ptr unsafe.Pointer
        bits = *(*uint64)(ptr(&f))
        func f[P ~*B, B any](p P) uintptr {
            return uintptr(unsafe.Pointer(p))
        }
        var p ptr = nil
        

        Функции Alignof и Sizeof принимают выражение x любого типа и возвращают, соответственно, выравнивание или размер гипотетической переменной v, как если бы она была объявлена через var v = x.

        Функция Offsetof принимает (возможно, заключённый в скобки) селектор s.f, обозначающий поле f структуры s или *s, и возвращает смещение поля в байтах относительно адреса структуры. Если f — это встроенное поле, оно должно быть достижимо без указательных разыменований.

        uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))
        

        Архитектуры компьютеров могут требовать, чтобы адреса памяти были выровнены, то есть адрес переменной должен быть кратен выравниванию типа переменной. Функция Alignof возвращает это значение в байтах.

        uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0
        

        Переменная типа T имеет переменный размер, если T — это параметр типа, либо если это массив или структура, содержащие элементы переменного размера. В противном случае размер считается постоянным. Вызовы функций Alignof, Offsetof и Sizeof являются константными выражениями типа uintptr, если их аргументы имеют постоянный размер.

        Функция Add добавляет len к ptr и возвращает обновлённый указатель unsafe.Pointer(uintptr(ptr) + uintptr(len)) [Go 1.17]. Аргумент len должен быть целого типа или нетипизированной константой.

        Функция Slice возвращает срез, чей базовый массив начинается по адресу ptr, а длина и ёмкость равны len.

        (*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
        

        Если ptr равно nil и len равно нулю, функция возвращает nil [Go 1.17].

        Функция SliceData возвращает указатель на базовый массив среза. Если ёмкость cap(slice) не равна нулю, возвращается &slice[:1][0]. Если срез nil — результат nil. В противном случае возвращается ненулевой указатель на неопределённый адрес памяти [Go 1.20].

        Функция String возвращает строку, байты которой начинаются по адресу ptr и имеют длину len. Если len равно нулю — возвращается пустая строка. Так как строки Go неизменяемы, байты, на которые указывает ptr, не должны изменяться после вызова String.

        Функция StringData возвращает указатель на байты строки str. Для пустой строки возвращаемое значение не определено и может быть nil. Так как строки Go неизменяемы, возвращённые байты нельзя изменять.

        Гарантии размера и выравнивания

        Для числовых типов гарантируются следующие размеры:

        тип                                 размер в байтах
        byte, uint8, int8                     1
        uint16, int16                         2
        uint32, int32, float32                4
        uint64, int64, float64, complex64     8
        complex128                           16
        

        Гарантируются следующие минимальные свойства выравнивания:

        1. Для переменной x любого типа: unsafe.Alignof(x) не меньше 1.
        2. Для переменной x структурного типа: unsafe.Alignof(x) равно наибольшему из значений unsafe.Alignof(x.f) для каждого поля f структуры x, но не меньше 1.
        3. Для переменной x массивного типа: unsafe.Alignof(x) совпадает с выравниванием переменной того же типа, что и элементы массива.

        Структура или массив имеют нулевой размер, если они не содержат полей (или элементов соответственно) с размером больше нуля. Две различные переменные нулевого размера могут иметь один и тот же адрес в памяти.

        Приложение

        Версии языка

        Гарантия совместимости Go 1 гарантирует, что программы, написанные в соответствии со спецификацией Go 1, будут продолжать компилироваться и работать корректно, без изменений, на протяжении всего срока действия этой спецификации. Более общо, при внесении корректировок и добавлении новых возможностей в язык, гарантия совместимости обеспечивает, что программа Go, работающая с определённой версией языка, продолжит работать и с любыми последующими версиями.

        Например, возможность использовать префикс 0b для двоичных целых литералов была введена в Go 1.13, что обозначено как [Go 1.13] в разделе о целых литералах. Исходный код, содержащий литерал 0b1011, будет отклонён компилятором, если используемая (или требуемая) версия языка старше Go 1.13.

        В следующей таблице указана минимальная версия языка, необходимая для поддержки возможностей, добавленных после Go 1.

        Go 1.9

        Go 1.13

        Go 1.14

        Go 1.17

        Go 1.18

        В версии 1.18 в язык были добавлены полиморфные функции и типы («дженерики»). В частности:

        Go 1.20

        Go 1.21

        Go 1.22

        Go 1.23

        Go 1.24

        GoRu.dev Golang на русском

        На сайте представлена адаптированная под русский язык документация языка программирования Golang