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

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

Пламенный привет посетителям этой страницы, пришедшим из социальных сетей, да и всем остальным тоже!

В апреле 2021-го года наблюдал удивительное явление: обильный поток посетителей из 4-х социальных сетей. В связи с этим настоятельно рекомендую всем неоднократно и регулярно посещать сайт rtbsm.ru — там в общих чертах изложена Российская Теннисная Балльная Система Марии (Шараповой).

Приглашаю всех полюбоваться на Фото и Видео красавицы Марии — надеюсь, что Вы поделитесь адресом сайта rtbsm.ru с друзьями и знакомыми.

Главная проблема — известить Марию, чтобы она лично как можно скорее заявила на весь мир о РТБСМ.

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

Привожу информацию со страницы https://go.dev/ref/spec :

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

Языковая версия go1.23 (13 июня 2024 г.)

Оглавление
Введение
Обозначение
Представление исходного кода
Персонажи
Буквы и цифры
Лексические элементы
Комментарии
Жетоны
Точки с запятой
Идентификаторы
Ключевые слова
Операторы и пунктуация
Целочисленные литералы
Литералы с плавающей точкой
Мнимые литералы
Рунические литералы
Строковые литералы
Константы
Переменные
Типы
Булевы типы
Числовые типы
Типы строк
Типы массивов
Типы срезов
Типы конструкций
Типы указателей
Типы функций
Типы интерфейсов
Типы карт
Типы каналов
Свойства типов и значений
Базовые типы
Типы сердечников
Тип идентичности
Назначаемость
Репрезентативность
Методы наборов
Блоки
Декларации и область применения
Области применения этикеток
Пустой идентификатор
Предварительно объявленные идентификаторы
Экспортированные идентификаторы
Уникальность идентификаторов
Постоянные заявления
Йота
Тип декларации
Объявления параметров типа
Объявления переменных
Короткие объявления переменных
Декларации функций
Декларации методов
Выражения
Операнды
Квалифицированные идентификаторы
Составные литералы
Функциональные литералы
Первичные выражения
Селекторы
Метод выражений
Метод значений
Индексные выражения
Выражения среза
Утверждения типа
Звонки
Передача аргументов в … параметры
Инстантации
Вывод типа
Операторы
Арифметические операторы
Операторы сравнения
Логические операторы
Операторы адресов
Прием оператора
Конверсии
Постоянные выражения
Порядок оценки
Заявления
Завершение заявлений
Пустые заявления
Помеченные утверждения
Выражения высказываний
Отправить заявления
заявления IncDec
Заявления о назначении
Если утверждения
Переключить операторы
Для заявлений
Перейти заявления
Выберите утверждения
Возврат заявлений
Перерыв в заявлениях
Продолжить заявления
Goto-утверждения
Заявления о провале
Отсрочка заявлений
Встроенные функции
Добавление и копирование срезов
Прозрачный
Закрывать
Манипулирование комплексными числами
Удаление элементов карты
Длина и вместимость
Создание срезов, карт и каналов
Мин и макс
Распределение
Как справиться с паникой
Самозагрузка
Пакеты
Организация исходного файла
Пакетный пункт
Импортные декларации
Пример пакета
Инициализация и выполнение программы
Нулевое значение
Инициализация пакета
Инициализация программы
Выполнение программы
Ошибки
Паника во время выполнения
Системные соображения
Упаковка небезопасна
Гарантии размера и выравнивания
Приложение
Языковые версии
Правила унификации типов

Введение 

Это справочное руководство по языку программирования Go. Версию до Go1.18, без дженериков, можно найти здесь (). Для получения дополнительной информации и других документов см. go.dev ().

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

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

Обозначение 

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

Синтаксис = {Производство}.
Производство = имя_производства "=" [Выражение] "." .
Выражение = Термин { "|" Термин } .
Термин = Фактор { Фактор } .
Фактор = имя_производства | токен [ токен "…" ] | Группа | Параметр | Повторение .
Группа = "(" Выражение ")" .
Параметр = "[" Выражение "]" .
Повторение = "{" Выражение "}" .

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

| чередование
() группировка
[] вариант (0 или 1 раз)
{} повторение (от 0 до n раз)

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

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

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

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

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

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

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

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

Персонажи 

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

newline         = /* кодовая точка Unicode U+000A */ .
 unicode_char    = /* произвольная кодовая точка Unicode, кроме символа новой строки */ .
 unicode_letter = /* кодовая точка Unicode, относящаяся к категории «Буква» */ .
 unicode_digit   = /* кодовая точка Unicode, относящаяся к категории «Число, десятичная цифра» */ .

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

Буквы и цифры 

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

буква         = буква_юникода | "_" .
 десятичная_цифра = "0" … "9" .
 двоичная_цифра =   "0" | "1" .
 восьмеричная_цифра    = "0" … "7" .
 шестнадцатеричная_цифра      = "0" … "9" | "A" … "F" | "a" … "f" .

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

Комментарии 

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

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

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

Токены 

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

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

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

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

Для отражения идиоматического использования в примерах кода в этом документе точки с запятой опускаются с использованием этих правил.

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

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

идентификатор = буква { буква | цифра_юникода } .
а
_x9
ЭтаПеременнаяЭкспортируется
αβ

Некоторые идентификаторы объявлены заранее .

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

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

break default func интерфейс выбор
случай отсрочки перехода к карте структуры
chan else goto пакетный переключатель
константа проваливается, если тип диапазона
продолжить для импорта return var

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

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

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

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

Целочисленный литерал — это последовательность цифр, представляющая целочисленную константу . Необязательный префикс задает недесятичную базу: 0bor 0B для двоичной, 00o, или 0Oдля восьмеричной, и 0xили 0Xдля шестнадцатеричной [ Go 1.13 ]. Одиночный символ 0считается десятичным нулем. В шестнадцатеричных литералах буквы aот f до представляют значения Aот F10 до 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 .

десятичные_цифры = десятичная_цифра { [ "_" ] десятичная_цифра } .
 двоичные_цифры
 =   двоичная_цифра { [ "_" ] двоичная_цифра } . восьмеричные_цифры = восьмеричная_цифра { [ "_" ] восьмеричная_цифра } .
 шестнадцатеричные_цифры    = шестнадцатеричная_цифра { [      " _ " ] шестнадцатеричная_цифра } .
42
4_2
0600
0_600
0o600
0O600 // второй символ — заглавная буква «О»
0xBadFace
0xПлохое_Лицо
0x_67_7a_2f_cc_40_c6
170141183460469231731687303715884105727
170_141183_460469_231731_687303_715884_105727

_42 // идентификатор, а не целочисленный литерал
42_ // недопустимо: _ должен разделять последовательные цифры
4__2 // недопустимо: только один _ за раз
0_xBadFace // недопустимо: _ должен разделять последовательные цифры

Литералы с плавающей точкой 

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

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

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

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

float_lit          = decimal_float_lit | hex_float_lit .

decimal_float_lit = десятичные_цифры "." [ десятичные_цифры ] [ десятичная_экспонента ] |
                     десятичные_цифры  десятичная_экспонента |
                    "." десятичные_цифры [ десятичная_экспонента ] .
 десятичная_экспонента   = ( "e" | "E" ) [ "+" | "-" ] десятичные_цифры .

hex_float_lit      = "0" ( "x" | "X" ) hex_mantissa  hex_exponent .
 hex_mantissa       = [ "_" ] hex_digits "." [ hex_digits ] |
                    [ "_" ] шестнадцатеричные_цифры |
                    "." hex_digits .
 hex_exponent       = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
0.
72.40
072.40 // == 72.40
2.71828
1.е+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 [ Перейти 1.13 ]

мнимые_числа = ( десятичные_числа | целые_числа | плавающие_числа ) "i" .

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

0я
0123i // == 123i для обратной совместимости
0o123i // == 0o123 * 1i == 83i
0xabci // == 0xabc * 1i == 2748i
0.я
2.71828i
1.е+0и
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-dieresis, 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  hex_digit .
 big_u_value       = `\` "U" hex_digit 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" | `\` | "'" | `"` ) .
'а'
'д'
'本'
'\т'
'\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 | interpretation_string_lit .
 raw_string_lit          = "`" { unicode_char | newline } "`" .
 interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc` // то же, что и "abc"
`\n
\n` // то же самое, что и "\\n\n\\n"
"\н"
"\"" // то же, что и `"`
"Привет, мир!\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примененных к определенным значениям или примененных к некоторым выражениям , и примененных к комплексной константе и примененных к числовым константам. Булевы значения истинности представлены предопределенными константами и . Предопределенный идентификатор iotacap обозначает целочисленную константу. lenrealimagcomplextruefalse

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

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

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

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

Нетипизированная константа имеет тип по умолчанию , который является типом, к которому константа неявно преобразуется в контекстах, где требуется типизированное значение, например, в кратком объявлении переменной , например, i := 0где нет явного типа. Тип по умолчанию нетипизированной константы — boolruneintfloat64complex128или string соответственно, в зависимости от того, является ли она логической, рунической, целочисленной, с плавающей точкой, комплексной или строковой константой.

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

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

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

Переменные 

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

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

Структурированные переменные типа array , slice и 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

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

Типы 

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

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

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

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

Булевы типы 

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

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

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

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 действительной и мнимой частями

байтовый псевдоним для uint8
псевдоним руны для int32

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

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

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

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

Типы строк 

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

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

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

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

ArrayType    = "[" ArrayLength "]" ElementType .
 ArrayLength = Выражение .
 ElementType = Тип .

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

[32]байт
[2*N] структура { x, y int32 }
[1000]*float64
[3][5]инт
[2][2][2]float64 // то же самое, что и [2]([2]([2]float64))

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

// недопустимые типы массивов
тип (
	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 в структуре
)

