Примечания к выпуску Go 1.4
Введение в Go 1.4
Последний выпуск Go, версия 1.4, появился вовремя, как и ожидалось, через шесть месяцев после 1.3.
Он содержит лишь одно незначительное изменение языка, в виде обратно совместимой простой вариации цикла for-range, а также потенциально разрушающее изменение компилятора, касающееся методов, применяемых к указателям на указатели.
В этом выпуске основной акцент сделан на работе по реализации, улучшении сборщика мусора и подготовке к внедрению полностью параллельного сборщика в следующих выпусках.
Стеки теперь являются непрерывными, перераспределяются при необходимости, а не связываются с новыми «сегментами»;
поэтому данный выпуск устраняет известную проблему «горячего разбиения стека».
Также появились новые инструменты, включая поддержку генерации исходного кода во время сборки в команде go.
В этом выпуске также добавлена поддержка процессоров ARM на Android и Native Client (NaCl), а также AMD64 на Plan 9.
Как и всегда, Go 1.4 выполняет обещание совместимости, и почти всё, что ранее компилировалось и работало без изменений, продолжит работать в версии 1.4.
Изменения в языке
Циклы for-range
До Go 1.3 цикл for-range имел две формы
<code>for i, v := range x {
...
}
</code>
и
<code>for i := range x {
...
}
</code>
Если интересовали только итерации, а не значения цикла, всё равно требовалось упоминать переменную (вероятно, пустой идентификатор, как в for _ = range x), поскольку форма
<code>for range x {
...
}
</code>
не была синтаксически разрешена.
Эта ситуация казалась неудобной, поэтому начиная с Go 1.4 форма без переменных теперь допустима. Этот паттерн встречается редко, но код становится чище, когда он используется.
Обновление: Изменение полностью обратно совместимо с существующими программами на Go,
но инструменты, анализирующие синтаксические деревья Go, могут потребовать обновления для принятия этого нового вида,
где поле Key в RangeStmt
теперь может быть nil.
Вызовы методов для **T
Учитывая следующие объявления,
<code>type T int
func (T) M() {}
var x **T
</code>
оба компилятора gc и gccgo принимали вызов метода
<code>x.M() </code>
который представляет собой двойное разыменование указателя на указатель x.
Спецификация Go разрешает автоматически вставлять одно разыменование, но не два, поэтому такой вызов является ошибочным согласно определению языка.
Поэтому он был запрещён в Go 1.4, что является разрушающим изменением,
хотя затронуты будут лишь очень немногие программы.
Обновление: Код, зависящий от прежнего, ошибочного поведения, больше не будет компилироваться, но легко исправляется добавлением явного разыменования.
Изменения в поддерживаемых операционных системах и архитектурах
Android
Go 1.4 может создавать исполняемые файлы для процессоров ARM, работающих под операционной системой Android.
Также возможно создание библиотеки .so, которую можно загрузить в приложение Android с использованием поддерживающих пакетов из подрепозитория mobile.
Краткое описание планов по этой экспериментальной порте доступно здесь.
NaCl на ARM
В предыдущем выпуске была добавлена поддержка Native Client (NaCl) для 32-битного x86
(GOARCH=386)
и 64-битного x86 с 32-битными указателями (GOARCH=amd64p32).
В выпуске 1.4 добавлена поддержка NaCl для ARM (GOARCH=arm).
Plan 9 на AMD64
В этом выпуске добавлена поддержка операционной системы Plan 9 на процессорах AMD64,
при условии, что ядро поддерживает системный вызов nsec и использует страницы размером 4 КБ.
Изменения в руководстве по совместимости
Пакет unsafe позволяет обойти систему типов Go, используя внутренние детали реализации или машинное представление данных.
Ранее не было явно указано, что использование unsafe означает с точки зрения совместимости, как указано в
руководстве по совместимости Go.
Ответ, конечно же, заключается в том, что мы не можем дать никаких гарантий совместимости
для кода, который выполняет небезопасные действия.
Мы уточнили эту ситуацию в документации, включённой в релиз.
Руководство по совместимости Go и документация для пакета
unsafe теперь явно указывают, что небезопасный код не гарантирует сохранения совместимости.
Обновление: Технически ничего не изменилось; это просто уточнение документации.
Изменения в реализации и инструментах
Изменения в среде выполнения
До Go 1.4 среда выполнения (сборщик мусора, поддержка параллелизма, управление интерфейсами, карты, срезы, строки и т. д.) в основном была написана на C с некоторой поддержкой ассемблера. В 1.4 большая часть кода была переведена на Go, чтобы сборщик мусора мог сканировать стеки программ в среде выполнения и получать точную информацию о том, какие переменные активны. Это изменение было значительным, но не должно повлиять на семантику программ.
Этот переписанный код позволяет сборщику мусора в 1.4 быть полностью точным, то есть он осведомлён о местоположении всех активных указателей в программе. Это означает, что куча будет меньше, поскольку не будет ложных срабатываний, удерживающих неуказатели в живых. Другие связанные изменения также уменьшают размер кучи, который в целом стал меньше на 10–30% по сравнению с предыдущим релизом.
В результате стеки больше не разбиваются на сегменты, устраняя проблему "горячего разреза". Когда достигается предел стека, выделяется новый, более большой стек, все активные фреймы для горутины копируются туда, и все указатели на стек обновляются. Производительность может быть значительно лучше в некоторых случаях и всегда более предсказуема. Детали доступны в документе проектирования.
Использование контигуальных стеков означает, что стеки могут начинаться меньше без вызова проблем с производительностью, поэтому начальный размер стека горутины в 1.4 был уменьшен с 8192 байт до 2048 байт.
В качестве подготовки к конкурирующему сборщику мусора, запланированному для релиза 1.5, запись в значения указателей в куче теперь выполняется через вызов функции, называемой барьером записи, а не напрямую из функции, обновляющей значение. В следующем релизе это позволит сборщику мусора управлять записями в кучу во время его работы. Это изменение не имеет семантического эффекта на программы в 1.4, но было включено в релиз для тестирования компилятора и полученной производительности.
Реализация значений интерфейсов была изменена. В предыдущих версиях интерфейс содержал слово, которое было либо указателем, либо одноразрядным скалярным значением, в зависимости от типа конкретного объекта, хранящегося в интерфейсе. Эта реализация вызывала проблемы для сборщика мусора, поэтому начиная с 1.4 значения интерфейсов всегда содержат указатель. В запущенных программах большинство значений интерфейсов были указателями, поэтому эффект минимальный, но программы, которые хранят целые числа (например) в интерфейсах, увидят больше выделений.
Начиная с Go 1.3, среда выполнения завершается аварийно, если обнаруживает слово памяти, которое должно содержать
допустимый указатель, но вместо этого содержит очевидно недопустимый указатель (например, значение 3).
Программы, которые хранят целые числа в значениях указателей, могут столкнуться с этой проверкой и аварийно завершиться.
В Go 1.4 установка переменной среды GODEBUG
invalidptr=0 отключает аварийное завершение как временное решение, но мы не можем гарантировать,
что будущие релизы смогут избежать аварийного завершения; правильное решение — переписать код так, чтобы не смешивать целые числа и указатели.
Ассемблер
Язык, принимаемый ассемблерами cmd/5a, cmd/6a и cmd/8a, претерпел несколько изменений,
в основном с целью облегчения передачи информации о типах в среду выполнения.
Во-первых, файл textflag.h, определяющий флаги для директив TEXT,
был скопирован из каталога исходных кодов компоновщика в стандартное место,
чтобы его можно было включить с помощью простой инструкции
<code>#include "textflag.h" </code>
Более важные изменения касаются того, как исходный код ассемблера может определять необходимую информацию о типах.
Для большинства программ будет достаточно переместить определения данных
(DATA и GLOBL директивы)
из ассемблерных файлов в Go-файлы
и написать объявление Go для каждой функции, написанной на ассемблере.
Документация по ассемблеру описывает, что нужно сделать.
Обновление:
Ассемблерные файлы, которые включают textflag.h из его старого расположения, всё ещё будут работать,
но должны быть обновлены.
Что касается информации о типах, большинство ассемблерных процедур не потребуют изменений,
но все должны быть проверены.
Ассемблерные исходные файлы, которые определяют данные,
функции с непустыми стековыми кадрами или функции, возвращающие указатели,
требуют особого внимания.
Описание необходимых (но простых) изменений
находится в документе по ассемблеру.
Более подробная информация об этих изменениях содержится в документе по ассемблеру.
Статус gccgo
Графики выпусков проектов GCC и Go не совпадают. Выпуск GCC 4.9 содержит версию gccgo, соответствующую Go 1.2. Следующий выпуск, GCC 5, вероятно, будет содержать версию gccgo, соответствующую Go 1.4.
Внутренние пакеты
Система пакетов Go позволяет легко структурировать программы в компоненты с чёткими границами, но существует только два вида доступа: локальный (недоступный извне) и глобальный (экспортируемый). Иногда возникает необходимость иметь компоненты, которые не экспортируются, например, чтобы избежать появления клиентов интерфейсов к коду, входящему в состав общедоступного репозитория, но не предназначенного для использования вне программы, к которой он принадлежит.
Язык Go не обладает возможностью принудительно обеспечить такое различие,
но начиная с Go 1.4 команда go вводит механизм
для определения «внутренних» пакетов, которые не могут быть импортированы пакетами вне
исходного дерева, в котором они находятся.
Чтобы создать такой пакет, разместите его в каталоге с именем internal или в подкаталоге каталога,
названного internal.
Когда команда go видит импорт пакета, содержащего internal в его пути,
она проверяет, находится ли пакет, делающий импорт,
в дереве, корнем которого является родительский каталог каталога internal.
Например, пакет .../a/b/c/internal/d/e/f
может быть импортирован только кодом, находящимся в дереве каталогов, корнем которого является .../a/b/c.
Он не может быть импортирован кодом из .../a/b/g или из любого другого репозитория.
Для Go 1.4 механизм внутренних пакетов применяется для основного репозитория Go; начиная с версии 1.5 и далее он будет применяться ко всем репозиториям.
Полные детали механизма находятся в документе по дизайну.
Канонические пути импорта
Код часто размещается в репозиториях, размещённых на публичных сервисах, таких как github.com,
что означает, что пути импорта пакетов начинаются с имени хостинг-сервиса,
например github.com/rsc/pdf.
Можно использовать
существующий механизм,
чтобы предоставить «пользовательский» или «vanity» путь импорта, такой как
rsc.io/pdf, но
это создаёт два допустимых пути импорта для одного пакета.
Это проблема: можно случайно импортировать пакет через два
разных пути в одной программе, что является расточительным;
пропустить обновление пакета, потому что используемый путь не распознан как устаревший;
или сломать клиентов, использующих старый путь, перемещая пакет на другой сервис хостинга.
Go 1.4 вводит аннотацию для инструкций пакетов в исходных кодах Go, которая определяет канонический путь импорта для пакета.
Если попытаться импортировать пакет с использованием пути, который не является каноническим,
команда go откажется компилировать импортирующий пакет.
Синтаксис прост: добавьте комментарий с идентификатором в строку пакета. В нашем примере инструкция пакета будет выглядеть так:
<code>package pdf // import "rsc.io/pdf" </code>
При наличии такой аннотации команда go откажется компилировать пакет, который импортирует github.com/rsc/pdf,
что гарантирует, что код можно будет перемещать без нарушения работы пользователей.
Проверка происходит во время сборки, а не загрузки, поэтому если команда go get
не проходит из-за этой проверки, то импортированный пакет был скопирован на локальную машину
и должен быть удалён вручную.
Для дополнения этой новой функции была добавлена проверка во время обновления, которая проверяет,
соответствует ли удалённый репозиторий локального пакета его пользовательскому пути импорта.
Команда go get -u не сможет обновить пакет, если его удалённый репозиторий изменился с момента первого скачивания.
Новый флаг -f отменяет эту проверку.
Дополнительная информация содержится в документе проектирования.
Пути импорта для субрепозиториев
Субрепозитории проекта Go (code.google.com/p/go.tools и так далее)
теперь доступны по пользовательским путям импорта, где code.google.com/p/go. заменено на golang.org/x/,
например golang.org/x/tools.
Мы добавим комментарии с каноническими путями импорта в код примерно 1 июня 2015 года,
с этого момента Go 1.4 и более поздние версии перестанут принимать старые пути code.google.com.
Обновление: Весь код, импортирующий субрепозитории, должен быть изменён
для использования новых путей golang.org.
Go 1.0 и более поздние версии могут разрешать и импортировать новые пути, поэтому обновление не нарушит
совместимость со старыми версиями.
Код, который не был обновлён, перестанет компилироваться с Go 1.4 примерно 1 июня 2015 года.
Подкоманда go generate
Команда go имеет новую подкоманду,
go generate,
которая автоматизирует запуск инструментов для генерации исходного кода перед компиляцией.
Например, её можно использовать для запуска компилятора yacc
над файлом .y для создания Go-файла, реализующего грамматику,
или для автоматизации генерации методов String для типизированных констант с использованием нового
stringer
инструмента из субрепозитория golang.org/x/tools.
Для получения дополнительной информации см. документ проектирования.
Изменение обработки имён файлов
Ограничения сборки, также известные как теги сборки, управляют компиляцией, включая или исключая файлы
(см. документацию /go/build).
Компиляция также может контролироваться непосредственно именем файла, путём «тегирования» файла
суффиксом (до расширения .go или .s), который начинается с подчёркивания
и содержит имя архитектуры или операционной системы.
Например, файл gopher_arm.go будет скомпилирован только в том случае, если целевой процессор — ARM.
До Go 1.4 файл с именем arm.go также считался тегированным таким образом, но это поведение
может сломать исходный код при добавлении новых архитектур, заставляя файлы внезапно становиться тегированными.
В версии 1.4 файл будет тегироваться таким образом только в том случае, если тег (имя архитектуры или операционной системы)
предшествует символу подчёркивания.
Обновление: Пакеты, зависящие от старого поведения, больше не будут корректно компилироваться.
Файлы с именами вроде windows.go или amd64.go должны либо получить явные теги сборки в исходном коде,
либо быть переименованы, например, в os_windows.go или support_amd64.go.
Другие изменения в команде go
Существовало несколько незначительных изменений в команде
cmd/go, которые стоит отметить.
- Если
cgoне используется для сборки пакета, командаgoтеперь отказывается компилировать C-файлы, поскольку соответствующие C-компиляторы (6cи т.д.) назначены для удаления из установки в будущем релизе. (Используются они сегодня только для сборки части среды выполнения.) В любом случае, их использование сложно и не всегда корректно, поэтому мы отключили их. - Субкоманда
gotestимеет новый флаг-oдля установки имени результирующего двоичного файла, соответствующий тому же флагу в других субкомандах. Флаг-file, не имеющий функциональной нагрузки, был удален. - Субкоманда
gotestтеперь компилирует и связывает все файлы*_test.goв пакете, даже если в них отсутствуют функцииTest. Ранее такие файлы игнорировались. - Поведение флага
-aсубкомандыgobuildбыло изменено для установок, не являющихся разрабатываемыми. Для установок, работающих на релизной версии, флаг-aбольше не будет пересобирать стандартную библиотеку и команды, чтобы избежать перезаписи файлов установки.
Изменения в структуре исходных кодов пакетов
В основном репозитории Go исходный код пакетов хранится в директории
src/pkg, что имело смысл, но отличалось от других репозиториев,
включая подрепозитории Go.
В Go 1.4 уровень pkg в дереве исходных кодов был удален, поэтому, например,
исходный код пакета fmt, который раньше хранился в директории
src/pkg/fmt, теперь находится на один уровень выше — в src/fmt.
Обновление: Инструменты, такие как godoc, которые обнаруживают исходный код,
должны знать о новом местоположении. Все инструменты и сервисы, поддерживаемые командой Go,
были обновлены.
SWIG
Из-за изменений в среде выполнения в этом релизе, Go 1.4 требует SWIG 3.0.3.
Разное
В верхнеуровневой директории misc стандартного репозитория раньше хранилась
поддержка Go для редакторов и IDE: плагины, скрипты инициализации и т.д.
Поддержка этих материалов становилась трудоемкой и требовала внешней помощи,
поскольку многие из перечисленных редакторов не использовались членами основной команды.
Кроме того, нам приходилось принимать решения о том, какой плагин лучше подходит для конкретного
редактора, даже для тех, которые мы сами не используем.
Община Go лучше приспособлена для управления такой информацией. Поэтому в Go 1.4 эта поддержка была удалена из репозитория. Вместо этого теперь существует отобранный и информативный список доступных инструментов на wiki-странице.
Производительность
Большинство программ будут работать примерно с такой же скоростью или немного быстрее в 1.4, чем в 1.3; некоторые могут быть немного медленнее. Изменений много, поэтому сложно точно сказать, что можно ожидать.
Как упоминалось выше, значительная часть среды выполнения была переведена на Go с C, что привело к некоторому уменьшению размеров кучи. Это также немного повысило производительность, поскольку компилятор Go лучше оптимизирует код благодаря таким возможностям, как инлайнинг, чем C-компилятор, используемый для сборки среды выполнения.
Сборщик мусора был ускорен, что привело к измеримому улучшению работы программ с большим количеством сборок мусора. С другой стороны, новые барьеры записи замедляют работу снова, в среднем примерно на ту же величину, но, в зависимости от поведения, некоторые программы могут быть немного медленнее или быстрее.
Изменения в библиотеках, влияющие на производительность, документированы ниже.
Go 1.4 релиз
В этом релизе внесены изменения в поведение и функциональность различных пакетов.
Нововведения
В пакете
bufioдобавлены методыReadLineиReadBytesдля более гибкого чтения данных.Пакет
goтеперь поддерживает флаг-raceна FreeBSD.Пакет
encoding/gobбыл переписан для исключения использования небезопасных операций, что позволяет использовать его в средах, где запрещено использование пакетаunsafe. В обычных случаях он работает на 10–30% медленнее, но в некоторых случаях, особенно с массивами, может быть быстрее. Функциональность не изменилась.В пакете
fmtформатирование указателей на карты теперь соответствует форматированию указателей на структуры, массивы и т. д. Например,&map[string]int{"one": 1}теперь выводится как&map[one: 1], а не как шестнадцатеричное значение указателя.В пакете
imageметодыRGBAAtиGrayAtбыли добавлены к реализациям интерфейсаImage, таким какRGBAиGray, для более эффективного доступа к пикселям.Пакет
image/pngтеперь имеет типEncoderдля управления уровнем сжатия при кодировании.В пакете
mathдобавлена функцияNextafter32.В пакете
net/httpтипRequestполучил новый методBasicAuth, который возвращает имя пользователя и пароль из аутентифицированных запросов с использованием HTTP Basic Authentication Scheme.В пакете
net/httpтипTransportполучил новый хукDialTLS, позволяющий настраивать поведение исходящих TLS-соединений.В пакете
net/http/httputilтипReverseProxyполучил новое полеErrorLog, предоставляющее пользователю контроль над логированием.В пакете
osтеперь реализованы символические ссылки в операционной системе Windows через функциюSymlink. Также добавлена новая функцияUnsetenv.В пакете
reflectинтерфейсTypeполучил новый методComparable, который сообщает, поддерживает ли тип общие сравнения.В пакете
reflectинтерфейсValueтеперь занимает три слова вместо четырех из-за изменений в реализации интерфейсов в среде выполнения. Это экономит память, но не влияет на семантику.В пакете
runtimeтеперь реализованы монотонные часы в Windows, как и в других системах.Счетчик
Mallocsв пакетеruntimeтеперь учитывает очень маленькие аллокации, которые не были учтены в Go 1.3. Это может нарушить тесты, использующиеReadMemStatsилиAllocsPerRun, из-за более точного результата.В структуры
MemStatsиGCStatsв пакетеruntimeдобавлен массивPauseEnd, который является циклическим буфером времен окончания пауз сборки мусора. Соответствующие продолжительности пауз уже записываются вPauseNs.В пакете
runtime/raceтеперь поддерживается FreeBSD, что означает, что флаг-raceкомандыgoтеперь работает на FreeBSD.В пакете
sync/atomicдобавлен новый типValue.Valueпредоставляет эффективный механизм атомарных загрузок и сохранений значений произвольного типа.В реализации пакета
syscallна Linux отключены системные вызовыSetuidиSetgid, поскольку они работают с вызывающим потоком, а не с целым процессом, что отличается от других платформ и не соответствует ожидаемому результату.В пакете
testingдобавлена новая возможность для более точного управления запуском набора тестов. Если в коде тестов содержится функцияfunc TestMain(m *<a href="https://go.dev/pkg/testing#M"><code>testing.M</code></a>)
то она будет вызвана вместо прямого запуска тестов. Структура
Mсодержит методы для доступа и запуска тестов.В пакете
text/templateдобавлен методExecuteдля выполнения шаблонов.В пакете
timeдобавлен методAddдля добавления времени к значениюTime.
Изменения в поведении
В пакете
bufioметодReadRuneтеперь возвращает ошибкуio.ErrUnexpectedEOFвместоio.ErrShortBufferпри чтении неполных данных.В пакете
netпри установке соединения с удаленным хостом теперь используется протокол IPv6, если доступен, даже если IP-адрес указан в формате IPv4.В пакете
osфункцияStatтеперь возвращает ошибкуio.ErrUnexpectedEOFпри попытке чтения неполных данных.В пакете
reflectпри вызове методаInterfaceна пустом значении теперь возвращается nil вместо паники.В пакете
runtimeфункцияGOMAXPROCSтеперь может принимать значение 0, что означает использование всех доступных процессоров.В пакете
text/templateпри выполнении шаблона теперь учитываются все ошибки, а не только первая.В пакете
timeпри вызове методаAddс отрицательным значением теперь возвращается корректное время.
Устаревшие функции
В пакете
bufioустарел методReadRuneв пользу методаReadRune.В пакете
netустарел методDialer.Dialв пользу методаDialer.DialContext.В пакете
osустарела функцияStatв пользу функцииFileInfo.В пакете
reflectустарел методInterfaceв пользу методаInterface.В пакете
runtimeустарела функцияGOMAXPROCSв пользу функцииNumCPU.В пакете
text/templateустарел методExecuteв пользу методаExecute.