// допустимые типы массивов
тип (
	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всегда выделяет новый, скрытый массив, на который ссылается возвращаемое значение среза. То есть, выполнение

сделать([]T, длина, емкость)

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

сделать([]целое, 50, 100)
новый([100]int)[0:50]

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

Типы конструкций 

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

StructType     = "struct" "{" { FieldDecl ";" } "}" .
 FieldDecl      = ( Тип списка идентификаторов  | Встроенное поле ) [ Тег ] .
 Встроенное поле = [ "*" ] Имя типа [ Аргументы типа ] .
 Тег            = string_lit .
// Пустая структура.
структура {}

// Структура с 6 полями.
структура {
	x, y целые
	у float32
	_ float32 // заполнение
	А *[]целое
	F функция()
}

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

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

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

структура {
	T // конфликтует со встроенными полями *T и *PT
	*T // конфликтует со встроенным полем T и *PT
	*PT // конфликтует со встроенным полем T и *T
}

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

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

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

  • Если Sсодержит встроенное поле T, то наборы методовS и *Sоба включают продвигаемые методы с приемником T. Набор методов *Sтакже включает продвигаемые методы с приемником *T.
  • Если Sсодержит встроенное поле *T, то наборы методов Sи *Sоба включают продвигаемые методы с получателем Tили *T.

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

структура {
	x, y float64 "" // пустая строка тега похожа на отсутствующий тег
	имя строки "любая строка разрешена как тег"
	_ [4]byte "ceci не имеет поля структуры"
}

// Структура, соответствующая буферу протокола TimeStamp.
// Строки тегов определяют номера полей буфера протокола;
// они следуют соглашению, изложенному в пакете reflect.
структура {
	микросекунда uint64 `protobuf:"1"`
	serverIP6 uint64 `protobuf:"2"`
}

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

// недопустимые типы структур
тип (
	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 как компонент структуры T3 в массиве
)

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

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

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

Тип Указателя = "*" Базовый Тип .
 БазовыйТип     = Тип .
*Точка
*[4]целое

Типы функций 

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

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

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

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

функция()
функ(x целое) целое
func(a, _ int, z float32) bool
func(a, b int, z float32) (логическое)
func(строка префикса, значения ...целое)
func(a, b int, z float64, opt ...interface{}) (успех bool)
функция(целое, целое, float64) (float64, *[]целое)
функция(n int) функция(p *T)

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

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

InterfaceType   = "interface" "{" { InterfaceElem ";" } "}" .
 InterfaceElem   = MethodElem | TypeElem .
 MethodElem      = MethodName  Сигнатура .
 MethodName      = идентификатор .
 TypeElem        = TypeTerm { "|" TypeTerm } .
 TypeTerm        = Тип | UnderlyingType .
 UnderlyingType = "~" Тип .

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

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

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

// Простой файловый интерфейс.
интерфейс {
	Читать([]байт) (целое, ошибка)
	Запись([]байт) (целое, ошибка)
	Ошибка Close()
}

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

интерфейс {
	Строка() строка
	String() string // недопустимо: строка не уникальна
	_(x int) // недопустимо: метод должен иметь непустое имя
}

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

func (p T) Read(p []byte) (n int, err ошибка)
func (p T) Write(p []byte) (n int, err ошибка)
ошибка функции (p T) Close()

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

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

интерфейс{}

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

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

Тип интерфейса Locker {
	Замок()
	Разблокировать()
}

Если S1и S2также реализовать

функ (п Т) Блокировка() { … }
func (p T) Разблокировать() { … }

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

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

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

Тип интерфейса читателя {
	Read(p []byte) (n int, err ошибка)
	Ошибка Close()
}

Интерфейс Writer типа {
	Write(p []byte) (n int, err ошибка)
	Ошибка Close()
}

// Методы ReadWriter — Read, Write и Close.
тип интерфейса ReadWriter {
	Reader // включает методы Reader в набор методов ReadWriter
	Writer // включает методы Writer в набор методов ReadWriter
}

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

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

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

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

  • Набор типов пустого интерфейса представляет собой набор всех неинтерфейсных типов.
  • Набор типов непустого интерфейса представляет собой пересечение наборов типов его элементов интерфейса.
  • Набор типов спецификации метода — это набор всех неинтерфейсных типов, наборы методов которых включают этот метод.
  • Набор типов термина неинтерфейсного типа — это набор, состоящий только из этого типа.
  • Набор типов терма формы ~T — это набор всех типов, базовым типом которых является T.
  • Типовой набор объединения терминов представляет собой объединение типовых наборов терминов. t1|t2|…|tn

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

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

// Интерфейс, представляющий только тип int.
интерфейс {
	инт
}

// Интерфейс, представляющий все типы с базовым типом int.
интерфейс {
	~инт
}

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

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

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

тип MyInt int

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

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

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

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

интерфейс {
	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) // недопустимо

тип Плавающая структура {
	f Плавающий // недопустимый
}

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

// нелегально: Bad не может встраиваться
тип Плохой интерфейс {
	Плохой
}

// незаконно: Bad1 не может встраивать себя с помощью Bad2
тип интерфейса Bad1 {
	Плохо2
}
тип интерфейса Bad2 {
	Плохо1
}

// недопустимо: Bad3 не может встраивать объединение, содержащее Bad3
тип интерфейса Bad3 {
	~целое | ~строка | Bad3
}

// недопустимо: Bad4 не может встраивать массив, содержащий Bad4 как тип элемента
тип интерфейса Bad4 {
	[10]Плохо4
}

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

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

  • Tне является интерфейсом и является элементом типа set of I; или
  • T— это интерфейс, а набор типов T— это подмножество набора типов I.

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

Типы карт 

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

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

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

карта[строка]целое
map[*T]struct{ x, y float64 }
карта[строка]интерфейс{}

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

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

сделать(карта[строка]целое)
сделать(карта[строка]целое, 100)

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

Типы каналов 

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

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

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

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

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

chan<- chan int // то же самое, что chan<- (chan int)
chan<- <-chan int // то же самое, что chan<- (<-chan int)
<-chan <-chan int // то же, что и <-chan (<-chan int)
чан (<-чан цел.)

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

сделать(chan int, 100)

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

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

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

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

Базовые типы 

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

тип (
	А1 = строка
	А2 = А1
)

тип (
	струна B1
	В2 В1
	В3 []В1
	В4 В3
)

функция f[P любой](x P) { … }

Базовый тип stringA1A2B1, и B2— string. Базовый тип []B1B3, и B4— . []B1Базовый тип P.interface{}

Основные типы 

Каждый неинтерфейсный тип Tимеет базовый тип , который совпадает с базовым типом T.

Интерфейс Tимеет основной тип, если выполняется одно из следующих условий:

  1. Существует один тип U, который является базовым типом всех типов в наборе типовT ; или
  2. набор типов Tсодержит только типы каналов с идентичным типом элемента E, и все направленные каналы имеют одинаковое направление.

Никакие другие интерфейсы не имеют основного типа.

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

  1. тип U; или
  2. тип , chan Eесли Tсодержит только двунаправленные каналы, или тип chan<- Eили <-chan E в зависимости от направления присутствующих направленных каналов.

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

Примеры интерфейсов с основными типами:

тип Цельсия float32
тип Кельвин float32

интерфейс{ целое } // целое
интерфейс{ Цельсий|Кельвин } // float32
интерфейс{ ~канал целочисленный } // канал целочисленный
интерфейс{ ~канал целочисленный|~канал<- целочисленный } // канал<- целочисленный
интерфейс{ ~[]*данные; String() строка } // []*данные

Примеры интерфейсов без основных типов:

interface{} // нет единого базового типа
interface{ Celsius|float64 } // нет единого базового типа
interface{ chan int | chan<- string } // каналы имеют разные типы элементов
interface{ <-chan int | chan<- int } // направленные каналы имеют разные направления

Некоторые операции ( выражения среза и ) полагаются на немного более свободную форму основных типов, которые принимают байтовые срезы и строки. В частности, если есть ровно два типа appendиcopy[]byte , stringкоторые являются базовыми типами всех типов в наборе типов интерфейса T, то основной тип Tназывается bytestring.

Примеры интерфейсов с bytestringосновными типами:

interface{ int } // int (то же самое, что и обычный основной тип)
интерфейс{ []байт | строка } // байтовая строка
интерфейс{ ~[]байт | myString } // байтовая строка

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

Тип идентичности 

Два типа либо идентичны , либо различны .

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

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

Учитывая заявления

тип (
	A0 = []строка
	А1 = А0
	A2 = структура {a, b целое число}
	A3 = целое
	A4 = функция(A3, float64) *A0
	A5 = func(x int, _ float64) *[]строка

	Б0 А0
	B1 []строка
	B2 структура { a, b целое }
	B3 структура { a, c int }
	B4 функция(целое, float64) *B0
	B5 функция(x целое, y плавающее64) *A1

	С0 = В0
	D0[P1, P2 любой] структура { x P1; y P2 }
	E0 = D0[целое, строка]
)

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

A0, A1 и []строка
A2 и структура {a, b int}
A3 и инт
A4, func(int, float64) *[]string и A5

В0 и С0
D0[целое, строка] и E0
[]целое и []целое
структура {a, b *B5} и структура {a, b *B5}
func(x int, y float64) *[]string, func(int, float64) (результат *[]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может быть присвоено переменной типа (« можно присвоить ») , если выполняется одно из следующих условий: TxT

  • Vи Tидентичны.
  • Vи Tимеют идентичные базовые типы , но не являются параметрами типа и по крайней мере один из V или Tне является именованным типом .
  • Vи Tявляются типами каналов с идентичными типами элементов, Vявляется двунаправленным каналом и по крайней мере один из Vили Tне является именованным типом .
  • Tявляется типом интерфейса, но не параметром типа, и x реализует T .
  • x— это предварительно объявленный идентификатор nilT представляющий собой указатель, функцию, срез, карту, канал или тип интерфейса, но не параметр типа.
  • x— нетипизированная константа , представляемая значением типа T.

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

  • x— это предварительно объявленный идентификатор nilTпараметр типа, который xможет быть назначен каждому типу в Tнаборе типов.
  • Vне является именованным типом , Tявляется параметром типа и xможет быть назначен каждому типу в Tнаборе типов.
  • Vявляется параметром типа и Tне является именованным типом, и значения каждого типа в Vнаборе типов могут быть назначены T.

Репрезентативность 

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

  • xнаходится в наборе значений , определяемыхT .
  • Tявляется типом с плавающей точкой и xможет быть округлен до Tточности без переполнения. Округление использует правила округления до четного IEEE 754, но с отрицательным нулем IEEE, дополнительно упрощенным до нуля без знака. Обратите внимание, что постоянные значения никогда не приводят к отрицательному нулю IEEE, NaN или бесконечности.
  • Tявляется сложным типом, а компоненты и xмогут быть представлены значениями типа компонента ( или ). real(x)imag(x)Tfloat32float64

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

x T x можно представить значением T, поскольку

Байт 97 «a» находится в наборе значений байта
97 руна руна — это псевдоним для int32, а 97 входит в набор 32-битных целых чисел
"foo" строка "foo" находится в наборе строковых значений
1024 int16 1024 входит в набор 16-битных целых чисел
42.0 байт 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 не входит в набор булевых значений
строка 'a' 'a' — это руна, ее нет в наборе строковых значений
1024 байта 1024 не входит в набор беззнаковых 8-битных целых чисел
-1 uint16 -1 не входит в набор беззнаковых 16-битных целых чисел
1.1 int 1.1 не является целым числом
42i float32 (0 + 42i) не входит в набор значений float32
1e1000 float64 1e1000 переполняется до IEEE +Inf после округления

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

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

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

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

Блоки 

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

Блок = "{" StatementList "}" .
 StatementList = { Statement ";" } .

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

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

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

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

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

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

Объявление    = ConstDecl | TypeDecl | VarDecl .
 TopLevelDecl   = Объявление | FunctionDecl | MethodDecl .

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

Go имеет лексическую область видимости с помощью блоков :

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

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

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

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

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

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

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

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

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

Типы:
	любой логический байт, сопоставимый
	complex64 complex128 ошибка float32 float64
	int int8 int16 int32 int64 руническая строка
	uint uint8 uint16 uint32 uint64 uintptr

Константы:
	правда ложь йота

Нулевое значение:
	ноль

Функции:
	добавить cap очистить закрыть сложный копировать удалить imag len
	сделать max min новый паника печать println реальный восстановить

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

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

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

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

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

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

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

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

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

IdentifierList = идентификатор { "," идентификатор } .
 ExpressionList = Выражение { "," Выражение } .

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

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

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

константа (
	Воскресенье = йота
	Понедельник
	Вторник
	Среда
	Четверг
	Пятница
	Partyday
	numberOfDays // эта константа не экспортируется
)

Йота 

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

константа (
	c0 = йота // c0 == 0
	c1 = йота // c1 == 1
	c2 = йота // c2 == 2
)

константа (
	a = 1 << йота // a == 1 (йота == 0)
	b = 1 << йота // b == 2 (йота == 1)
	c = 3 // c == 3 (йота == 2, не используется)
	d = 1 << йота // d == 8 (йота == 3)
)

константа (
	u = iota * 42 // u == 0 (нетипизированная целочисленная константа)
	v float64 = йота * 42 // v == 42.0 (константа float64)
	w = йота * 42 // w == 84 (нетипизированная целочисленная константа)
)

константа x = йота // x == 0
константа y = йота // y == 0

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

константа (
	бит0, маска0 = 1 << йота, 1<<йота - 1 // бит0 == 1, маска0 == 0 (йота == 0)
	бит1, маска1 // бит1 == 2, маска1 == 1 (йота == 1)
	_, _ // (йота == 2, не используется)
	бит3, маска3 // бит3 == 8, маска3 == 7 (йота == 3)
)

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

Тип декларации 

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

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

Декларации псевдонимов 

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

AliasDecl = идентификатор "=" Тип .

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

тип (
	nodeList = []*Node // nodeList и []*Node — идентичные типы
	Polar = polar // Polar и polar обозначают идентичные типы
)

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

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

TypeDef = идентификатор [ ПараметрыТипа ] Тип .

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

тип (
	Point struct{ x, y float64 } // Point и struct{ x, y float64 } — это разные типы
	полярная точка // полярная и точка обозначают разные типы
)

тип структуры TreeNode {
	левый, правый *TreeNode
	ценность любого
}

тип Блок интерфейса {
	BlockSize() целое число
	Шифровать(источник, dst []байт)
	Расшифровать(src, dst []byte)
}

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

// Мьютекс — это тип данных с двумя методами: Lock и Unlock.
тип Mutex struct { /* Поля Mutex */ }
func (m *Mutex) Lock() { /* Реализация блокировки */ }
func (m *Mutex) Unlock() { /* Реализация разблокировки */ }

// NewMutex имеет тот же состав, что и Mutex, но его набор методов пуст.
тип NewMutex Мьютекс

// Набор методов базового типа PtrMutex *Mutex остается неизменным,
// но набор методов PtrMutex пуст.
тип PtrMutex *Mutex

// Набор методов *PrintableMutex содержит методы
// Блокировка и разблокировка привязаны к встроенному полю Mutex.
тип PrintableMutex структура {
	Мьютекс
}

// MyBlock — это тип интерфейса, имеющий тот же набор методов, что и Block.
тип MyBlock Блок

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

тип TimeZone int

константа (
	EST TimeZone = -(5 + йота)
	КСТ
	МСТ
	Тихоокеанское стандартное время
)

функция (tz TimeZone) String() строка {
	return fmt.Sprintf("GMT%+dh", tz)
}

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

тип Список[T любой] структура {
	следующий *Список[T]
	значение Т
}

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

тип T[P любой] P // недопустимо: P — параметр типа

функция f[T любая]() {
	тип LT // недопустимо: T — параметр типа, объявленный охватывающей функцией
}

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

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

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

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

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

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

[P любой]
[S-интерфейс{ ~[]байт|строка}]
[С ~[]Э, Э любой]
[P Ограничение[int]]
[_ любой]

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

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

тип Т[П *С] …
тип Т[П (С)] …
тип Т[П *С|Q] …
…

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

тип T[P интерфейс{*C}] …
тип Т[П *С,] …

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

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

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

type T6[P int] struct{ f *T6[P] } // ok: ссылка на T6 отсутствует в списке параметров типа

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

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

ОграничениеТипа = ЭлементТипа .

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

[T []P] // = [T интерфейс{[]P}]
[T ~целое] // = [T интерфейс{~целое}]
[T целое|строка] // = [T интерфейс{целое|строка}]
Ограничение типа ~int // неверно: ~int отсутствует в списке параметров типа

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

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

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

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

Удовлетворение ограничения типа 

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

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

тип аргумент тип ограничение // удовлетворение ограничения

int interface{ ~int } // удовлетворено: int реализует interface{ ~int }
строка сопоставима // удовлетворено: строка реализует сопоставимость (строка строго сопоставима)
[]байт сопоставим // не удовлетворено: срезы не сопоставимы
любой интерфейс { сопоставимый; int } // не удовлетворен: any не реализует интерфейс { int }
любой сопоставимый // удовлетворен: любой сопоставим и реализует базовый интерфейс любой
struct{f any} сравнима // удовлетворена: struct{f any} сравнима и реализует базовый интерфейс any
любой интерфейс { сопоставимый; m() } // не удовлетворен: любой не реализует базовый интерфейс interface{ m() }
interface{ m() } interface{ Compare; m() } // удовлетворено: interface{ m() } сопоставим и реализует базовый интерфейс interface{ m() }

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

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

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

VarDecl      = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
 VarSpec      = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int
вар U, V, W float64
вар к = 0
переменная x, y float32 = -1, -2
вар (
	я инт
	u, v, s = 2,0, 3,0, "бар"
)
var re, im = complexSqrt(-1)
var _, found = entry[name] // поиск по карте; интересует только «found»

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

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

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

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

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

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

ShortVarDecl = СписокИдентификаторов ":=" СписокВыражений .

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

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

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

поле1, смещение := nextField(str, 0)
field2, offset := nextField(str, offset) // переопределяет offset
x, y, x := 1, 2, 3 // неправильно: x повторяется слева от :=

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

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

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

FunctionDecl = "func" ИмяФункции [ ПараметрыТипа ] Подпись [ ТелоФункции ].
 ИмяФункции = идентификатор .
 ТелоФункции = Блок .

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

func IndexRune(строка s, руна r) int {
	для i, c := диапазон s {
		если с == г {
			вернуть я
		}
	}
	// недействительно: отсутствует оператор возврата
}

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

функция min[T ~int|~float64](x, y T) T {
	если х < у {
		вернуть х
	}
	вернуть y
}

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

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

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

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

MethodDecl = "func" Получатель  ИмяМетода  Сигнатура [ ТелоФункции ].
 Получатель    = Параметры .

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

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

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

Учитывая определенный тип, Pointобъявления

функция (p *Point) Длина() float64 {
	вернуть math.Sqrt(px * px + py * py)
}

func (p *Point) Масштаб(фактор float64) {
	пикс *= фактор
	py *= фактор
}

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

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

тип Пара[A, B любой] структура {
	а А
	б Б
}

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

Выражения 

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

Операнды 

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

Операнд      = Литерал | ИмяОперанда [ TypeArgs ] | "(" Выражение ")" .
 Литерал      = БазовыйЛит | СоставнойЛит | ФункцияЛит .
 БазовыйЛит     = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
 ИмяОперанда = идентификатор | КвалифицированныйИдент .

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

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

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

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

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

QualifiedIdent = PackageName "." идентификатор .

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

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 T должен быть типом структуры, массива, среза или карты (синтаксис обеспечивает это ограничение, за исключением случаев, когда тип задан как TypeName). Типы элементов и ключей должны быть назначаемыми соответствующим типам полей, элементов и ключей типа T; дополнительное преобразование не выполняется. Ключ интерпретируется как имя поля для литералов структуры, индекс для литералов массива и среза и ключ для литералов карты. Для литералов карты все элементы должны иметь ключ. Указывать несколько элементов с одинаковым именем поля или постоянным значением ключа является ошибкой. Для непостоянных ключей карты см. раздел о порядке вычисления .

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

  • Ключ должен быть именем поля, объявленным в типе структуры.
  • Список элементов, не содержащий ключей, должен содержать элемент для каждого поля структуры в том порядке, в котором поля объявлены.
  • Если какой-либо элемент имеет ключ, то каждый элемент должен иметь ключ.
  • Список элементов, содержащий ключи, не обязательно должен иметь элемент для каждого поля структуры. Пропущенные поля получают нулевое значение для этого поля.
  • Литерал может не содержать список элементов; такой литерал оценивается как нулевое значение для своего типа.
  • Указание элемента для неэкспортированного поля структуры, принадлежащей другому пакету, является ошибкой.

Учитывая заявления

тип Point3D структура { x, y, z float64 }
тип Линия структура { p, q Point3D }

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

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

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

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

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

указатель var *Point3D = &Point3D{y: 1000}

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

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

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

буфер := [10]строка{} // длина(буфер) == 10
intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6
days := [...]string{"Сб", "Вс"} // len(days) == 2

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

[]Т{x1, x2, … xn}

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

tmp := [n]T{x1, x2, … xn}
тмп[0 : н]

В составном литерале типа массива, среза или карты 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}}
[][]Точка{{{0, 1}, {1, 2}}} // то же самое, что и [][]Точка{[]Точка{Точка{0, 1}, Точка{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"}

тип 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», и составной литерал не заключен в круглые, квадратные или фигурные скобки. В этом редком случае открывающая фигурная скобка литерала ошибочно анализируется как вводная для блока операторов. Чтобы разрешить неоднозначность, составной литерал должен появляться в круглых скобках.

если х == (Т{а,б,в}[я]) { … }
если (x == T{a,b,c}[i]) { … }

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

// список простых чисел
простые числа := []целое число{2, 3, 5, 7, 9, 2147483647}

// vowels[ch] истинно, если ch — гласная
гласные := [128]bool{'a': правда, 'e': правда, 'i': правда, 'o': правда, 'u': правда, 'y': правда}

// массив [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
фильтр := [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" Подпись  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 =
	 Операнд |
	 Преобразование |
	 MethodExpr |
	 Селектор PrimaryExpr  |
	 Индекс PrimaryExpr |
	 Срез PrimaryExpr |
	 Утверждение типа PrimaryExpr |
	 Аргументы PrimaryExpr .    

Селектор        = "." идентификатор .
 Индекс           = "[" Выражение [ "," ] "]" .
 Срез           = "[" [ Выражение ] ":" [ Выражение ] "]" |
                 "[" [ Выражение ] ":" Выражение ":" Выражение "]" .
 TypeAssertion   = "." "(" Тип ")" .
 Arguments       = "(" [ ( ExpressionList | Тип [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
х
2
(с + ".txt")
f(3.1415, правда)
Точка{1, 2}
м["фу"]
с[я : j + 1]
объект.цвет
fp[i].x()

Селекторы 

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

хф

обозначает поле или метод 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 нет метода с именем , выражение селектора является недопустимым. fI
  3. В качестве исключения, если тип xявляется определенным типом указателя и (*x).fявляется допустимым выражением селектора, обозначающим поле (но не метод), x.fявляется сокращением для (*x).f.
  4. Во всех остальных случаях x.fэто незаконно.
  5. Если xимеет тип указателя и имеет значение nilи x.fобозначает поле структуры, то присвоение или вычисление x.f вызывает панику во время выполнения .
  6. Если xимеет тип интерфейса и имеет значение nilвызов или оценка метода x.f приводит к панике во время выполнения .

Например, учитывая заявления:

тип T0 структура {
	х инт
}

функция (*T0) M0()

тип T1 структура {
	у инт
}

функция (T1) M1()

тип T2 структура {
	z инт
	Т1
	*Т0
}

функция (*T2) M2()

тип Q *T2

var t T2 // с t.T0 != nil
var p *T2 // где p != nil и (*p).T0 != nil
вар q Q = p

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

тц // тц
ty // т.T1.y
tx // (*t.T0).x

пз // (*п).з
py // (*p).T1.y
пикс // (*(*p).T0).x

qx // (*(*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находится в наборе методов типа TT.Mто это функция, которая может быть вызвана как обычная функция с теми же аргументами, что и Mс префиксом в виде дополнительного аргумента, который является получателем метода.

MethodExpr     = ReceiverType "." ИмяМетода .
 ТипПриемника   = Тип .

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

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

вар т Т

Выражение

Т.Мв

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

func(tv T, a int) int

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

т.Мв(7)
Т.Мв(т, 7)
(Т).Мв(т, 7)
f1 := T.Mv; f1(t, 7)
f2 := (T).Mv; f2(t, 7)

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

(*Т).Мп

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

функция(tp *T, f float32) float32

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

(*Т).Мв

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

func(tv *T, a int) int

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

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

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

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

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

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

тип S структура { *T }
тип T инт
функ (t T) M() { print(t) }

т := новый(Т)
с := С{Т: т}
f := tM // приемник *t оценивается и сохраняется в f
g := sM // приемник *(sT) оценивается и сохраняется в g
*t = 42 // не влияет на сохраненные приемники в f и g

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

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

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

вар т Т
вар пт *Т
функция makeT() T

Выражение

т.Мв

выдает значение функции типа

функ(целое) целое

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

т.Мв(7)
f := t.Mv; f(7)

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

пт.МП

выдает значение функции типа

функция(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 интерфейс { M(int) } = myVal
f := iM; f(7) // как iM(7)

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

Первичное выражение формы

а[х]

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

Если aэто не параметр ни карты, ни типа:

  • индекс xдолжен быть нетипизированной константой или его основной тип должен быть целым числом
  • константный индекс должен быть неотрицательным и представляться значением типаint
  • константный индекс, который не типизирован, имеет типint
  • индекс xнаходится в диапазоне , если 0 <= x < len(a), в противном случае он находится вне диапазона

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

  • константный индекс должен находиться в диапазоне
  • если xво время выполнения выходит за пределы диапазона, возникает паника во время выполнения
  • a[x]— это элемент массива по индексу x, а тип — a[x]это тип элементаA

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

  • a[x]это сокращение от(*a)[x]

Для aтипа ломтика S :

  • если xво время выполнения выходит за пределы диапазона, возникает паника во время выполнения
  • a[x]является элементом среза по индексу x, а тип a[x]является типом элементаS

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

  • константный индекс должен быть в диапазоне , если строка aтакже является константой
  • если xво время выполнения выходит за пределы диапазона, возникает паника во время выполнения
  • a[x]— это непостоянное значение байта по индексу x, а тип a[x]byte
  • a[x]не может быть назначен

Для aтипа карты M :

  • xтип должен быть назначен ключевому типуM
  • если карта содержит запись с ключом xa[x]является элементом карты с ключом x , а тип a[x]является типом элементаM
  • если карта содержит nilили не содержит такую ​​запись, a[x]является ли нулевым значением для типа элементаM

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

  • Выражение индекса a[x]должно быть допустимым для значений всех типов в Pнаборе типов.
  • Типы элементов всех типов в Pнаборе типов должны быть идентичны. В этом контексте тип элемента строкового типа — byte.
  • Если в наборе типов есть тип карты P, все типы в этом наборе типов должны быть типами карты, а соответствующие ключевые типы должны быть идентичными.
  • a[x]— это элемент массива, среза или строки по индексу xили элемент карты с ключом xаргумента типа, Pс которым создается экземпляр, а тип a[x]— это тип (идентичных) типов элементов.
  • a[x]не может быть назначен, если Pнабор типов включает строковые типы.

В противном случае a[x]это незаконно.

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

v, ок = а[х]
v, ок := a[x]
вар v, ок = а[х]

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

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

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

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

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

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

а[низкий : высокий]

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

а := [5]целое{1, 2, 3, 4, 5}
с := а[1:4]

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

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

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

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. В противном случае, если результат является срезом, он разделяет свой базовый массив с операндом.

вар а [10]целое
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 []целое
s3 := s[:0] // s3 == ноль

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

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

а[низкий : высокий : максимальный]

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

а := [5]целое{1, 2, 3, 4, 5}
т := а[1:3:5]

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

т[0] == 2
т[1] == 3

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

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

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

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

х.(Т)

утверждает, что xэто не так nil и что значение, хранящееся в, xимеет тип T. Нотация x.(T)называется утверждением типа .

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

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

var x interface{} = 7 // x имеет динамический тип int и значение 7
i := x.(int) // i имеет тип int и значение 7

интерфейс типа I { m() }

функция f(y I) {
	s := y.(string) // неверно: string не реализует I (отсутствует метод m)
	r := y.(io.Reader) // r имеет тип io.Reader, а динамический тип y должен реализовывать как I, так и io.Reader
	…
}

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

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

выдает дополнительное нетипизированное логическое значение. Значение okравно , true если утверждение выполняется. В противном случае оно равно false, а значение vравно нулю для типа T. В этом случае паника во время выполнения не возникает.

Звонки 

Дано выражение fс основным типом F функции type ,

f(a1, a2, … an)

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

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

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

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

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

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

func Split(s string, pos int) (string, string) {
	вернуть s[0:поз], s[поз:]
}

функция Join(s, t строка) строка {
	вернуть с + т
}

если Join(Split(значение, len(значение)/2)) != значение {
	log.Panic("тест не пройден")
}

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

вар п Точка
стр.Масштаб(3.5)

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

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

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

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

func Greeting(префиксная строка, кто ...строка)
Приветствие("никто")
Приветствие("привет:", "Джо", "Анна", "Эйлин")

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

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

Учитывая срез sи призыв

s := []string{"Джеймс", "Жасмин"}
Приветствие("до свидания:", с...)

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

Инстанцирования 

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

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

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

список параметров типа аргументы типа после подстановки

[P any] int int удовлетворяет любому
[S ~[]E, E любой] []int, int []int удовлетворяет ~[]int, int удовлетворяет любой
[P io.Writer] строка недопустима: строка не удовлетворяет io.Writer
[P сопоставимый] любой любой удовлетворяет (но не реализует) сопоставимый

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

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

// 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

тип sumFunc func(x... строка) строка
var f sumFunc = sum // то же, что var f sumFunc = sum[string]
f = сумма // то же самое, что f = сумма[строка]

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

функция применить[S ~[]E, E любой](s S, f функция(E) E) S { … }

f0 := apply[] // недопустимо: список аргументов типа не может быть пустым
f1 := apply[[]int] // аргумент типа для S указан явно, аргумент типа для E выведен
f2 := apply[[]string, string] // оба аргумента типа указаны явно

var байты []байт
r := apply(bytes, func(byte) byte { … }) // оба аргумента типа выведены из аргументов функции

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

Вывод типа 

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

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

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

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

// dedup возвращает копию фрагмента аргумента, из которой удалены все повторяющиеся записи.
функция дедупликации[S ~[]E, E сопоставимо](S) S { … }

тип Срез []целое
var s Срез
s = dedup(s) // то же самое, что s = dedup[Slice, int](s)

переменная sтипа Sliceдолжна быть присваиваемой типу параметра функции S, чтобы программа была допустимой. Чтобы уменьшить сложность, вывод типа игнорирует направленность присваиваний, поэтому отношение типа между Sliceи Sможет быть выражено через (симметричное) уравнение типа (или, если на то пошло), где in указывает, что типы LHS и RHS должны соответствовать правилам присваиваемости (подробнее см. в разделе об унификации типов ). Аналогично, параметр типа должен удовлетворять своему ограничению . Это можно выразить как , где означает « удовлетворяет ограничению ». Эти наблюдения приводят к набору из двух уравнений Slice ≡A SS ≡A SliceAAS~[]ES ≡C ~[]EX ≡C YXY

	Срез ≡ A S (1)
	С ≡ С ~[]Э (2)

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

	S ➞ Слайс
	E ➞ цел.

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

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

  • Для вызова функции , где или аргумент функции является обобщенной функцией: Каждая пара соответствующих аргументов функции и параметров, где не является нетипизированной константой, дает уравнение . Если является нетипизированной константой , а является параметром связанного типа , пара собирается отдельно от уравнений типа. f(a0, a1, …)fai
    (ai, pi)aitypeof(pi) ≡A typeof(ai)
    aicjtypeof(pi)Pk(cj, Pk)

  • Для присвоения v = fуниверсальной функции f(неуниверсальной) переменной vтипа функции: .
    typeof(v) ≡A typeof(f)

  • Для оператора возврата, return …, f, … где f— это универсальная функция, возвращаемая в качестве результата в (неуниверсальную) результирующую переменную rтипа функции: .
    typeof(r) ≡A typeof(f)

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

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

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

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

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

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

	П к ➞ А к

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

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

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

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

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

	[10]структура{ элемент P, список []P } ≡ A [10]структура{ элемент строка; список []строка }

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

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

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

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

  • Если Cимеет базовый тип core(C) и Pимеет известный аргумент типа Acore(C)и Aдолжен унифицироваться слабо. Если Pне имеет известного аргумента типа и Cсодержит ровно один термин типа T , который не является базовым типом (тильда), унификация добавляет отображение P ➞ Tк карте.
  • Если Cне имеет основного типа и Pимеет известный аргумент типа AAто должны быть все методы C, если таковые имеются, и соответствующие типы методов должны точно унифицироваться.

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

Операторы 

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

Выражение = UnaryExpr | Выражение  binary_op  Выражение .
 UnaryExpr   = PrimaryExpr | unary_op  UnaryExpr .

двоичный_оп   = "||" | "&&" | rel_op | добавить_оп | мул_оп ​​.
 rel_op      = "==" | "!=" | "<" | "<=" | ">" | ">=" .
 add_op      = "+" | "-" | "|" | "^" .
 mul_op      = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .

unary_op    = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .

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

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

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

var a [1024]байт
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; к == 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<<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<<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<<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.

+х // х
42 + а - б // (42 + а) - б
23 + 3*x[i] // 23 + (3 * x[i])
х <= f() // х <= f()
^а >> б // (^а) >> б
ф() || г() // ф() || г()
x == y+1 && <-chanInt > 0 // (x == (y+1)) && ((<-chanInt) > 0)

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

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

+ сумма целых чисел, чисел с плавающей точкой, комплексных значений, строк
- разность целых чисел, чисел с плавающей точкой, комплексных значений
* произведение целых чисел, чисел с плавающей точкой, комплексных значений
/ частное целых чисел, чисел с плавающей точкой, комплексных значений
% остаток целых чисел

& побитовое И целые числа
| побитовое ИЛИ целых чисел
^ побитовое XOR целых чисел
&^ бит очистить (И НЕ) целые числа

<< сдвиг влево целое число << целое число >= 0
>> сдвиг вправо целое число >> целое число >= 0

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

функция dotProduct[F ~float32|~float64](v1, v2 []F) F {
	вар s F
	для i, x := диапазон v1 {
		у := v2[i]
		с += х * у
	}
	вернуть с
}

произведение 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 / уx % у
 5 3 1 2
-5 3 -1 -2
 5 -3 -1 2
-5 -3 1 -2

Единственным исключением из этого правила является то, что если делимое xявляется самым отрицательным значением для типа int x, то частное q = x / -1равно x(и ) из-за переполнения целого числаr = 0 в дополнительном коде :

                         х, д
int8 -128
int16 -32768
int32 -2147483648
int64 -9223372036854775808

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

хх / 4 х % 4 х >> 2 х и 3
 11 2 3 2 3
-11 -2 -3 -3 1

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

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

+х это 0 + х
-x отрицание равно 0 - x
^x побитовое дополнение — это m ^ x, где m = «все биты установлены в 1» для беззнакового x
                                      и m = -1 для знакового x

Целочисленное переполнение 

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

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

Операторы с плавающей точкой 

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

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

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

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

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

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

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

с := "привет" + строка(с)
s += "и до свидания"

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

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

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

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

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

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

  • Булевы типы сравнимы. Два булевых значения равны, если они являются либо обоими, trueлибо обоими false.
  • Целочисленные типы сравнимы и упорядочены. Два целочисленных значения сравниваются обычным способом.
  • Типы с плавающей точкой сравнимы и упорядочены. Два значения с плавающей точкой сравниваются, как определено стандартом IEEE 754.
  • Комплексные типы сравнимы. Два комплексных значения uи vравны, если оба real(u) == real(v)и imag(u) == imag(v).
  • Строковые типы сравнимы и упорядочены. Два строковых значения сравниваются лексически побайтово.
  • Типы указателей сравнимы. Два значения указателя равны, если они указывают на одну и ту же переменную или если оба имеют значение nil. Указатели на различные переменные нулевого размера могут быть равными или не равными.
  • Типы каналов сопоставимы. Два значения каналов равны, если они были созданы одним и тем же вызовом make или если оба имеют значение nil.
  • Типы интерфейсов, которые не являются параметрами типа, сравнимы. Два значения интерфейса равны, если они имеют идентичные динамические типы и равные динамические значения или если оба имеют значение nil.
  • Значение xнеинтерфейсного типа Xи значение tинтерфейсного типа Tможно сравнивать, если тип Xсопоставим и X реализует T . Они равны, если tдинамический тип идентичен X , а tдинамическое значение равно x.
  • Типы структур сравнимы, если все типы их полей сравнимы. Два значения структур равны, если их соответствующие непустые значения полей равны. Поля сравниваются в исходном порядке, и сравнение останавливается, как только два значения полей различаются (или все поля были сравнены).
  • Типы массивов сравнимы, если сравнимы типы их элементов массива. Два значения массива равны, если равны соответствующие им значения элементов. Элементы сравниваются в порядке возрастания индекса, и сравнение останавливается, как только два значения элементов различаются (или все элементы были сравнены).
  • Параметры типа сопоставимы, если они строго сопоставимы (см. ниже).

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

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

const c = 3 < 4 // c — нетипизированная булева константа true

тип MyBool bool
переменная x, y целое
вар (
	// Результат сравнения — нетипизированное логическое значение.
	// Применяются обычные правила назначения.
	b3 = x == y // b3 имеет тип bool
	b4 bool = x == y // b4 имеет тип bool
	b5 MyBool = x == y // b5 имеет тип MyBool
)

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

  • Типы данных «Булев», «Числовой», «Строка», «Указатель» и «Канал» строго сопоставимы.
  • Типы структур строго сопоставимы, если все типы их полей строго сопоставимы.
  • Типы массивов строго сопоставимы, если типы их элементов массива строго сопоставимы.
  • Параметры типов строго сопоставимы, если все типы в их наборе типов строго сопоставимы.

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

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

&& условное И p && q — это «если p, то q, иначе ложь»
|| условное ИЛИ p || q — «если p, то истинно, иначе q»
! НЕ !p это "не p"

Операторы адресов 

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

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

&х
&а[ф(2)]
&Точка{2, 3}
*р
*пф(х)

var x *int = ноль
*x // вызывает панику во время выполнения
&*x // вызывает панику во время выполнения

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

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

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

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

х, ок = <-ч
х, ок := <-ч
вар х, ок = <-ч
вар х, ок Т = <-ч

выдает дополнительный нетипизированный логический результат, сообщающий, была ли связь успешной. Значение равно, 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 (однозначно)

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

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

uint(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 не является логическим, числовым или строковым типом
int(1.2) // неверно: 1.2 не может быть представлено как int
string(65.0) // неверно: 65.0 не является целочисленной константой

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

функция f[P ~float32|~float64]() {
	… Р(1,1) …
}

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

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

  • xможет быть назначенT .
  • игнорируя структурные теги (см. ниже), xтипы и Tне являются параметрами типа , но имеют идентичные базовые типы .
  • игнорируя структурные теги (см. ниже), xтипы и Tявляются типами указателей, которые не являются именованными типами , а их базовые типы указателей не являются параметрами типа, но имеют идентичные базовые типы.
  • xТип и Tоба являются целочисленными или типами с плавающей точкой.
  • xтип и Tоба являются сложными типами.
  • xпредставляет собой целое число или фрагмент байтов или рун и Tимеет строковый тип.
  • xпредставляет собой строку и Tпредставляет собой фрагмент байтов или рун.
  • xявляется срезом, Tявляется массивом [ Go 1.20 ] или указателем на массив [ Go 1.17 ], а типы среза и массива имеют идентичные типы элементов.

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

  • Оба Vявляются Tпараметрами типа, и значение каждого типа в Vнаборе типов может быть преобразовано в каждый тип в Tнаборе типов.
  • Только Vпараметр типа и значение каждого типа в Vнаборе типов могут быть преобразованы в T.
  • Only T— это параметр типа, который xможет быть преобразован в любой тип из Tнабора типов.

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

тип Person структура {
	Имя строки
	Адрес *структура {
		Уличная строка
		Городская строка
	}
}

var данные *структура {
	Имя строки `json:"name"`
	Адрес *структура {
		Строка улицы `json:"улица"`
		Строка города `json:"city"`
	} `json:"адрес"`
}

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'}) // "привет"
    строка([]байт{}) // ""
    строка([]байт(ноль)) // ""
    
    тип байты []байт
    string(bytes{'h', 'e', ​​'l', 'l', '\xc3', '\xb8'}) // "привет"
    
    тип myByte байт
    string([]myByte{'w', 'o', 'r', 'l', 'd', '!'}) // "мир!"
    myString([]myByte{'\xf0', '\x9f', '\x8c', '\x8d'}) // ""
    
  2. Преобразование фрагмента рун в строковый тип дает строку, которая представляет собой конкатенацию отдельных значений рун, преобразованных в строки.
    string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
    строка([]руна{}) // ""
    строка([]руна(ноль)) // ""
    
    тип руны []руна
    string(runes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
    
    тип myRune руна
    строка([]мояРуна{0x266b, 0x266c}) // "\u266b\u266c" == "♫♬"
    мояСтрока([]мояРуна{0x1f30e}) // "\U0001f30e" == ""
    
  3. Преобразование значения строкового типа в срез типа байтов дает ненулевой срез, последовательными элементами которого являются байты строки.
    []byte("hellø") // []byte{'h', 'e', ​​'l', 'l', '\xc3', '\xb8'}
    []байт("") // []байт{}
    
    байты("hellø") // []байт{'h', 'e', ​​'l', 'l', '\xc3', '\xb8'}
    
    []myByte("мир!") // []myByte{'w', 'o', 'r', 'l', 'd', '!'}
    []myByte(myString("")) // []myByte{'\xf0', '\x9f', '\x8c', '\x8f'}
    
  4. Преобразование значения строкового типа в срез рунического типа дает срез, содержащий отдельные кодовые точки Unicode строки.
    []rune(myString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4}
    []руна("") // []руна{}
    
    руны("白鵬翔") // []руна{0x767d, 0x9d6c, 0x7fd4}
    
    []мояРуна("♫♬") // []мояРуна{0x266b, 0x266c}
    []мояРуна(мояСтрока("")) // []мояРуна{0x1f310}
    
  5. Наконец, по историческим причинам целочисленное значение может быть преобразовано в строковый тип. Эта форма преобразования дает строку, содержащую (возможно, многобайтовое) представление UTF-8 кодовой точки Unicode с заданным целочисленным значением. Значения за пределами диапазона допустимых кодовых точек Unicode преобразуются в "\uFFFD".
    строка('а') // "а"
    строка(65) // "А"
    строка('\xf8') // "\u00f8" == "ø" == "\xc3\xb8"
    строка(-1) // "\ufffd" == "\xef\xbf\xbd"
    
    тип myString строка
    myString('\u65e5') // "\u65e5" == "日" == "\xe6\x97\xa5"
    

    Примечание: эта форма преобразования может быть в конечном итоге удалена из языка. go vetИнструмент помечает определенные преобразования целых чисел в строки как потенциальные ошибки. Вместо этого следует использовать библиотечные функции, такие как utf8.AppendRuneили .utf8.EncodeRune

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

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

s := make([]байт, 2, 4)

a0 := [0]байт(ы)
a1 := [1]byte(s[1:]) // a1[0] == s[1]
a2 := [2]байт(ы) // a2[0] == s[0]
a4 := [4]byte(s) // паника: len([4]byte) > len(s)

s0 := (*[0]байт)(с) // s0 != ноль
s1 := (*[1]байт)(s[1:]) // &s1[0] == &s[1]
s2 := (*[2]байт)(с) // &s2[0] == &s[0]
s4 := (*[4]byte)(s) // паника: len([4]byte) > len(s)

var t []строка
t0 := [0]string(t) // ok для нулевого среза t
t1 := (*[0]строка)(t) // t1 == ноль
t2 := (*[1]string)(t) // паника: len([1]string) > len(t)

u := make([]байт, 0)
u0 := (*[0]байт)(u) // u0 != ноль

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

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

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

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

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

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' (нетипизированная руническая константа)
const l = "hi" // l == "hi" (нетипизированная строковая константа)
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 Огромный = 1 << 100 // Огромный == 1267650600228229401496703205376 (нетипизированная целочисленная константа)
const Четыре int8 = Огромный >> 98 // Четыре == 4 (тип int8)

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

3.14 / 0.0 // неправильно: деление на ноль

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

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

Маска, используемая унарным оператором побитового дополнения, ^соответствует правилу для неконстант: маска состоит из единиц для беззнаковых констант и -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()], ок = g(z || h(), i()+x[j()], <-c), k()

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

а := 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}: порядок оценки между двумя назначениями карты не указан
n := map[int]int{a: f()} // n может быть {2: 3} или {3: 3}: порядок оценки между ключом и значением не указан

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

переменная а, б, в = f() + v(), g(), sqr(u()) + v()

функция f() int { return c }
func g() int { return a }
функция sqr(x int) int { return x*x }

// функции u и v независимы от всех других переменных и функций

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

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

Заявления 

Заявления контролируют исполнение.

Оператор =
	 Объявление | LabeledStmt | SimpleStmt |
	 GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt |
	 FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt |
	 DeferStmt .

SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Назначение | ShortVarDecl .

Завершающие заявления 

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

  1. Оператор «возврата» или «перехода» .
  2. Вызов встроенной функции panic.
  3. Блок , в котором список операторов заканчивается завершающим оператором.
  4. Утверждение «если», в котором:
    • присутствует ветвь «else», и
    • обе ветви являются завершающими операторами.
  5. Выражение «for», в котором:
    • нет операторов «break», ссылающихся на оператор «for», и
    • условие цикла отсутствует, и
    • В операторе «for» не используется предложение диапазона.
  6. «Переключающее» утверждение , в котором:
    • нет операторов «break», ссылающихся на оператор «switch»,
    • есть случай по умолчанию, и
    • списки операторов в каждом случае, включая оператор по умолчанию, заканчиваются завершающим оператором или, возможно, оператором с меткой «fallthrough» .
  7. «Выборочное» утверждение , в котором:
    • нет операторов «break», ссылающихся на оператор «select», и
    • списки операторов в каждом случае, включая оператор по умолчанию, если он присутствует, заканчиваются завершающим оператором.
  8. Помеченный оператор, маркирующий завершающий оператор.

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

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

Пустые заявления 

Пустое утверждение ничего не делает.

ПустойСтмт = .

Помеченные утверждения 

Помеченное утверждение может быть целью утверждения gotobreakили continue.

LabeledStmt = Метка ":" Оператор .
 Метка        = идентификатор .
Ошибка: log.Panic("произошла ошибка")

Выражения 

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

ExpressionStmt = Выражение .

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

добавить кап сложное изображение len сделать новый реальный
небезопасный.Добавить небезопасный.Выровнять небезопасный.Смещение небезопасного.Размер небезопасного.Срез небезопасный.Данные среза небезопасный.Строка небезопасный.Данные строки
ч(х+у)
f.Закрыть()
<-ч
(<-ч)
len("foo") // недопустимо, если len — встроенная функция

Отправить заявления 

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

SendStmt = Канал "<-" Выражение .
 Канал   = Выражение .

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

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

Заявления IncDec 

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

IncDecStmt = Выражение ( "++" | "--" ) .

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

IncDec заявление Назначение
х++ х += 1
х-- х -= 1

Утверждения о назначении 

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

Назначение = СписокВыражений  assign_op  СписокВыражений .

Assign_op = [ add_op | mul_op ] "=" .

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

х = 1
*п = ф()
а[я] = 23
(k) = <-ch // то же самое, что: k = <-ch

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

а[я] <<= 2
я &^= 1<<n

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

х, у = f()

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

один, два, три = '一', '二', '三'

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

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

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

a, b = b, a // поменять местами a и b

х := []целое{1, 2, 3}
я := 0
i, x[i] = 1, 2 // установить i = 1, x[0] = 2

я = 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.

тип Point struct { x, y int }
вар п *Точка
x[2], px = 6, 7 // устанавливаем x[2] = 6, затем устанавливаем панику px = 7

я = 2
х = []целое{3, 5, 7}
для i, x[i] = диапазон x { // установить i, x[2] = 0, x[0]
	перерыв
}
// после этого цикла i == 0 и x равен []int{3, 5, 3}

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

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

Если утверждения 

Операторы «If» определяют условное выполнение двух ветвей в соответствии со значением логического выражения. Если выражение оценивается как истинное, выполняется ветвь «if», в противном случае, если она присутствует, выполняется ветвь «else».

IfStmt = "if" [ SimpleStmt ";" ] Блок выражения  [ "else" ( IfStmt | Block ) ] .
если х > макс {
	х = макс
}

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

если х := f(); х < у {
	вернуть х
} иначе если х > z {
	возвращение z
} еще {
	вернуть y
}

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

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

SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .

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

Переключатели выражений 

В выражении switch вычисляется выражение switch, а выражения case, которые не обязательно должны быть константами, вычисляются слева направо и сверху вниз; первое выражение, которое равно выражению switch, запускает выполнение операторов связанного case; остальные case пропускаются. Если ни один case не соответствует и есть case «default», выполняются его операторы. Может быть максимум один case 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 может предшествовать простой оператор, который выполняется до вычисления выражения.

тег переключения {
по умолчанию: s3()
случай 0, 1, 2, 3: s1()
случай 4, 5, 6, 7: s2()
}

switch x := f(); { // пропущенное выражение switch означает «истина»
случай x < 0: возврат -x
по умолчанию: вернуть x
}

выключатель {
случай х < у: f1()
случай х < z: f2()
случай х == 4: f3()
}

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

Тип переключателей 

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

переключатель x.(тип) {
// случаи
}

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

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

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

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

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

переключатель i := x.(тип) {
случай ноль:
	printString("x is nil") // тип i равен типу x (interface{})
случай int:
	printInt(i) // тип i — int
случай float64:
	printFloat64(i) // тип i - float64
случай функция(целое) float64:
	printFunction(i) // тип i - func(int) float64
случай bool, строка:
	printString("тип - bool или string") // тип i - тип x (interface{})
по умолчанию:
	printString("не знаю тип") // тип i равен типу x (interface{})
}

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

v := x // x вычисляется ровно один раз
если v == ноль {
	i := v // тип i равен типу x (interface{})
	printString("x равен нулю")
} иначе если i, isInt := v.(int); isInt {
	printInt(i) // тип i — int
} иначе если i, isFloat64 := v.(float64); isFloat64 {
	printFloat64(i) // тип i - float64
} иначе если i, isFunc := v.(func(int) float64); isFunc {
	printFunction(i) // тип i - func(int) float64
} еще {
	_, isBool := v.(bool)
	_, isString := v.(строка)
	если isBool || isString {
		i := v // тип i равен типу x (interface{})
		printString("тип - bool или string")
	} еще {
		i := v // тип i равен типу x (interface{})
		printString("не знаю тип")
	}
}

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

func f[P любой](x любой) int {
	переключатель x.(тип) {
	случай П:
		возврат 0
	строка случая:
		возврат 1
	случай []P:
		возврат 2
	случай []байт:
		возвращение 3
	по умолчанию:
		возвращение 4
	}
}

var v1 = f[string]("foo") // v1 == 0
var v2 = f[байт]([]байт{}) // v2 == 2

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

Оператор «fallthrough» не допускается в переключателе типов.

Для заявлений 

Оператор «for» определяет повторное выполнение блока. Существует три формы: итерация может контролироваться одним условием, предложением «for» или предложением «range».

ForStmt = "for" [ Условие | ForClause | RangeClause ] Блок .
 Условие = Выражение .

Для утверждений с одним условием 

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

для а < б {
	а *= 2
}

Для заявлений с forпунктом 

Оператор «for» с ForClause также контролируется его условием, но дополнительно он может указывать оператор init и post , такой как оператор присваивания, инкремента или декремента. Оператор init может быть коротким объявлением переменной , но оператор post не должен.

ForClause = [ InitStmt ] ";" [ Состояние ] ";" [ Постстмт ] .
 InitStmt = SimpleStmt .
 ПостСтмт = ПростоСтмт .
для i := 0; i < 10; i++ {
	ф(я)
}

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

для cond { S() } то же самое, что и для ; cond ; { S() }
для { S() } то же самое, что и для true { S() }

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

var печатает []func()
для i := 0; i < 5; i++ {
	печатает = append(печатает, func() { println(i) })
	я++
}
для _, p := диапазон печатает {
	р()
}

отпечатки

1
3
5

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

6
6
6

Для заявлений с rangeпунктом 

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

RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Выражение .

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

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

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

Выражение диапазона 1-е значение 2-е значение

массив или срез a [n]E, *[n]E или []E индекс i int a[i] E
строка s тип строки индекс i int см. ниже руна
карта m карта[K]V ключ k K m[k] V
канал c chan E, <-chan E элемент e E
целочисленное значение n целочисленного типа или нетипизированное значение 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. Если присутствует не более одной переменной итерации, цикл диапазона производит значения итераций от 0 до len(a)-1и не индексирует сам массив или срез. Для nilсреза количество итераций равно 0.
  2. Для строкового значения предложение «range» перебирает кодовые точки Unicode в строке, начиная с индекса байта 0. При последующих итерациях значение индекса будет индексом первого байта последовательных кодовых точек UTF-8 в строке, а второе значение типа runeбудет значением соответствующей кодовой точки. Если итерация встречает недопустимую последовательность UTF-8, вторым значением будет 0xFFFD, заменяющий символ Unicode, а следующая итерация переместит строку на один байт.
  3. Порядок итерации по картам не указан и не гарантируется, что он будет одинаковым от одной итерации к другой. Если запись карты, которая еще не была достигнута, удаляется во время итерации, соответствующее значение итерации не будет создано. Если запись карты создается во время итерации, эта запись может быть создана во время итерации или может быть пропущена. Выбор может меняться для каждой созданной записи и от одной итерации к другой. Если карта — nil, количество итераций равно 0.
  4. Для каналов, полученные значения итерации являются последовательными значениями, отправляемыми по каналу до тех пор, пока канал не будет закрыт . Если канал — nil, выражение диапазона блокируется навсегда.
  5. Для целочисленного значения n, где nis имеет целочисленный тип или нетипизированную целочисленную константу , значения итерации от 0 до n-1 производятся в порядке возрастания. Если nis имеет целочисленный тип, значения итерации имеют тот же тип. В противном случае тип 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 {
	а *[7]целое
}
для i, _ := диапазон testdata.a {
	// testdata.a никогда не оценивается; len(testdata.a) является константой
	// i варьируется от 0 до 6
	ф(я)
}

var a [10]строка
для i, s := диапазон a {
	// тип i - int
	// тип s — строка
	// с == а[я]
	г(я, с)
}

var ключевая строка
var val interface{} // тип элемента m может быть назначен val
m := map[string]int{"пн":0, "вт":1, "ср":2, "чт":3, "пт":4, "сб":5, "вс":6}
для ключа, значение = диапазон m {
	h(ключ, значение)
}
// ключ == последний ключ карты, обнаруженный в итерации
// значение == карта[ключ]

var ch chan Работа = производитель()
для w := диапазон ch {
	doWork(w)
}

// очистить канал
для диапазона ch {}

// вызов f(0), f(1), ... f(9)
для i := диапазон 10 {
	// тип i - int (тип по умолчанию для нетипизированной константы 10)
	ф(я)
}

// неверно: 256 не может быть присвоено uint8
var u uint8
для u = диапазон 256 {
}

// неверно: 1e3 — константа с плавающей точкой
для диапазона 1e3 {
}

// fibo генерирует последовательность Фибоначчи
fibo := func(yield func(x int) bool) {
	ф0, ф1 := 0, 1
	для доходности (f0) {
		ф0, ф1 = ф1, ф0+ф1
	}
}

// вывести числа Фибоначчи ниже 1000:
для x := диапазон фибо {
	если х >= 1000 {
		перерыв
	}
	fmt.Printf("%d ", x)
}
// вывод: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

// поддержка итераций для рекурсивной древовидной структуры данных
тип Дерево[K cmp.Ordered, V любой] структура {
	левое, правое *Дерево[K, V]
	клавиша К
	значение 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(выход)
}

// обход дерева t в порядке возрастания
var t Дерево[строка, целое]
для k, v := диапазон t.Walk {
	// процесс k, v
}

Перейти к заявлениям 

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

GoStmt = "go" Выражение .

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

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

перейти на сервер()
go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)

Выберите утверждения 

Оператор «select» выбирает, какой из набора возможных операций отправки или получения будет выполнен. Он похож на оператор «switch» , но все случаи относятся к операциям связи.

SelectStmt = "select" "{" { CommClause } "}" .
 CommClause = CommCase ":" StatementList .
 CommCase    = "case" ( SendStmt | RecvStmt ) | "default" .
 RecvStmt    = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
 RecvExpr    = Выражение .

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

Выполнение оператора «select» происходит в несколько этапов:

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

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

var a []целое
переменная c, c1, c2, c3, c4 канал int
вар i1, i2 целые
выбирать {
случай i1 = <-c1:
	print("получено ", i1, " от c1\n")
случай c2 <- i2:
	print("отправлено ", i2, " в c2\n")
case i3, ok := (<-c3): // то же самое, что: i3, ok := <-c3
	если ок {
		print("получено ", i3, " от c3\n")
	} еще {
		print("c3 закрыт\n")
	}
случай а[f()] = <-c4:
	// то же самое, что:
	// случай t := <-c4
	// а[ф()] = т
по умолчанию:
	print("нет связи\n")
}

для { // отправить случайную последовательность бит в c
	выбирать {
	case c <- 0: // примечание: нет оператора, нет перехода, нет сворачивания случаев
	случай с <- 1:
	}
}

выберите {} // заблокировать навсегда

Возврат заявлений 

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

ReturnStmt = "return" [ ExpressionList ] .

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

функция noResult() {
	возвращаться
}

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

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

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

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

func f(n int) (res int, err ошибка) {
	если _, ошибка := f(n-1); ошибка != ноль {
		return // недопустимый оператор возврата: err затенен
	}
	возвращаться
}

Заявления о перерывах 

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

BreakStmt = "break" [ Метка ].

Если есть метка, то она должна быть меткой охватывающего оператора «for», «switch» или «select», и именно на ней завершается выполнение.

Внешний контур:
	для i = 0; i < n; i++ {
		для j = 0; j < m; j++ {
			переключатель а[i][j] {
			случай ноль:
				состояние = Ошибка
				перерыв OuterLoop
			предмет дела:
				состояние = Найдено
				перерыв OuterLoop
			}
		}
	}

Продолжить заявления 

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

ContinueStmt = "продолжить" [ Метка ].

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

RowLoop:
	для y, строка := диапазон строк {
		для x, данные := диапазон строк {
			если данные == endOfRow {
				продолжить RowLoop
			}
			строка[x] = данные + смещение(x, y)
		}
	}

Операторы Goto 

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

GotoStmt = Метка "goto" .
перейти к ошибке

Выполнение оператора «goto» не должно приводить к попаданию в область видимости каких-либо переменных , которые еще не находились в области видимости в точке goto. Например, этот пример:

	перейти на L // ПЛОХО
	в := 3
Л:

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

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

если n%2 == 1 {
	перейти на L1
}
для n > 0 {
	ф()
	н--
Л1:
	ф()
	н--
}

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

Провалы заявлений 

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

FallthroughStmt = "провал".

Отсрочка заявлений 

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

DeferStmt = "отложить" Выражение .

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

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

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

замок(л)
defer unlock(l) // разблокировка происходит до возврата окружающей функции

// выводит 3 2 1 0 до возврата окружающей функции
для я := 0; я <= 3; я++ {
	отложить fmt.Print(i)
}

// f возвращает 42
func f() (результат int) {
	отложить функцию() {
		// результат доступен после того, как ему было присвоено значение 6 оператором return
		результат *= 7
	}()
	возвращение 6
}

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

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

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

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

Встроенные функции appendи copyпомогают в общих операциях среза. Для обеих функций результат не зависит от того, перекрывается ли память, на которую ссылаются аргументы.

Функция с переменным числом аргументов добавляет append ноль или более значений xк срезу s и возвращает результирующий срез того же типа, что и sОсновной тип должен sбыть срезом типа []E. Значения xпередаются параметру типа ...E , и применяются соответствующие правила передачи параметров . В качестве особого случая, если основной тип s— []byteappendтакже принимает второй аргумент с основным типом, bytestringза которым следует .... Эта форма добавляет байты байтового среза или строки.

append(s S, x ...E) S // основной тип S - []E

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

s0 := []целое{0, 0}
s1 := append(s0, 2) // добавить один элемент s1 is []int{0, 0, 2}
s2 := append(s1, 3, 5, 7) // добавить несколько элементов s2 is []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...) // добавить срез s3 is []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...) // добавить перекрывающийся срез s4 is []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []интерфейс{}
t = append(t, 42, 3.1415, "foo") // t — это []интерфейс{}{42, 3.1415, "foo"}

var b []байт
b = append(b, "bar"...) // добавить содержимое строки b is []byte{'b', 'a', 'r' }

Функция copyкопирует элементы среза из источника srcв место назначения dstи возвращает количество скопированных элементов. Основные типы обоих аргументов должны быть срезами с идентичным типом элемента. Количество скопированных элементов равно минимуму len(src)и len(dst). В качестве особого случая, если основной тип места назначения — []bytecopyтакже принимает исходный аргумент с основным типом bytestring. Эта форма копирует байты из байтового среза или строки в байтовый срез.

копия(dst, src []T) целое
copy(dst []byte, src string) int

Примеры:

вар а = [...] int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]байт, 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, "Привет, мир!") // n3 == 5, b is []byte("Привет")

Прозрачный 

Встроенная функция clearпринимает аргумент типа параметра map , slice или type и удаляет или обнуляет все элементы [ Go 1.21 ].

Вызов Тип аргумента Результат

clear(m) map[K]T удаляет все записи, в результате чего
                              пустая карта (len(m) == 0)

clear(s) []T устанавливает все элементы длиной до
                              sк нулевому значению T

параметр типа clear(t) см. ниже

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

Если карта или срез — это nilclearто это пустая операция.

Закрывать 

Для аргумента chс основным типом , который является каналом , встроенная функция close записывает, что больше никаких значений не будет отправлено по каналу. Это ошибка, если chэто канал только для приема. Отправка или закрытие закрытого канала вызывает панику во время выполнения . Закрытие канала nil также вызывает панику во время выполнения . После вызова closeи после получения любых ранее отправленных значений операции получения вернут нулевое значение для типа канала без блокировки. Многозначная операция получения возвращает полученное значение вместе с указанием того, закрыт ли канал.

Манипулирование комплексными числами 

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

комплексный(действительнаяЧасть, мнимаяЧасть floatT) комплексныйT
real(complexT) floatT
imag(complexT) floatT

Тип аргументов и возвращаемого значения соответствуют. Для complexдва аргумента должны иметь одинаковый тип с плавающей точкой , а возвращаемый тип — комплексный тип с соответствующими компонентами с плавающей точкой: complex64для float32аргументов и complex128для float64аргументов. Если один из аргументов вычисляется как нетипизированная константа, он сначала неявно преобразуется в тип другого аргумента. Если оба аргумента вычисляются как нетипизированные константы, они должны быть некомплексными числами или их мнимые части должны быть равны нулю, а возвращаемое значение функции — нетипизированная комплексная константа.

Для realи imagаргумент должен быть комплексного типа, а возвращаемый тип — соответствующий тип с плавающей точкой: float32для complex64аргумента и float64для complex128аргумента. Если аргумент вычисляется как нетипизированная константа, он должен быть числом, а возвращаемое значение функции — нетипизированная константа с плавающей точкой.

Функции realи imagвместе образуют обратную функцию , поэтому complexдля значения zкомплексного типа Zz == Z(complex(real(z), imag(z)))

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

var a = комплекс(2, -2) // комплекс128
const b = complex(1.0, -1.4) // нетипизированная комплексная константа 1 - 1.4i
x := float32(math.Cos(math.Pi/2)) // float32
var c64 = комплекс(5, -x) // комплекс64
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 предполагает сложный тип, сдвиг невозможен

Аргументы типа параметра типа не допускаются.

Удаление элементов карты 

Встроенная функция deleteудаляет элемент с ключом kиз карты m . Значение kдолжно быть назначаемым типу ключа m.

delete(m, k) // удалить элемент m[k] из карты m

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

Если карта mсуществует nilили элемент m[k] не существует, deleteто это пустая операция.

Длина и вместимость 

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

Вызов Тип аргумента Результат

len(s) тип строки длина строки в байтах
          [n]T, *[n]T длина массива (== n)
          []Длина среза T
          map[K]T длина карты (количество определенных ключей)
          chan T количество элементов, поставленных в очередь в буфере канала
          параметр типа см. ниже

cap(s) [n]T, *[n]T длина массива (== n)
          []Емкость среза T
          емкость буфера канала T
          параметр типа см. ниже

Если тип аргумента является параметром типа P , вызов len(e)(или cap(e)соответственно) должен быть допустимым для каждого типа в Pнаборе типов . Результатом является длина (или емкость, соответственно) аргумента, тип которого соответствует аргументу типа, с которым Pбыл создан экземпляр .

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

0 <= длина(ы) <= размер(ы)

Длина среза nil, карты или канала равна 0. Емкость nilсреза или канала равна 0.

Выражение len(s)является константой, если sявляется строковой константой. Выражения len(s)и cap(s)являются константами, если тип sявляется массивом или указателем на массив и выражение sне содержит приемы каналов или (неконстантные) вызовы функций ; в этом случае sне вычисляется. В противном случае вызовы lenи capне являются константами и sвычисляется.

константа (
	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) — это (неконстантный) вызов функции
)
вар z комплекс128

Создание срезов, карт и каналов 

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

Вызов Тип ядра Результат

make(T, n) slice срез типа T длиной n и емкостью n
make(T, n, m) срез срез типа T длиной n и емкостью m

make(T) map карта типа T
make(T, n) map map типа T с начальным пространством для приблизительно n элементов

make(T) канал небуферизованный канал типа T
make(T, n) канал буферизованный канал типа T, размер буфера n

Каждый из аргументов размера 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) // карта с начальным пространством примерно для 100 элементов

Вызов makeс указанием типа и размера карты nсоздаст карту с начальным пространством для хранения nэлементов карты. Точное поведение зависит от реализации.

Мин и макс 

Встроенные функции minи maxвычисляют наименьшее — или наибольшее, соответственно — значение фиксированного числа аргументов упорядоченных типов . Должен быть хотя бы один аргумент [ Go 1.21 ].

Применяются те же правила типов, что и для операторов : для упорядоченных аргументов xи ymin(x, y)допустимо, если x + yдопустимо, а тип min(x, y)является типом x + y (и аналогично для max). Если все аргументы являются константами, результат является константой.

переменная x, y целое
м := мин(х) // м == х
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 []строка
_ = min(s...) // недопустимо: аргументы среза не допускаются
t := max("", "foo", "bar") // t == "foo" (строковый тип)

Для числовых аргументов, предполагая, что все NaN равны, minа также maxявляются коммутативными и ассоциативными:

мин(х, у) == мин(у, х)
min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))

Для аргументов с плавающей точкой (отрицательный ноль, NaN и бесконечность) применяются следующие правила:

   xy мин(x, y) макс(x, y)

  -0.0 0.0 -0.0 0.0 // отрицательный ноль меньше (неотрицательного) нуля
  -Inf y -Inf y // отрицательная бесконечность меньше любого другого числа
  +Inf yy +Inf // положительная бесконечность больше любого другого числа
   NaN y NaN NaN // если какой-либо аргумент — NaN, результат — NaN

Для строковых аргументов результатом minявляется первый аргумент с наименьшим (или для maxнаибольшим) значением, сравниваемый лексически побайтово:

min(x, y) == если x <= y тогда x иначе y
min(x, y, z) == min(min(x, y), z)

Распределение 

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

тритон)

Например

тип S struct { a int; b float64 }
новости)

выделяет хранилище для переменной типа S, инициализирует ее ( a=0b=0.0) и возвращает значение типа , *Sсодержащее адрес местоположения.

Как справиться с паникой 

Две встроенные функции panicи recoverпомогают сообщать о паниках во время выполнения и программно-определяемых состояниях ошибок и обрабатывать их.

функция паники(интерфейс{})
интерфейс funcrecover(){}

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

паника(42)
паника("недоступен")
паника(Ошибка("невозможно проанализировать"))

Функция recoverпозволяет программе управлять поведением паникующей горутины. Предположим, что функция Gоткладывает функцию D, которая вызывает , recoverи в функции той же горутины, в которой G выполняется , происходит паника. Когда выполнение отложенных функций достигает D, возвращаемым значением Dвызова ‘s recoverбудет значение, переданное вызову panic. Если Dвозвращается нормально, без начала нового panic, паникующая последовательность останавливается. В этом случае состояние функций, вызванных между Gи вызовом , panic отбрасывается, и возобновляется нормальное выполнение. Затем запускаются все функции, отложенные до , и Gвыполнение завершается возвратом к вызывающей стороне. DG

Возвращаемое значение — recoverэто nilкогда goroutine не паникует или recoverне была вызвана напрямую отложенной функцией. И наоборот, если goroutine паникует и recoverбыла вызвана напрямую отложенной функцией, возвращаемое значение recoverгарантированно не будет nil. Чтобы гарантировать это, вызов panicсо nilзначением интерфейса (или нетипизированным nil) вызывает панику во время выполнения .

Функция protectв примере ниже вызывает аргумент функции gи защищает вызывающие объекты от паник во время выполнения, вызванных g.

функция защиты (g функция()) {
	отложить функцию() {
		log.Println("done") // Println выполняется нормально, даже если возникла паника
		если x := восстановить(); x != ноль {
			log.Printf("паника во время выполнения: %v", x)
		}
	}()
	log.Println("старт")
	г()
}

Самонастройка 

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

Поведение функции

print выводит все аргументы; форматирование аргументов зависит от реализации
println похож на print, но печатает пробелы между аргументами и новую строку в конце

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

Пакеты 

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

Организация исходного файла 

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

SourceFile        = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .

Пакетное положение 

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

PackageClause   = "package" PackageName .
 PackageName     = идентификатор .

PackageName не может быть пустым идентификатором .

пакет математика

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

Импортные декларации 

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

ImportDecl        = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
 ImportSpec        = [ "." | PackageName ] ImportPath .
 ImportPath        = string_lit .

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

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

Ограничение реализации: компилятор может ограничить ImportPaths непустыми строками, используя только символы, принадлежащие к общим категориям Unicode L, M, N, P и S (графические символы без пробелов), а также может исключить символы !"#$%&'()*,:;<=>?[\]^`{|} и заменяющий символ Unicode U+FFFD.

Рассмотрим скомпилированный пакет, содержащий предложение пакета package math, которое экспортирует функцию Sin, и установим скомпилированный пакет в файле, идентифицированном "lib/math". Эта таблица иллюстрирует, как Sinосуществляется доступ в файлах, которые импортируют пакет после различных типов объявления импорта.

Импортная декларация Местное название Sin

импорт "lib/math" math.Sin
импорт m "lib/math" m.Sin
импорт . "lib/math" Грех

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

импорт _ "lib/math"

Пример пакета 

Вот полный пакет Go, реализующий параллельное простое решето.

пакет основной

импорт "фмт"

// Отправляем последовательность 2, 3, 4, … на канал 'ch'.
функция генерации(ch chan<- int) {
	для я := 2; ; я++ {
		ch <- i // Отправляем 'i' на канал 'ch'.
	}
}

// Копируем значения из канала 'src' в канал 'dst',
// удаляем те, которые делятся на «простое».
func filter(src <-chan int, dst chan<- int, prime int) {
	for i := range src { // Цикл по значениям, полученным от 'src'.
		если я%простое != 0 {
			dst <- i // Отправляем 'i' на канал 'dst'.
		}
	}
}

// Главное сито: последовательные процессы фильтрации.
функция решето() {
	ch := make(chan int) // Создать новый канал.
	go generate(ch) // Запустить generate() как подпроцесс.
	для {
		простое число := <-ч
		fmt.Print(простое число, "\n")
		ch1 := make(chan int)
		перейти фильтр(ch, ch1, prime)
		ч = ч1
	}
}

функция main() {
	сито()
}

Инициализация и выполнение программы 

Нулевое значение 

Когда память выделяется для переменной , либо через объявление или вызов new, либо когда создается новое значение, либо через составной литерал, либо вызов make, и не предоставляется явная инициализация, переменной или значению присваивается значение по умолчанию. Каждому элементу такой переменной или значения присваивается нулевое значение для его типа: falseдля логических значений, 0для числовых типов, "" для строк и nilдля указателей, функций, интерфейсов, срезов, каналов и карт. Эта инициализация выполняется рекурсивно, поэтому, например, каждый элемент массива структур будет иметь обнуленные поля, если не указано значение.

Эти два простых объявления эквивалентны:

var i int
var i int = 0

После

тип T структура { i int; f float64; next *T }
т := новый(Т)

справедливо следующее:

ти == 0
тф == 0.0
t.next == ноль

То же самое будет верно и после

вар т Т

Инициализация пакета 

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

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

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

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

вар х = а
var a, b = f() // a и b инициализируются вместе, до инициализации x

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

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

Анализ зависимости не опирается на фактические значения переменных, а только на лексические ссылки на них в источнике, анализируемые транзитивно. Например, если xвыражение инициализации переменной ссылается на функцию, тело которой ссылается на переменную, yто xзависит от y. В частности:

  • Ссылка на переменную или функцию — это идентификатор, обозначающий эту переменную или функцию.
  • Ссылка на метод m— это значение метода или выражение метода в форме t.m, где (статический) тип tне является типом интерфейса, а метод mнаходится в наборе методовt . Не имеет значения, t.mвызывается ли результирующее значение функции.
  • Переменная, функция или метод xзависят от переменной, yесли xвыражение инициализации или тело (для функций и методов) содержит ссылку на y или на функцию или метод, которые зависят от y.

Например, учитывая заявления

вар (
	а = с + б // == 9
	б = ф() // == 4
	с = ф() // == 5
	d = 3 // == 5 после завершения инициализации
)

функция f() int {
	д++
	вернуться д
}

порядок инициализации — dbca. Обратите внимание, что порядок подвыражений в выражениях инициализации не имеет значения: a = c + bи a = b + cв этом примере получается тот же порядок инициализации.

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

Например, учитывая заявления

var x = I(T{}).ab() // x имеет необнаруженную, скрытую зависимость от a и b
var _ = sideEffect() // не связано с x, a или b
вар а = б
вар б = 42

интерфейс типа I { ab() []int }
тип T struct{}
функция (T) ab() []int { return []int{a, b} }

переменная aбудет инициализирована после , bно xинициализируется ли она до b, между bи a, или после a, и, таким образом, момент, в который sideEffect()вызывается (до или после xинициализации), не указан.

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

функция init() { … }

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

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

Инициализация программы 

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

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

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

Выполнение программы 

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

функция main() { … }

Выполнение программы начинается с инициализации программы и последующего вызова функции mainв package main. Когда вызов этой функции возвращается, программа завершается. Она не ждет mainзавершения других (не ) goroutines.

Ошибки 

Предварительно объявленный тип errorопределяется как

интерфейс ошибки типа {
	Ошибка() строка
}

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

func Read(f *File, b []byte) (n int, err ошибка)

Паники во время выполнения 

Ошибки выполнения, такие как попытка индексировать массив за пределами границ, вызывают панику во время выполнения, эквивалентную вызову встроенной функции panic со значением определенного реализацией типа интерфейса runtime.Error. Этот тип удовлетворяет предварительно объявленному типу интерфейса error. Точные значения ошибок, которые представляют различные состояния ошибок во время выполнения, не указаны.

время выполнения пакета

Тип интерфейса ошибки {
	ошибка
	// и возможно другие методы
}

Системные соображения 

Упаковка unsafe

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

посылка небезопасна

type ArbitraryType int // сокращение для произвольного типа Go; это не настоящий тип
тип Указатель * ПроизвольныйТип

func Alignof(переменная ПроизвольныйТип) uintptr
func Offsetof(селектор ПроизвольныйТип) uintptr
func Sizeof(переменная ПроизвольныйТип) uintptr

тип IntegerType int // сокращение для целочисленного типа; это не настоящий тип
func Add(ptr Указатель, len IntegerType) Указатель
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
func SliceData(slice []ПроизвольныйТип) *ПроизвольныйТип
func String(ptr *byte, len IntegerType) строка
func StringData(str string) *byte

Pointer— это тип указателя , но Pointer значение не может быть разыменовано . Любой указатель или значение основного типа uintptr можно преобразовать в тип основного типа Pointerи наоборот. Эффект преобразования между Pointerи uintptrопределяется реализацией.

вар ф float64
биты = *(*uint64)(небезопасный.Указатель(&f))

тип ptr небезопасный.Указатель
биты = *(*uint64)(ptr(&f))

функция f[P ~*B, B любой](p P) uintptr {
	вернуть uintptr(небезопасный.Указатель(p))
}

var p ptr = ноль

Функции Alignofи Sizeofпринимают выражение x любого типа и возвращают выравнивание или размер, соответственно, гипотетической переменной, v как если бы vона была объявлена ​​через var v = x.

Функция Offsetofпринимает (возможно, заключенный в скобки) селектор s.f , обозначающий поле fструктуры, обозначенной как s или *s, и возвращает смещение поля в байтах относительно адреса структуры. Если fэто встроенное поле , оно должно быть доступно без косвенных ссылок указателя через поля структуры. Для структуры sс полем f:

uintptr(небезопасный.Указатель(&s)) + небезопасный.Смещение(sf) == uintptr(небезопасный.Указатель(&s.f))

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

uintptr(небезопасный.Указатель(&x)) % небезопасный.Выравнивание(x) == 0

Тип (переменная) Tимеет переменный размер , если T является параметром типа или если это массив или тип структуры, содержащий элементы или поля переменного размера. В противном случае размер является постоянным . Вызовы AlignofOffsetofи являются константными выражениямиSizeof времени компиляции типа , если их аргументы (или структура в выражении селектора для ) являются типами постоянного размера. uintptrss.fOffsetof

Функция Addдобавляет и возвращает lenобновленный ptr указатель unsafe.Pointer(uintptr(ptr) + uintptr(len)) [ Go 1.17 ]. lenАргумент должен быть целочисленного типа или нетипизированной константой . Константный lenаргумент должен быть представлен значением типа int; если это нетипизированная константа, то ей присваивается тип int. Правила допустимого использования по Pointer-прежнему применяются.

Функция Sliceвозвращает срез, базовый массив которого начинается с ptr , а его длина и емкость равны lenSlice(ptr, len)эквивалентно

(*[len]ПроизвольныйТип)(небезопасный.Указатель(ptr))[:]

за исключением того, что в качестве особого случая, если ptr равно nilи lenравно нулю, Sliceвозвращается nil [ Go 1.17 ].

Аргумент lenдолжен быть целочисленного типа или нетипизированной константой . Константный lenаргумент должен быть неотрицательным и представимым значением типа int; если это нетипизированная константа, ей присваивается тип int. Во время выполнения, если lenотрицательно или если ptrравно nilи lenне равно нулю, возникает паника во время выполнения [ Go 1.17 ].

Функция SliceDataвозвращает указатель на базовый массив аргумента slice. Если емкость среза cap(slice)не равна нулю, этот указатель равен &slice[:1][0]. Если sliceравен nil, результат равен nil. В противном случае это не nilуказатель на неуказанный адрес памяти [ Go 1.20 ].

Функция Stringвозвращает stringзначение, базовые байты которого начинаются с ptrи длина которого равна len. К аргументу ptrи применяются те же требования len, что и в функции Slice. Если lenравно нулю, результатом является пустая строка "". Поскольку строки Go неизменяемы, переданные байты Stringне должны впоследствии изменяться. [ Go 1.20 ]

Функция StringDataвозвращает указатель на базовые байты аргумента str. Для пустой строки возвращаемое значение не указано и может быть nil. Поскольку строки Go неизменяемы, байты, возвращаемые функцией, StringDataне должны изменяться [ Go 1.20 ].

Гарантии размера и выравнивания 

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

размер шрифта в байтах

байт, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64, float64, complex64 8
комплекс128 16

Гарантируются следующие минимальные свойства выравнивания:

  1. Для переменной xлюбого типа: unsafe.Alignof(x)не менее 1.
  2. Для переменной xтипа struct: unsafe.Alignof(x)наибольшее из всех значений unsafe.Alignof(x.f)для каждого поля fxно не менее 1.
  3. Для переменной xтипа массива: unsafe.Alignof(x)то же самое, что и выравнивание переменной типа элемента массива.

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

Приложение 

Языковые версии 

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

Например, возможность использовать префикс 0bдля двоичных целочисленных литералов была введена в Go 1.13, обозначенная как [ Go 1.13 ] в разделе о целочисленных литералах . Исходный код, содержащий целочисленный литерал, такой как , 0b1011 будет отклонен, если подразумеваемая или требуемая версия языка, используемая компилятором, старше Go 1.13.

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

Перейти 1.9 

Перейти 1.13 

  • Целочисленные литералы могут использовать префиксы 0b0B0o, и 0Oдля двоичных и восьмеричных литералов соответственно.
  • Шестнадцатеричные литералы с плавающей точкой можно записывать с использованием префиксов 0xи 0X.
  • Мнимый суффикс i может использоваться с любым (двоичным, десятичным, шестнадцатеричным) целым числом или числом с плавающей точкой, а не только с десятичными литералами.
  • Цифры любого числового литерала могут быть разделены (сгруппированы) с помощью подчеркивания _.
  • Счетчик сдвига в операции сдвига может быть целым числом со знаком.

Перейти 1.14 

Перейти 1.17 

  • Срез может быть преобразован в указатель массива, если типы элементов среза и массива совпадают, а массив не длиннее среза.
  • Встроенный пакетunsafe включает в себя новые функции Addи Slice.

Перейти 1.18 

В версии 1.18 в язык добавлены полиморфные функции и типы («дженерики»). В частности:

Перейти 1.20 

  • Срез может быть преобразован в массив, если типы элементов среза и массива совпадают, а массив не длиннее среза.
  • Встроенный пакетunsafe включает в себя новые функции SliceDataString, и StringData.
  • Сравнимые типы (например, обычные интерфейсы) могут удовлетворять comparableограничениям, даже если аргументы типа не являются строго сравнимыми.

Перейти 1.21 

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

Перейти 1.22 

  • В операторе «for» каждая итерация имеет свой собственный набор итеративных переменных, а не использует одни и те же переменные в каждой итерации.
  • Оператор «for» с предложением «range» может выполнять итерацию по целым значениям от нуля до верхнего предела.

Перейти 1.23 

  • Оператор «for» с предложением «range» принимает функцию итератора в качестве выражения диапазона.

Правила унификации типов 

Правила унификации типов описывают, как и когда два типа объединяются. Точные детали важны для реализаций Go, влияют на специфику сообщений об ошибках (например, сообщает ли компилятор о выводе типа или другой ошибке) и могут объяснить, почему вывод типа не работает в необычных ситуациях кода. Но в целом эти правила можно игнорировать при написании кода Go: вывод типа разработан так, чтобы в основном «работать так, как ожидается», и правила унификации настраиваются соответствующим образом.

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

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

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

Если оба типа являются параметрами связанного типа, они унифицируются согласно заданным режимам соответствия, если:

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

Параметр одного связанного типа Pи другой тип Tунифицируются согласно заданным режимам соответствия, если:

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

Наконец, два типа, не являющиеся связанными параметрами типа, унифицируются слабо (и в соответствии с режимом сопоставления элементов), если:

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

!…

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

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

Deviz_11

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Проверка комментариев включена. Прежде чем Ваши комментарии будут опубликованы пройдет какое-то время.