четверг, 22 сентября 2022 г.

Релиз Go 1.19

Релиз Go, версия 1.19, выходит через пять месяцев после Go 1.18. Большинство его изменений касаются реализации цепочки инструментов, среды выполнения и библиотек. Как всегда, выпуск поддерживает обещание совместимости Go 1. Ожидается, что почти все программы Go продолжат компилироваться и работать, как и раньше.

Изменения в языке

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

Модель памяти

Модель памяти Go была пересмотрена, чтобы привести Go в соответствие с моделью памяти, используемой в C, C++, Java, JavaScript, Rust и Swift. Go предоставляет только последовательно согласованные атомарные элементы, а не какие-либо более простые формы, встречающиеся в других языках. Наряду с обновлением модели памяти в Go 1.19 представлены новые типы в пакете sync/atomic, которые упрощают использование атомарных значений, таких как atomic.Int64 и atomic.Pointer[T].

Порты

64-битная версия LoongArch

Go 1.19 добавляет поддержку 64-битной архитектуры Loongson LoongArch для Linux (GOOS=linux, GOARCH=loong64). Реализованный ABI — LP64D. Минимальная поддерживаемая версия ядра — 5.19.

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

RISC-V

Порт riscv64 теперь поддерживает передачу аргументов функции и результата с использованием регистров. Сравнительный анализ показывает типичное повышение производительности на 10% и более на riscv64.

Инструменты

Комментарии к документу

В Go 1.19 добавлена поддержка ссылок, списков и более четких заголовков в комментариях к документам. В рамках этого изменения gofmt теперь переформатирует комментарии к документам, чтобы сделать их отображаемый смысл более ясным.В качестве еще одной части этого изменения новый пакет go/doc/comment обеспечивает синтаксический анализ и переформатирование комментариев к документам, а также поддержку их преобразования в HTML, Markdown и текст.

Новое ограничение сборки unix

Unix-ограничение сборки теперь распознается в строках //go:build. Ограничение выполняется, если целевая операционная система, также известная как GOOS, является Unix или Unix-подобной системой. Для версии 1.19 допустимо, если GOOS является одним из следующих: aix, android, darwin, dragonfly, freebsd, hurd, illumos, ios, linux, netbsd, openbsd или solaris. В будущих релизах ограничение unix может соответствовать дополнительным вновь поддерживаемым операционным системам.

Команда go

Флаг -trimpath, если он установлен, теперь включается в настройки сборки, проставленные в двоичных файлах Go командой go build, и может быть проверен с помощью go version -m или debug.ReadBuildInfo.

go generate теперь устанавливает переменную среды GOROOT явно в среде генератора, так что генераторы могут найти правильный GOROOT, даже если он собран с -trimpath.

go test и go generate теперь размещает GOROOT/bin в начало PATH, используемого для подпроцесса, поэтому тесты и генераторы, выполняющие команду go, разрешат ее в один и тот же GOROOT.

go env теперь заключает в кавычки записи, содержащие пробелы в отчетных переменных CGO_CFLAGS, CGO_CPPFLAGS, CGO_CXXFLAGS, CGO_FFLAGS, CGO_LDFLAGS и GOGCCFLAGS.

go list -json теперь принимает список полей JSON, разделенных запятыми, для заполнения. Если указан список, выходные данные JSON будут включать только эти поля, и список перехода может избежать работы по вычислению полей, которые не включены. В некоторых случаях это может подавить ошибки, о которых в противном случае сообщалось бы.

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

Ветеринар (Vet)

vet чекер "errorsas" теперь сообщает когда errors.As вызывается со вторым аргументом типа *error, что является распространенной ошибкой.

Среда выполнения (Runtime)

Среда выполнения теперь включает поддержку мягкого ограничения памяти. Это ограничение памяти включает кучу (heap) Go и всю другую память, управляемую средой выполнения, и исключает внешние источники памяти, такие как сопоставления самого двоичного файла, память, управляемую на других языках, и память, удерживаемую операционной системой от имени программы Go. Этим ограничением можно управлять с помощью runtime/debug.SetMemoryLimit или эквивалентной переменной среды GOMEMLIMIT. Ограничение работает в сочетании с runtime/debug.SetGCPercent / GOGC и будет соблюдаться, даже если GOGC=off, позволяя программам Go всегда максимально использовать свой лимит памяти, в некоторых случаях повышая эффективность использования ресурсов. Обратите внимание, что небольшие ограничения памяти, порядка десятков мегабайт или меньше, с меньшей вероятностью будут соблюдаться из-за внешних факторов задержки, таких как планирование ОС. Большие пределы памяти, порядка сотен мегабайт и более, стабильны и готовы к работе.

Чтобы ограничить последствия перегрузки GC, когда размер динамической кучи программы приближается к пределу программной памяти, среда выполнения Go также пытается ограничить общее использование ЦП GC до 50%, исключая время простоя, предпочитая использовать больше памяти, а не предотвращая выполнение приложения. На практике ожидается, что это ограничение будет играть роль только в исключительных случаях, и новая метрика среды выполнения /gc/limiter/last-enabled:gc-cycle сообщает, когда это произошло в последний раз.

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

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

В операционных системах Unix программы Go, которые импортируют пакет os, теперь автоматически увеличивают лимит открытых файлов (RLIMIT_NOFILE) до максимально допустимого значения; то есть они изменяют мягкое ограничение, чтобы оно соответствовало жесткому ограничению. Это исправляет искусственно заниженные ограничения, установленные в некоторых системах для совместимости с очень старыми программами на C, использующими системный вызов select. Это ограничение не помогает программам Go, и вместо этого даже простые программы, такие как gofmt, часто не имеют файловых дескрипторов в таких системах при параллельной обработке большого количества файлов. Одним из последствий этого изменения является то, что программы Go, которые, в свою очередь, выполняют очень старые программы C в дочерних процессах, могут запускать эти программы со слишком высоким ограничением. Это можно исправить, установив жесткое ограничение перед вызовом программы Go.

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

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

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

Компилятор

Теперь компилятор использует таблицу переходов для реализации больших целочисленных и строковых операторов переключения. Улучшения производительности оператора switch различаются, но могут быть на порядка 20% быстрее. (только GOARCH=amd64 и GOARCH=arm64)

Компилятору Go теперь требуется флаг -p=importpath для создания связываемого объектного файла. Это уже предоставлено командой go и Bazel. Любые другие системы сборки, которые напрямую вызывают компилятор Go, также должны убедиться, что они передают этот флаг.

Компилятор Go больше не поддерживает флаг -importmap. Системы сборки, которые напрямую вызывают компилятор Go, должны вместо этого использовать флаг -importcfg.

Ассемблер

Как и компилятору, ассемблеру теперь требуется флаг -p=importpath для создания компоновочного объектного файла. Это уже предоставлено командой go. Любые другие системы сборки, которые напрямую вызывают ассемблер Go, также должны убедиться, что они передают этот флаг.

Линкер

На платформах ELF компоновщик теперь создает сжатые разделы DWARF в стандартном формате gABI (SHF_COMPRESSED) вместо устаревшего формата .zdebug.

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

Новые атомарные типы

Пакет sync/atomic определяет новые атомарные типы Bool, Int32, Int64, Uint32, Uint64, Uintptr и Pointer. Эти типы скрывают базовые значения, поэтому все обращения вынуждены использовать атомарные API. Pointer также избавляет от необходимости конвертировать в unsafe.Pointer в местах вызовов. Int64 и Uint64 автоматически выравниваются по 64-битным границам в структурах и выделенных данных даже в 32-битных системах.

PATH поиск

Command и LookPath больше не позволяют находить результаты поиска PATH относительно текущего каталога. Это устраняет общий источник проблем с безопасностью, но может также нарушить работу существующих программ, которые зависят от использования, скажем, exec.Command("prog") для запуска двоичного файла с именем prog (или, в Windows, prog.exe) в текущем каталоге.

В Windows Command и LookPath теперь учитывают переменную среды NoDefaultCurrentDirectoryInExePath, что позволяет отключить неявный поиск “.” по умолчанию в поиске PATH в системах Windows.

Мелкие изменения в библиотеке

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

archive/zip

Reader теперь игнорирует данные, отличные от ZIP, в начале ZIP-файла, что соответствует большинству других реализаций. Это необходимо, помимо прочего, для чтения некоторых файлов Java JAR.

crypto/elliptic

Работа с недопустимыми точками кривой (теми, для которых метод IsOnCurve возвращает false и которые никогда не возвращаются Unmarshal или методом Curve, работающим с допустимой точкой) всегда было неопределенным поведением и может привести к атакам восстановления ключа. Если для Marshal, MarshalCompressed, Add, Double или ScalarMult указана недопустимая точка, они теперь будут паниковать.

Операции ScalarBaseMult с кривыми P224, P384 и P521 теперь выполняются до трех раз быстрее, что приводит к аналогичному ускорению некоторых операций ECDSA. Общая (не оптимизированная для платформы) реализация P256 была заменена реализацией, полученной из официально проверенной модели; это может привести к значительному замедлению работы на 32-разрядных платформах.

crypto/rand

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

В Plan 9 функция Read была повторно реализована, заменив алгоритм ANSI X9.31 на генератор быстрого стирания ключей.

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

crypto/tls

Опция GODEBUG tls10default=1 удалена. По-прежнему можно включить TLS 1.0 на стороне клиента, установив Config.MinVersion.

Сервер и клиент TLS теперь отклоняют повторяющиеся расширения в рукопожатиях TLS, как того требуют RFC 5246, раздел 7.4.1.4 и RFC 8446, раздел 4.2.

crypto/x509

CreateCertificate больше не поддерживает создание сертификатов с SignatureAlgorithm, для которого задано значение MD5WithRSA.

CreateCertificate больше не принимает отрицательные серийные номера.

CreateCertificate больше не будет выдавать пустую SEQUENCE, если созданный сертификат не имеет расширений.

Удаление GODEBUG optionx509sha1=1, первоначально запланированное для Go 1.19, было перенесено на будущий релиз. Приложения, использующие его, должны работать при миграции. Практические атаки на SHA-1 были продемонстрированы с 2017 года, а общедоступные доверенные центры сертификации не выпускали сертификаты SHA-1 с 2015 года.

ParseCertificate и ParseCertificateRequest теперь отклоняют сертификаты и CSR, которые содержат повторяющиеся расширения.

Новые методы CertPool.Clone и CertPool.Equal позволяют клонировать CertPool и проверять эквивалентность двух CertPools соответственно.

Новая функция ParseRevocationList обеспечивает более быстрый и безопасный анализатор CRL, который возвращает RevocationList. При разборе CRL также заполняются новые поля RevocationList RawIssuer, Signature, AuthorityKeyId и Extensions, которые игнорируются CreateRevocationList.

Новый метод RevocationList.CheckSignatureFrom проверяет, является ли подпись в CRL действительной подписью сертификата.

Функции ParseCRL и ParseDERCRL теперь объявлены устаревшими в пользу ParseRevocationList. Метод Certificate.CheckCRLSignature устарел в пользу RevocationList.CheckSignatureFrom.

Построитель пути Certificate.Verify был переработан и теперь должен создавать более качественные цепочки и/или быть более эффективным в сложных сценариях. Ограничения имен теперь также применяются к неконечным сертификатам.

crypto/x509/pkix

Типы CertificateList и TBSCertificateList устарели. Вместо этого следует использовать новую функциональность crypto/x509 CRL.

debug/elf

Новые константы EM_LOONGARCH и R_LARCH_* поддерживают порт loong64.

debug/pe

Новый метод File.COFFSymbolReadSectionDefAux, возвращающий COFFSymbolAuxFormat5, обеспечивает доступ к информации COMDAT в разделах PE-файла. Они поддерживаются новыми константами IMAGE_COMDAT_* и IMAGE_SCN_*.

encoding/binary

Новый интерфейс AppendByteOrder предоставляет эффективные методы добавления uint16, uint32 или uint64 к срезу байтов. BigEndian и LittleEndian теперь реализуют этот интерфейс.

Точно так же новые функции AppendUvarint и AppendVarint являются эффективными версиями добавления функций PutUvarint и PutVarint.

encoding/csv

Новый метод Reader.InputOffset сообщает о текущей позиции ввода средства чтения в виде смещения в байтах, аналогично Decoder.InputOffset в encoding/json.

encoding/xml

Новый метод Decoder.InputPos сообщает текущую позицию ввода считывателя в виде строки и столбца, аналогично Decoder.FieldPos из encoding/csv.

flag

Новая функция TextVar определяет флаг со значением, реализующим encoding.TextUnmarshaler, что позволяет переменным флага командной строки иметь такие типы, как big.Int, netip.Addr и time.Time.

fmt

Новые функции Append, Appendf и Appendln добавляют отформатированные данные в байтовые срезы.

go/parser

Синтаксический анализатор теперь распознает ~x как унарное выражение с оператором token.TILDE, что позволяет лучше устранять ошибки, когда ограничение типа, такое как ~int, используется в неправильном контексте.

go/types

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

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

hash/maphash

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

html/template

Тип FuncMap теперь является псевдонимом для FuncMap пакета text/template вместо собственного именованного типа. Это позволяет писать код, который работает с FuncMap при любых настройках.

image/draw

Рисование с оператором Src сохраняет цвета без предварительного умножения альфа-канала, когда целевое и исходное изображения оба имеют формат image.NRGBA или оба изображения image.NRGBA64. Это отменяет изменение поведения, случайно внесенное оптимизацией библиотеки Go 1.18; код теперь соответствует поведению в Go 1.17 и более ранних версиях.

io

Результат NopCloser теперь реализует WriterTo всякий раз, когда это происходит на входе.

Результат MultiReader теперь безоговорочно реализует WriterTo. Если какое-либо базовое средство чтения не реализует WriterTo, оно моделируется соответствующим образом.

mime

Только в Windows пакет mime теперь игнорирует запись реестра о том, что расширение .js должно иметь тип MIME text/plain. Это распространенная непреднамеренная неправильная настройка в системах Windows. В результате .js будет иметь тип MIME text/javascript по умолчанию; кодировка=utf-8. Приложения, которые ожидают text/plain в Windows, теперь должны явно вызывать AddExtensionType.

net

Чистый преобразователь Go теперь будет использовать EDNS(0) для включения рекомендуемой максимальной длины ответного пакета, что позволяет ответным пакетам содержать до 1232 байт (ранее максимальное значение составляло 512). В том маловероятном случае, если это вызовет проблемы с локальным распознавателем DNS, установка переменной среды GODEBUG=netdns=cgo для использования распознавателя на основе cgo должна работать.

Когда функция или метод пакета net возвращает ошибку "I/O timeout", эта ошибка теперь будет удовлетворять errors.Is(err, context.DeadlineExceeded). Когда функция пакета net возвращает ошибку "operation was canceled" ("операция была отменена"), эта ошибка теперь удовлетворяет errors.Is(err, context.Canceled). Эти изменения предназначены для того, чтобы упростить тестирование кода в случаях, когда отмена контекста или таймаут приводят к тому, что функция или метод пакета net возвращает ошибку, сохраняя при этом обратную совместимость для сообщений об ошибках.

Resolver.PreferGo теперь реализован в Windows и Plan 9. Ранее он работал только на платформах Unix. В сочетании с Dialer.Resolver и Resolver.Dial теперь можно писать переносимые программы и контролировать все поиски DNS-имен при подключении.

Пакет net теперь имеет начальную поддержку тега сборки netgo в Windows. При использовании пакет использует DNS-клиент Go (используемый Resolver.PreferGo) вместо того, чтобы запрашивать у Windows результаты DNS. Однако вышестоящий DNS-сервер, который он обнаруживает из Windows, может быть еще неправильным со сложными системными сетевыми конфигурациями.

net/http

ResponseWriter.WriteHeader теперь поддерживает отправку определяемых пользователем информационных заголовков 1xx.

io.ReadCloser, возвращаемый MaxBytesReader, теперь будет возвращать определенный тип ошибки MaxBytesError при превышении предела чтения.

HTTP-клиент будет обрабатывать ответ 3xx без заголовка Location, возвращая его вызывающей стороне, а не рассматривая его как ошибку.

net/url

Новая функция JoinPath и метод URL.JoinPath создают новый URL-адрес путем присоединения к списку элементов пути.

Тип URL-адреса теперь различает URL-адреса без полномочий и URL-адреса с пустыми полномочиями. Например, http:///path имеет пустые права доступа (хост), а в http:/path их нет.

Новое поле URL-адреса OmitHost имеет значение true, если URL-адрес имеет пустые полномочия.

os/exec

Cmd с непустым полем Dir и nil Env теперь неявно устанавливает переменную среды PWD для подпроцесса в соответствие с Dir.

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

reflect

Метод Value.Bytes теперь принимает адресные массивы в дополнение к срезам.

Методы Value.Len и Value.Cap теперь успешно работают с указателем на массив и возвращают длину этого массива, чтобы соответствовать тому, что делают встроенные функции len и cap.

regexp/syntax

Go 1.18 релиз-кандидат 1, Go 1.17.8 и Go 1.16.15 включали исправление безопасности для синтаксического анализатора регулярных выражений, заставляющее его отклонять очень глубоко вложенные выражения. Поскольку релизы исправлений Go не вводят новый API, синтаксический анализатор в этом случае вернул синтаксис.ErrInternalError. В Go 1.19 добавлена ​​более конкретная ошибка синтаксис.ErrNestingDepth, которую теперь возвращает синтаксический анализатор.

runtime

Функция GOROOT теперь возвращает пустую строку (вместо "go"), когда бинарный файл был собран с установленным флагом -trimpath и переменная GOROOT не установлена в среде процесса.

runtime/metrics

Новая метрика /sched/gomaxprocs:threads сообщает о текущем значении runtime.GOMAXPROCS.

Новая метрика /cgo/go-to-c-calls:calls сообщает об общем количестве вызовов, сделанных с Go на C. Эта метрика идентична функции runtime.NumCgoCall.

Новая метрика /gc/limiter/last-enabled:gc-cycle сообщает о последнем цикле сборки мусора, когда был включен ограничитель ЦП сборщика мусора.

runtime/pprof

Время пауз Stop-the-world было значительно сокращено при сборе профилей goroutine, что уменьшило общее влияние задержки на приложение.

MaxRSS теперь сообщается в профилях кучи для всех операционных систем Unix (ранее сообщалось только для GOOS=android, darwin, ios и linux).

runtime/race

Детектор гонок был обновлен для использования санитайзера потоков версии v3 на всех поддерживаемых платформах, кроме windows/amd64 и openbsd/amd64, которые остаются на версии v2. По сравнению с v2, теперь он обычно в 1,5–2 раза быстрее, использует вдвое меньше памяти и поддерживает неограниченное количество горутин. В Linux детектор гонки теперь требует как минимум glibc версии 2.17 и GNU binutils 2.26.

Детектор гонки теперь поддерживается на GOARCH=s390x.

Поддержка детектора гонки для openbsd/amd64 была удалена из вышестоящего санитайзера потоков, поэтому вряд ли оно когда-либо будет обновлено с версии v2.

runtime/trace

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

sort

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

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

strconv

Quote и связанные с ней функции теперь указывают руну U+007F как \x7f, а не \u007f, для совместимости с другими значениями ASCII.

syscall

На PowerPC (GOARCH=ppc64, ppc64le) Syscall, Syscall6, RawSyscall и RawSyscall6 теперь всегда возвращают 0 для возвращаемого значения r2 вместо неопределенного значения.

В AIX и Solaris теперь определен Getrusage.

time

Новый метод Duration.Abs предоставляет удобный и безопасный способ получения абсолютного значения длительности путем преобразования −2⁶³ в 2⁶³−1. (Этот пограничный случай может произойти в результате вычитания недавнего времени из нулевого времени.)

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


Читайте также:


понедельник, 12 сентября 2022 г.

Начало работы с фаззингом в Golang

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

В этом руководстве вы напишете fuzz тест для простой функции, запустите команду go, отладите и исправите проблемы в коде.

Предпосылки

  • Установленный Go 1.18 или более поздней версии.
  • Инструмент для редактирования вашего кода. Любой текстовый редактор, который у вас есть, будет работать нормально.
  • Командный терминал. Go хорошо работает с любым терминалом в Linux и Mac, а также с PowerShell или cmd в Windows.
  • Среда, поддерживающая фаззинг. В настоящее время фаззинг с инструментами покрытия доступен только для архитектур AMD64 и ARM64.

Создайте папку для своего кода

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

1. Откройте командную строку и перейдите в свой домашний каталог.

В Linux или Mac:

$ cd

В Windows:

C:\> cd %HOMEPATH%

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

2. В командной строке создайте каталог для своего кода с именем fuzz.

$ mkdir fuzz
$ cd fuzz

3. Создайте модуль для хранения вашего кода.

Запустите команду go mod init, указав путь к модулю вашего нового кода.

$ go mod init example/fuzz
go: creating new go.mod: module example/fuzz

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

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

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

На этом шаге вы добавите функцию для обращения строки.

Напишите код

1. Используя текстовый редактор, создайте файл с именем main.go в каталоге fuzz.

2. В файл main.go в верхней части файла вставьте следующее объявление пакета.

package main

Автономная программа (в отличие от библиотеки) всегда находится в пакете main.

3. Под объявлением пакета вставьте следующее объявление функции.

func Reverse(s string) string {
    b := []byte(s)
    for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
        b[i], b[j] = b[j], b[i]
    }
    return string(b)
}

Эта функция примет строку, обработает ее побайтно и в конце вернет перевернутую строку.

Примечание. Этот код основан на функции stringutil.Reverse из golang.org/x/example.

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

func main() {
    input := "The quick brown fox jumped over the lazy dog"
    rev := Reverse(input)
    doubleRev := Reverse(rev)
    fmt.Printf("original: %q\n", input)
    fmt.Printf("reversed: %q\n", rev)
    fmt.Printf("reversed again: %q\n", doubleRev)
}

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

5. main функция использует пакет fmt, поэтому вам нужно будет его импортировать.

Первые строки кода должны выглядеть так:

package main

import "fmt"

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
original: "The quick brown fox jumped over the lazy dog"
reversed: "god yzal eht revo depmuj xof nworb kciuq ehT"
reversed again: "The quick brown fox jumped over the lazy dog"

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

Теперь, когда код работает, пришло время его протестировать.

Добавить модульный (юнит) тест

На этом шаге вы напишете базовый модульный тест для функции Reverse.

Напишите код

1. Используя текстовый редактор, создайте файл с именем reverse_test.go в каталоге fuzz.

2. Вставьте следующий код в reverse_test.go.

package main

import (
    "testing"
)

func TestReverse(t *testing.T) {
    testcases := []struct {
        in, want string
    }{
        {"Hello, world", "dlrow ,olleH"},
        {" ", " "},
        {"!12345", "54321!"},
    }
    for _, tc := range testcases {
        rev := Reverse(tc.in)
        if rev != tc.want {
                t.Errorf("Reverse: %q, want %q", rev, tc.want)
        }
    }
}

Этот простой тест подтвердит, что перечисленные входные строки будут правильно инвертированы.

Запустите код

Запустите модульный тест, используя go test

$ go test
PASS
ok      example/fuzz  0.013s

Далее вы измените модульный тест на fuzz тест.

Добавить fuzz тест

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

В этом разделе вы преобразуете модульный тест в fuzz тест, чтобы вы могли генерировать больше входных данных с меньшими затратами!

Обратите внимание, что вы можете хранить модульные тесты, бенчмарки и fuzz тесты в одном и том же файле *_test.go, но в этом примере вы преобразуете модульный тест в fuzz тест.

Напишите код

В текстовом редакторе замените модульный тест в reverse_test.go следующим fuzz тестом.

func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc)  // Используйте f.Add для предоставления начального корпуса (seed corpus)
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev := Reverse(orig)
        doubleRev := Reverse(rev)
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}

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

Например, в тестовом случае Reverse("Hello, world") модульный тест определяет результат как "dlrow,olleH".

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

Однако есть несколько свойств функции Reverse, которые вы можете проверить с помощью fuzz теста. В этом fuzz тесте проверяются два свойства:

  • Двойное обращение строки сохраняет исходное значение
  • Перевернутая строка сохраняет свое состояние как действительное UTF-8.

Обратите внимание на синтаксические различия между модульным тестом и fuzz тестом:

  • Функция начинается с FuzzXxx вместо TestXxx и принимает *testing.F вместо *testing.T.
  • Там, где вы ожидаете увидеть выполнение t.Run, вместо этого вы видите f.Fuzz, который принимает целевую функцию фаззинга с параметрами *testing.T и типами, подлежащими фаззингу. Входные данные из вашего модульного теста предоставляются как входные данные начального корпуса с помощью f.Add.

Убедитесь, что новый пакет unicode/utf8 импортирован.

package main

import (
    "testing"
    "unicode/utf8"
)

Когда модульный тест преобразован в fuzz тест, пришло время снова запустить тест.

Запустите код

1. Запустите fuzz тест без фаззинга, чтобы убедиться, что исходные входные данные пройдены.

$ go test
PASS
ok      example/fuzz  0.013s

Вы также можете запустить go test -run=FuzzReverse, если у вас есть другие тесты в этом файле, и вы хотите запустить только fuzz тест.

2. Запустите FuzzReverse с фаззингом, чтобы увидеть, не приведут ли какие-либо случайно сгенерированные вводы строк к сбою. Это выполняется с помощью go test с новым флагом -fuzz.

$ go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers
fuzz: minimizing 38-byte failing input file...
--- FAIL: FuzzReverse (0.01s)
    --- FAIL: FuzzReverse (0.00s)
        reverse_test.go:20: Reverse produced invalid UTF-8 string "\x9c\xdd"

    Failing input written to testdata/fuzz/FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
    To re-run:
    go test -run=FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
FAIL
exit status 1
FAIL    example/fuzz  0.030s

Во время фаззинга произошел сбой, и ввод, вызвавший проблему, записывается в файл начального корпуса (seed corpus), который будет запущен при следующем вызове go test, даже без флага -fuzz. Чтобы просмотреть ввод, вызвавший сбой, откройте файл корпуса, записанный в каталог testdata/fuzz/FuzzReverse, в текстовом редакторе. Ваш файл исходного корпуса может содержать другую строку, но формат будет таким же.

go test fuzz v1
string("泃")

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

3. Снова запустите go test без флага -fuzz; будет использоваться новая ошибочная запись корпуса исходных данных:

$ go test
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a (0.00s)
        reverse_test.go:20: Reverse produced invalid string
FAIL
exit status 1
FAIL    example/fuzz  0.016s

Поскольку наш тест провалился, пришло время отладки.

Исправить ошибку неверной строки

В этом разделе вы отладите сбой и исправите ошибку.

Диагностируйте ошибку

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

В этом руководстве мы будем регистрировать полезную информацию об отладке на вашем терминале.

Во-первых, рассмотрим документацию для utf8.ValidString.

ValidString сообщает, состоит ли s полностью из допустимых рун в кодировке UTF-8.

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

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

Напишите код

В текстовом редакторе замените цель fuzz в FuzzReverse следующим.

f.Fuzz(func(t *testing.T, orig string) {
    rev := Reverse(orig)
    doubleRev := Reverse(rev)
    t.Logf("Number of runes: orig=%d, rev=%d, doubleRev=%d", utf8.RuneCountInString(orig), utf8.RuneCountInString(rev), utf8.RuneCountInString(doubleRev))
    if orig != doubleRev {
        t.Errorf("Before: %q, after: %q", orig, doubleRev)
    }
    if utf8.ValidString(orig) && !utf8.ValidString(rev) {
        t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
    }
})

Здесь строка t.Logf будет выводиться в командную строку в случае возникновения ошибки или при выполнении теста с ключом -v, что может помочь вам отладить эту конкретную проблему.

Запустите код

Запустите тест, используя go test

$ go test
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)
        reverse_test.go:16: Number of runes: orig=1, rev=3, doubleRev=1
        reverse_test.go:21: Reverse produced invalid UTF-8 string "\x83\xb3\xe6"
FAIL
exit status 1
FAIL    example/fuzz    0.598s

Во всем начальном корпусе (seed corpus) использовались строки, в которых каждый символ представлял собой один байт. Однако для таких символов, как 泃, может потребоваться несколько байтов. Таким образом, обращение строки байт за байтом сделает недействительными многобайтовые символы.

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

Лучше поняв ошибку, исправьте ошибку в функции Reverse.

Исправьте ошибку

Чтобы исправить функцию Reverse, пройдемся по строке по рунам, а не по байтам.

Напишите код

В текстовом редакторе замените существующую функцию Reverse() следующей.

func Reverse(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

Ключевое отличие состоит в том, что Reverse теперь перебирает каждую руну в строке, а не каждый байт.

Запустите код

Запустите тест, используя go test

$ go test
PASS
ok      example/fuzz  0.016s

Теперь тест проходит!

Еще раз проверьте его с помощью go test -fuzz, чтобы увидеть, есть ли какие-либо новые ошибки.

$ go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/37 completed
fuzz: minimizing 506-byte failing input file...
fuzz: elapsed: 0s, gathering baseline coverage: 5/37 completed
--- FAIL: FuzzReverse (0.02s)
    --- FAIL: FuzzReverse (0.00s)
        reverse_test.go:33: Before: "\x91", after: "�"

    Failing input written to testdata/fuzz/FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c
    To re-run:
    go test -run=FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c
FAIL
exit status 1
FAIL    example/fuzz  0.032s

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

Давайте снова отладим.

Исправить ошибку двойного реверса

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

Диагностируйте ошибку

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

В этом руководстве мы будем регистрировать полезную информацию об отладке в функции Reverse.

Посмотрите внимательно на перевернутую строку, чтобы найти ошибку. В Go строка представляет собой срез байтов, доступный только для чтения, и может содержать байты, которые не являются допустимыми UTF-8. Исходная строка представляет собой срез байтов с одним байтом, '\x91'. Когда входная строка имеет значение []rune, Go кодирует байтовый срез в UTF-8 и заменяет байт символом UTF-8 �. Когда мы сравниваем замещающий символ UTF-8 с входным байтовым срезом, они явно не равны.

Напишите код

В текстовом редакторе замените функцию Reverse на следующую.

func Reverse(s string) string {
    fmt.Printf("input: %q\n", s)
    r := []rune(s)
    fmt.Printf("runes: %q\n", r)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

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

Запустите код

На этот раз мы хотим запустить только неудачный тест, чтобы проверить логи. Для этого мы будем использовать go test -run.

$ go test -run=FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0
input: "\x91"
runes: ['�']
input: "�"
runes: ['�']
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)
        reverse_test.go:16: Number of runes: orig=1, rev=1, doubleRev=1
        reverse_test.go:18: Before: "\x91", after: "�"
FAIL
exit status 1
FAIL    example/fuzz    0.145s

Чтобы запустить конкретную запись корпуса в FuzzXxx/testdata, вы можете указать {FuzzTestName}/{filename} для -run. Это может быть полезно при отладке.

Зная, что ввод является недопустимым юникодом, давайте исправим ошибку в нашей функции Reverse.

Исправьте ошибку

Чтобы решить эту проблему, давайте вернем ошибку, если ввод в Reverse недействителен в кодировке UTF-8.

Напишите код

В текстовом редакторе замените существующую функцию Reverse на следующую.

func Reverse(s string) (string, error) {
    if !utf8.ValidString(s) {
        return s, errors.New("input is not valid UTF-8")
    }
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r), nil
}

Это изменение вернет ошибку, если входная строка содержит символы, недопустимые в кодировке UTF-8.

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

func main() {
    input := "The quick brown fox jumped over the lazy dog"
    rev, revErr := Reverse(input)
    doubleRev, doubleRevErr := Reverse(rev)
    fmt.Printf("original: %q\n", input)
    fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
    fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
}

Эти вызовы Reverse должны возвращать нулевую ошибку, поскольку входная строка является допустимой UTF-8.

Вам нужно будет импортировать пакеты errors и unicode/utf8. Оператор импорта в main.go должен выглядеть следующим образом.

import (
    "errors"
    "fmt"
    "unicode/utf8"
)

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

func FuzzReverse(f *testing.F) {
    testcases := []string {"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc)  //  Используйте f.Add для предоставления начального корпуса (seed corpus)
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev, err1 := Reverse(orig)
        if err1 != nil {
            return
        }
        doubleRev, err2 := Reverse(rev)
        if err2 != nil {
             return
        }
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}

Вместо возврата вы также можете вызвать t.Skip(), чтобы остановить выполнение этого fuzz ввода.

Запустите код

Запустите тест, используя go test

$ go test
PASS
ok      example/fuzz  0.019s

Фаззите с помощью go test -fuzz=Fuzz, затем через несколько секунд прекратите фаззинг, нажав Ctrl-C.

$ go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/38 completed
fuzz: elapsed: 0s, gathering baseline coverage: 38/38 completed, now fuzzing with 4 workers
fuzz: elapsed: 3s, execs: 86342 (28778/sec), new interesting: 2 (total: 35)
fuzz: elapsed: 6s, execs: 193490 (35714/sec), new interesting: 4 (total: 37)
fuzz: elapsed: 9s, execs: 304390 (36961/sec), new interesting: 4 (total: 37)
...
fuzz: elapsed: 3m45s, execs: 7246222 (32357/sec), new interesting: 8 (total: 41)
^Cfuzz: elapsed: 3m48s, execs: 7335316 (31648/sec), new interesting: 8 (total: 41)
PASS
ok      example/fuzz  228.000s

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

Проведите фаззинг с помощью go test -fuzz=Fuzz -fuzztime 30s, который будет фаззить в течение 30 секунд перед выходом, если сбоев не обнаружено.

$ go test -fuzz=Fuzz -fuzztime 30s
fuzz: elapsed: 0s, gathering baseline coverage: 0/5 completed
fuzz: elapsed: 0s, gathering baseline coverage: 5/5 completed, now fuzzing with 4 workers
fuzz: elapsed: 3s, execs: 80290 (26763/sec), new interesting: 12 (total: 12)
fuzz: elapsed: 6s, execs: 210803 (43501/sec), new interesting: 14 (total: 14)
fuzz: elapsed: 9s, execs: 292882 (27360/sec), new interesting: 14 (total: 14)
fuzz: elapsed: 12s, execs: 371872 (26329/sec), new interesting: 14 (total: 14)
fuzz: elapsed: 15s, execs: 517169 (48433/sec), new interesting: 15 (total: 15)
fuzz: elapsed: 18s, execs: 663276 (48699/sec), new interesting: 15 (total: 15)
fuzz: elapsed: 21s, execs: 771698 (36143/sec), new interesting: 15 (total: 15)
fuzz: elapsed: 24s, execs: 924768 (50990/sec), new interesting: 16 (total: 16)
fuzz: elapsed: 27s, execs: 1082025 (52427/sec), new interesting: 17 (total: 17)
fuzz: elapsed: 30s, execs: 1172817 (30281/sec), new interesting: 17 (total: 17)
fuzz: elapsed: 31s, execs: 1172817 (0/sec), new interesting: 17 (total: 17)
PASS
ok      example/fuzz  31.025s

Фаззинг пройден!

Завершенный код — main.go —

package main

import (
    "errors"
    "fmt"
    "unicode/utf8"
)

func main() {
    input := "The quick brown fox jumped over the lazy dog"
    rev, revErr := Reverse(input)
    doubleRev, doubleRevErr := Reverse(rev)
    fmt.Printf("original: %q\n", input)
    fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
    fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
}

func Reverse(s string) (string, error) {
    if !utf8.ValidString(s) {
        return s, errors.New("input is not valid UTF-8")
    }
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r), nil
}

— reverse_test.go —

package main

import (
    "testing"
    "unicode/utf8"
)

func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc) // Use f.Add to provide a seed corpus
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev, err1 := Reverse(orig)
        if err1 != nil {
            return
        }
        doubleRev, err2 := Reverse(rev)
        if err2 != nil {
            return
        }
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}


Читайте также:


пятница, 20 мая 2022 г.

Начало работы с дженериками в Golang

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

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

Предпосылки

  • Установка Go 1.18 или более поздней версии.
  • Инструмент для редактирования вашего кода. Любой текстовый редактор, который у вас есть, будет работать нормально.
  • Командный терминал. Go хорошо работает с любым терминалом в Linux и Mac, а также с PowerShell или cmd в Windows.

Создайте папку для своего кода

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

1. Откройте командную строку и перейдите в свой домашний каталог.

В Linux или Mac:

$ cd

В Windows:

C:\> cd %HOMEPATH%

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

2. В командной строке создайте каталог для своего кода с именем generics.

$ mkdir generics
$ cd generics

3. Создайте модуль для хранения вашего кода.

Запустите команду инициализации go mod, указав путь к модулю вашего нового кода.

$ go mod init example/generics
go: creating new go.mod: module example/generics

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

Далее вы добавите простой код для работы с картами.

Добавьте неуниверсальные функции

На этом шаге вы добавите две функции, каждая из которых суммирует значения карты и возвращает итог.

Вы объявляете две функции вместо одной, потому что работаете с картами двух разных типов: в одной хранятся значения int64, а в другой хранятся значения float64.

Напишите код

1. Используя текстовый редактор, создайте файл с именем main.go в каталоге generics. Вы будете писать свой код Go в этом файле.

2. В файл main.go в верхней части файла вставьте следующее объявление пакета.

package main

Автономная программа (в отличие от библиотеки) всегда находится в пакете main.

3. Под объявлением пакета вставьте следующие два объявления функций.

// SumInts суммирует значения m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats суммирует значения m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

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

SumFloats преобразует строку в значения float64.

SumInts преобразует строку в значения int64.

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

func main() {
    // Инициализировать карту для целочисленных значений
    ints := map[string]int64{
        "first":  34,
        "second": 12,
    }

    // Инициализируем карту для значений с плавающей запятой
    floats := map[string]float64{
        "first":  35.98,
        "second": 26.99,
    }

    fmt.Printf("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))
}

В этом коде вы:

  • Инициализируете карту значений float64 и карту значений int64, каждая с двумя записями.
  • Вызывате две объявленные ранее функции, чтобы найти сумму значений каждой карты.
  • Распечатываете результат.

5. В верхней части main.go, прямо под объявлением пакета, импортируйте пакет, который вам понадобится для поддержки кода, который вы только что написали.

Первые строки кода должны выглядеть так:

package main

import "fmt"

6. Сохраните main.go.

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Non-Generic Sums: 46 and 62.97

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

Добавьте общую функцию для обработки нескольких типов

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

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

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

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

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

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

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

Напишите код

1. Под двумя функциями, которые вы добавили ранее, вставьте следующую общую функцию.

// SumIntsOrFloats суммирует значения карты m.
// Поддерживает как int64, так и float64 
// как типы для значений карты.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

В этом коде вы:

  • Объявите функцию SumIntsOrFloats с двумя параметрами типа (в квадратных скобках), K и V, и одним аргументом, который использует параметры типа, m типа map[K]V. Функция возвращает значение типа V.
  • Укажете для параметра типа K сопоставимое ограничение типа. Предназначенное специально для таких случаев, сопоставимое ограничение уже объявлено в Go. Он допускает любой тип, значения которого можно использовать в качестве операнда операторов сравнения == и !=. Go требует, чтобы ключи сопоставления были сопоставимы. Поэтому необходимо объявить K сопоставимым, чтобы вы могли использовать K в качестве ключа в переменной карты. Это также гарантирует, что вызывающий код использует допустимый тип для ключей сопоставления.
  • Укажете для параметра типа V ограничение, представляющее собой объединение двух типов: int64 и float64. Использование | задает объединение двух типов, что означает, что это ограничение допускает любой тип. Любой тип будет разрешен компилятором в качестве аргумента в вызывающем коде.
  • Укажете, что аргумент m имеет тип map[K]V, где K и V — это типы, уже указанные для параметров типа. Обратите внимание, что мы знаем, что map[K]V является допустимым типом карты, потому что K является сопоставимым типом. Если бы мы не объявили K сопоставимым, компилятор отклонил бы ссылку на map[K]V.

2. В main.go под уже имеющимся кодом вставьте следующий код.

fmt.Printf("Generic Sums: %v and %v\n",
    SumIntsOrFloats[string, int64](ints),
    SumIntsOrFloats[string, float64](floats))

В этом коде вы:

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

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97

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

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

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

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

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

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

Напишите код

В main.go под уже имеющимся кодом вставьте следующий код.

fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
    SumIntsOrFloats(ints),
    SumIntsOrFloats(floats))

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

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97

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

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

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

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

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

Напишите код

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

type Number interface {
    int64 | float64
}

В этом коде вы:

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

2. Под уже имеющимися функциями вставьте следующую общую функцию SumNumbers.

// SumNumbers суммирует значения карты m. 
// Поддерживает целые числа и 
// числа с плавающей точкой как значения карты.
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

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

3. В main.go под уже имеющимся кодом вставьте следующий код.

fmt.Printf("Generic Sums with Constraint: %v and %v\n",
    SumNumbers(ints),
    SumNumbers(floats))

В этом коде вы вызовете SumNumbers с каждой картой, выводя сумму из значений каждой.

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

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97
Generic Sums with Constraint: 46 and 62.97

Завершенный код

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

package main

import "fmt"

type Number interface {
    int64 | float64
}

func main() {
    // Инициализируем карту для целочисленных значений
    ints := map[string]int64{
        "first": 34,
        "second": 12,
    }

    // Инициализируем карту для значений с плавающей точкой
    floats := map[string]float64{
        "first": 35.98,
        "second": 26.99,
    }

    fmt.Printf("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))

    fmt.Printf("Generic Sums: %v and %v\n",
        SumIntsOrFloats[string, int64](ints),
        SumIntsOrFloats[string, float64](floats))

    fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
        SumIntsOrFloats(ints),
        SumIntsOrFloats(floats))

    fmt.Printf("Generic Sums with Constraint: %v and %v\n",
        SumNumbers(ints),
        SumNumbers(floats))
}

// SumInts суммирует значения m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats суммирует значения m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

// SumIntsOrFloats суммирует значения карты m. 
// Поддерживает как числа с плавающей запятой, так и целые числа
// как значения карты.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

// SumNumbers суммирует значения карты m. 
// Поддерживает как числа с плавающей запятой, так и целые числа
// как значения карты.
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

Вывод:

Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97
Generic Sums with Constraint: 46 and 62.97


Читайте также:


понедельник, 2 мая 2022 г.

Введение в использование дженериков в Golang

В релизе Go 1.18 добавлена поддержка дженериков. Дженерики — это самое большое изменение, которое было внесено в Go с момента первого релиза с открытым исходным кодом. В этом посте вы познакомитесь с новыми функциями языка. Мы не будем пытаться охватить все детали, но затронем все важные моменты.

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

Дженерики добавляют в язык три новых важных вещи:

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

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

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

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

func Min(x, y float64) float64 {
    if x < y {
        return x
    }
    return y
}

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

import "golang.org/x/exp/constraints"

func GMin[T constraints.Ordered](x, y T) T {
    if x < y {
        return x
    }
    return y
}

Теперь можно вызвать эту функцию с аргументом типа, написав вызов вида

x := GMin[int](2, 3)

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

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

fmin := GMin[float64]
m := fmin(2.71, 3.14)

экземпляр GMin[float64] создает то, что фактически является нашей исходной функцией Min с плавающей запятой, и мы можем использовать это в вызове функции.

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

type Tree[T interface{}] struct {
    left, right *Tree[T]
    value       T
}

func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }

var stringTree Tree[string]

Здесь дерево универсального типа хранит значения параметра типа T. Универсальные типы могут иметь методы, такие как Lookup в этом примере. Чтобы использовать универсальный тип, необходимо создать его экземпляр; Tree[string] — это пример создания экземпляра Tree с типом аргумента string.

Наборы типов

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

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

Точно так же наборы параметров типа имеют тип для каждого параметра типа. Поскольку параметр типа сам по себе является типом, типы параметров типа определяют наборы типов. Этот метатип называется ограничением типа (type constraint).

В универсальном GMin ограничение типа импортируется из пакета constraints. Ограничение Ordered описывает набор всех типов со значениями, которые можно упорядочить или, другими словами, сравнить с оператором < (или <= , > и т. д.). Ограничение гарантирует, что в GMin могут быть переданы только типы с упорядочиваемыми значениями. Это также означает, что в теле функции GMin значения параметра этого типа могут использоваться для сравнения с оператором <.

В Go ограничения типов должны быть интерфейсами. То есть тип интерфейса может использоваться как тип значения, а также может использоваться как метатип. Интерфейсы определяют методы, поэтому очевидно, что мы можем выразить ограничения типов, которые требуют наличия определенных методов. Но constraints.Ordered — это тоже интерфейсный тип, а оператор < не является методом.

Чтобы это работало, мы смотрим на интерфейсы по-новому.

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

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

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

Синтаксис типов интерфейса был расширен, чтобы это работало. Например, interface{ int|string|bool } определяет набор типов, содержащий типы int, string и bool.

Другими словами, этому интерфейсу удовлетворяют только int, string или bool.

Теперь давайте посмотрим на фактическое определение contraints.Ordered:

type Ordered interface {
    Integer|Float|~string
}

В этом объявлении говорится, что интерфейс Ordered представляет собой набор всех типов целых чисел, чисел с плавающей запятой и строк. Вертикальная черта обозначает объединение типов (или наборов типов в данном случае). Integer и Float — это типы интерфейса, которые аналогичным образом определены в пакете contraints. Обратите внимание, что нет методов, определенных интерфейсом Ordered.

Для ограничений типа мы обычно не заботимся о конкретном типе, таком как string; нас интересуют все типы строк. Именно для этого используется токен ~. Выражение ~string означает набор всех типов, базовым типом которых является string. Это включает в себя сам тип string, а также все типы, объявленные с такими определениями, как type MyString string.

Конечно, мы по-прежнему хотим указывать методы в интерфейсах, и мы хотим быть обратно совместимыми. В Go 1.18 интерфейс может содержать методы и встроенные интерфейсы, как и раньше, но он также может включать неинтерфейсные типы, объединения (union) и наборы базовых типов.

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

Интерфейсам, используемым в качестве ограничений, могут быть присвоены имена (например, Ordered), или они могут быть литеральными интерфейсами, встроенными в список параметров типа. Например:

[S interface{~[]E}, E interface{}]

Здесь S должен быть типом среза, тип элемента которого может быть любым.

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

[S ~[]E, E interface{}]

Поскольку пустой интерфейс распространен в списках параметров типа и в обычном коде Go, в этом отношении Go 1.18 вводит новый предварительно объявленный идентификатор any в качестве псевдонима для пустого типа интерфейса. При этом мы приходим к этому идиоматическому коду:

[S ~[]E, E any]

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

Вывод типа

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

Вывод типа аргумента функции

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

func GMin[T constraints.Ordered](x, y T) T { ... }

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

var a, b, m float64

m = GMin[float64](a, b) // явный аргумент типа

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

var a, b, m float64

m = GMin(a, b) // нет аргумента типа

Это работает путем сопоставления типов аргументов a и b с типами параметров x и y.

Этот тип вывода, который выводит аргументы типа из типов аргументов функции, называется выводом типа аргумента функции.

Вывод типа аргумента функции работает только для параметров типа, которые используются в параметрах функции, а не для параметров типа, используемых только в результатах функции или только в теле функции. Например, это не относится к таким функциям, как MakeT[T any]() T, которые используют только T для результата.

Вывод типа ограничения

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

// Scale возвращает копию s с каждым элементом, умноженным на c.
// У этой реализации есть проблема, как мы увидим.
func Scale[E constraints.Integer](s []E, c E) []E {
    r := make([]E, len(s))
    for i, v := range s {
        r[i] = v * c
    }
    return r
}

Это общая функция, которая работает для среза любого целочисленного типа.

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

type Point []int32

func (p Point) String() string {
    // Детали не важны.
}

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

// ScaleAndPrint удваивает Point и печатает ее.
func ScaleAndPrint(p Point) {
    r := Scale(p, 2)
    fmt.Println(r.String()) // НЕ КОМПИЛИРУЕТСЯ
}

К сожалению, это не компилируется, выдавая ошибку типа r.String undefined (тип []int32 не имеет поля или метода String).

Проблема в том, что функция Scale возвращает значение типа []E, где E — тип элемента среза аргумента. Когда мы вызываем Scale со значением типа Point, базовым типом которого является []int32, мы возвращаем значение типа []int32, а не типа Point. Это следует из того, как написан универсальный код, но это не то, что нам нужно.

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

// Scale возвращает копию s с каждым элементом, умноженным на c.
func Scale[S ~[]E, E constraints.Integer](s S, c E) S {
    r := make(S, len(s))
    for i, v := range s {
        r[i] = v * c
    }
    return r
}

Мы ввели новый параметр типа S, который является типом аргумента среза. Мы наложили на него ограничения таким образом, что базовым типом является S, а не []E, а тип результата теперь равен S. Поскольку E ограничено целым числом, эффект тот же, что и раньше: первый аргумент должен быть срез некоторого целочисленного типа. Единственное изменение в теле функции заключается в том, что теперь мы передаем S, а не []E, когда вызываем make.

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

Но справедливо спросить: почему можно написать вызов Scale без передачи явных аргументов типа? То есть, почему мы можем писать Scale(p, 2) без аргументов типа вместо того, чтобы писать Scale[Point, int32](p, 2)? Наша новая функция Scale имеет два параметра типа, S и E. При вызове Scale без передачи каких-либо аргументов типа вывод типа аргумента функции, описанный выше, позволяет компилятору сделать вывод, что аргумент типа для S — это Point. Но функция также имеет параметр типа E, который является типом коэффициента умножения c. Соответствующий аргумент функции равен 2, и поскольку 2 является нетипизированной константой, вывод типа аргумента функции не может вывести правильный тип для E (в лучшем случае он может вывести тип по умолчанию для 2, который является int и который будет неправильным). Вместо этого процесс, посредством которого компилятор делает вывод о том, что аргумент типа для E является типом элемента среза, называется выводом типа ограничения.

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

Обычно это применяется, когда одно ограничение использует форму ~type для некоторого типа, где этот тип записывается с использованием других параметров типа. Мы видим это в примере Scale. S — это ~[]E, за которым следует тип []E, записанный в терминах другого параметра типа. Если мы знаем аргумент типа для S, мы можем вывести аргумент типа для E. S — это тип среза, а E — тип элемента этого среза.

Вывод типа на практике

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

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

Заключение

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


Читайте также:


пятница, 8 апреля 2022 г.

Релиз Go 1.18

Релиз Go, версия 1.18, является важным релизом, включающим изменения в языке, реализации цепочки инструментов, среды выполнения и библиотек. Go 1.18 выходит через семь месяцев после Go 1.17. Как всегда, релиз поддерживает обещание совместимости Go 1. Ожидается, что почти все программы Go продолжат компилироваться и работать, как и прежде.

Изменения в языке


Дженерики

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

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

Хотя считается, что новые функции языка хорошо разработаны и четко определены, возможно, допущены ошибки. Важно подчеркнуть, что гарантия совместимости с Go 1 гласит: "Если возникает необходимость устранить несоответствие или неполноту в спецификации, решение проблемы может повлиять на смысл или законность существующих программ. Мы оставляем за собой право решать такие проблемы, включая обновление реализации". В нем также говорится: "Если в компиляторе или библиотеке есть ошибка, нарушающая спецификацию, программа, зависящая от ошибочного поведения, может сломаться, если ошибка будет исправлена. Мы оставляем за собой право исправлять такие ошибки". Другими словами, возможно, что будет код, использующий дженерики, который будет работать с релизом 1.18, но сломается в более поздних релизах. Не планируется и не ожидается подобных изменений. Однако поломка программ 1.18 в будущих релизах может оказаться необходимой по причинам, которые сегодня не возможно предвидеть. Будет сведена к минимуму любая такая поломка, но не возможно гарантировать, что ее не будет совсем.

Ниже приведен список наиболее заметных изменений.

  • Синтаксис объявлений функций и типов теперь принимает параметры типа.
  • Параметризованные функции и типы могут быть созданы, если за ними следует список аргументов типа в квадратных скобках.
  • В набор операторов и знаков препинания добавлен новый токен ~.
  • Синтаксис типов интерфейсов теперь позволяет встраивать произвольные типы (а не только имена типов интерфейсов), а также элементы типа union и ~T. Такие интерфейсы могут использоваться только как ограничения типа. Интерфейс теперь определяет набор типов, а также набор методов.
  • Новый предварительно объявленный идентификатор any является псевдонимом для пустого интерфейса. Его можно использовать вместо interface{}.
  • Новый предварительно объявленный идентификатор compare — это интерфейс, обозначающий набор всех типов, которые можно сравнивать с помощью == или !=. Его можно использовать только как (или встроенное) ограничение типа.

Есть три экспериментальных пакета, использующих дженерики, которые могут оказаться полезными. Эти пакеты находятся в репозитории x/exp; их API не покрывается гарантией Go 1 и может меняться по мере того, как накапливается больше опыта работы с дженериками.

golang.org/x/exp/constraints
Ограничения, полезные для универсального кода, такие как constraints.Ordered.

golang.org/x/exp/slices
Набор универсальных функций, которые работают со срезами элементов любого типа.

golang.org/x/exp/maps
Набор универсальных функций, которые работают с картами любого типа ключа или элемента.

Текущая реализация дженериков имеет следующие известные ограничения:

  • Компилятор Go не может обрабатывать объявления типов внутри универсальных функций или методов. Планируется обеспечить поддержку этой функции в Go 1.19.
  • Компилятор Go не принимает аргументы типа параметра типа с предварительно объявленными функциями real, imag и complex. Планируется убрать это ограничение в Go 1.19.
  • Компилятор Go поддерживает только вызов метода m для значения x типа параметра типа P, если m явно объявлен интерфейсом ограничения P. Точно так же значения метода x.m и выражения метода P.m также поддерживаются только в том случае, если m явно объявлен P, даже если m может находиться в наборе методов P в силу того факта, что все типы в P реализуют m. Планируется убрать это ограничение в Go 1.19.
  • Компилятор Go не поддерживает доступ к полю структуры x.f, где x имеет тип параметра типа, даже если все типы в наборе типов параметра типа имеют поле f. Планируется убрать это ограничение в Go 1.19.
  • Встраивание параметра типа или указателя на параметр типа в качестве безымянного поля в типе структуры не допускается. Точно так же не допускается встраивание параметра типа в тип интерфейса. Будут ли они когда-либо разрешены, в настоящее время неясно.
  • Элемент union с более чем одним термином не может содержать тип интерфейса с непустым набором методов. Будет ли это когда-либо разрешено, пока неясно.

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

Исправление ошибок

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

Компилятор Go 1.18 теперь сообщает о переполнении при передаче константного выражения руны, такого как '1' << 32, в качестве аргумента предварительно объявленным функциям print и println, что соответствует поведению пользовательских функций. До Go 1.18 компилятор не сообщал об ошибке в таких случаях, а молча принимал такие константные аргументы, если они соответствовали типу int64. В результате этого изменения (возможно, некорректные) программы могут больше не компилироваться. Необходимое исправление простое: исправить программу, если она на самом деле была неправильной, или явно преобразовать неверный аргумент в правильный тип. Поскольку go vet всегда указывал на эту ошибку, количество затронутых программ, вероятно, очень мало.

Порты


AMD64

Go 1.18 представляет новую переменную среды GOAMD64, которая выбирает во время компиляции минимальную целевую версию архитектуры AMD64. Допустимые значения: v1, v2, v3 или v4. Каждый более высокий уровень требует и использует дополнительные функции процессора.

Переменная среды GOAMD64 по умолчанию имеет значение v1.

RISC-V

64-разрядная архитектура RISC-V в Linux (порт linux/riscv64) теперь поддерживает режимы сборки c-archive и c-shared.

Linux

Для Go 1.18 требуется ядро Linux версии 2.6.32 или выше.

Windows

Порты windows/arm и windows/arm64 теперь поддерживают некооперативное вытеснение, предоставляя эту возможность всем четырем портам Windows, что должно исправить тонкие ошибки, возникающие при вызове функций Win32, которые блокируются в течение длительных периодов времени.

iOS

На iOS (порт ios/arm64) и симуляторе iOS, работающем на macOS на базе AMD64 (порт ios/amd64), для Go 1.18 теперь требуется iOS 12 или новее; поддержка предыдущих версий прекращена.

FreeBSD

Go 1.18 — это последний выпуск, поддерживаемый FreeBSD 11.x, срок службы которого уже истек. Go 1.19 потребует FreeBSD 12.2+ или FreeBSD 13.0+. Для FreeBSD 13.0+ потребуется ядро с установленным параметром COMPAT_FREEBSD12 (это значение по умолчанию).

Инструменты


Фаззинг

Go 1.18 включает в себя реализацию фаззинга.

Имейте в виду, что фаззинг может потреблять много памяти и может повлиять на производительность вашей машины во время ее работы. Также имейте в виду, что механизм фаззинга записывает значения, которые расширяют тестовое покрытие, в каталог кэша фаззинга в $GOCACHE/fuzz во время его работы. В настоящее время нет ограничений на количество файлов или общее количество байтов, которые могут быть записаны в нечеткий кэш, поэтому он может занимать большой объем памяти (возможно, несколько ГБ).

Команда go


go get

go get больше не собирает и не устанавливает пакеты в режиме с поддержкой модулей. go get теперь предназначен для настройки зависимостей в go.mod. По сути, флаг -d всегда включен. Чтобы установить последнюю версию исполняемого файла вне контекста текущего модуля, используйте go install example.com/cmd@latest. Вместо последней версии можно использовать запрос любой версии. Эта форма go install была добавлена ​​в Go 1.16, поэтому в проектах, поддерживающих более старые версии, может потребоваться предоставить инструкции по установке как для go install, так и для go get. go get теперь сообщает об ошибке при использовании вне модуля, так как нет файла go.mod для обновления. В режиме GOPATH (с GO111MODULE=off) go get по-прежнему собирает и устанавливает пакеты, как и раньше.

Автоматические обновления go.mod и go.sum

go mod graph, go mod vendor, go mod verify и go mod why подкоманды больше не обновляют автоматически файлы go.mod и go.sum. (Эти файлы можно явно обновить с помощью go get, go mod tidy или go mod download.)

go version

Команда go теперь встраивает информацию о контроле версий в двоичные файлы. Она включает текущую проверенную версию, время коммита и флаг, указывающий, присутствуют ли отредактированные или неотслеживаемые файлы. Информация о контроле версий встраивается, если команда go вызывается в каталоге в репозитории Git, Mercurial, Fossil или Bazaar, а main пакет и содержащий его main модуль находятся в одном репозитории. Эту информацию можно опустить, используя флаг -buildvcs=false.

Кроме того, команда go встраивает информацию о сборке, включая теги сборки и инструмента (установленные с помощью -tags), флаги компилятора, ассемблера и компоновщика (например, -gcflags), включено ли cgo, и если да, то значения переменные среды cgo (например, CGO_CFLAGS). Информация о VCS и сборке может быть прочитана вместе с информацией о модуле с помощью go version -m file или runtime/debug.ReadBuildInfo (для текущего исполняемого бинарного файла) или нового пакета debug/buildinfo.

Базовый формат данных встроенной информации о сборке может меняться с новыми релизами go, поэтому более старая версия go может не обрабатывать информацию о сборке, созданную с помощью более новой версии go. Чтобы прочитать информацию о версии из двоичного файла, созданного с помощью go 1.18, используйте команду go version и пакет debug/buildinfo из go 1.18+.

go mod download

Если в файле go.mod основного модуля указана версия go 1.17 или выше, go mod download без аргументов теперь загружает исходный код только для тех модулей, которые явно требуются в файле go.mod основного модуля. (В go 1.17 или более поздней версии в модуле этот набор уже включает все зависимости, необходимые для сборки пакетов и тестов в основном модуле.) Чтобы также загрузить исходный код для транзитивных зависимостей, используйте go mod download all.

go mod vendor

Подкоманда go mod vendor теперь поддерживает флаг -o для установки выходного каталога. (Другие команды go по-прежнему считываются из каталога vendor в корневом каталоге модуля при загрузке пакетов с параметром -mod=vendor, поэтому в основном этот флаг используется для сторонних инструментов, которым необходимо собирать исходный код пакета.)

go mod tidy

Команда go mod tidy теперь сохраняет дополнительные контрольные суммы в файле go.sum для модулей, исходный код которых необходим для проверки того, что каждый импортированный пакет предоставляется только одним модулем в списке сборки. Поскольку это условие встречается редко и его несоблюдение приводит к ошибке сборки, это изменение не зависит от версии go в файле go.mod основного модуля.

go work

Команда go теперь поддерживает режим "Workspace" ("Рабочее пространство"). Если файл go.work найден в рабочем каталоге или родительском каталоге, или он указан с помощью переменной среды GOWORK, команда go будет переведена в режим рабочей области (workspace mode). В режиме рабочей области (workspace mode) файл go.work будет использоваться для определения набора основных модулей, используемых в качестве корней для разрешения модулей, вместо использования обычно находящегося файла go.mod для указания одного основного модуля.

go build -asan

Команда go build и связанные с ней команды теперь поддерживают флаг -asan, который позволяет взаимодействовать с кодом C (или C++), скомпилированным с помощью address sanitizer (параметр компилятора C -fsanitize=address).

go test

Команда go теперь поддерживает дополнительные параметры командной строки для новой поддержки фаззинга:

  • go test поддерживает параметры -fuzz, -fuzztime и -fuzzminimizetime.
  • go clean поддерживает параметр -fuzzcache.

//go:build строки

Go 1.17 представил строки //go:build как более читаемый способ записи ограничений сборки вместо строк // +build. Начиная с Go 1.17, gofmt добавляет строки //go:build в соответствие с существующими строками +build и поддерживает их синхронизацию, в то время как go vet диагностирует, когда они не синхронизированы.

Поскольку релиз Go 1.18 знаменует собой прекращение поддержки Go 1.16, все поддерживаемые версии Go теперь понимают строки //go:build. В Go 1.18 go fix теперь удаляет устаревшие строки // +build в модулях, объявляющих go 1.17 или более позднюю версию в своих файлах go.mod.

gofmt

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

Vet


Обновления для дженериков

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

func Print[T ~int|~string](t T) {
    fmt.Printf("%d", t)
}

потому что он сообщит об ошибке формата в неуниверсальном эквиваленте Print[string]:

func PrintString(x string) {
    fmt.Printf("%d", x)
}

Улучшение точности для существующих проверок

Средства проверки cmd/vet - copylock, printf, sortslice, testinggoroutine - и тесты имеют умеренные улучшения точности для обработки дополнительных шаблонов кода. Это может привести к новым сообщениям об ошибках в существующих пакетах. Например, средство проверки printf теперь отслеживает строки форматирования, созданные путем объединения строковых констант. Поэтому vet сообщит об ошибке в:

// fmt.Printf formatting directive %d is being passed to Println.
// директива форматирования fmt.Printf %d передается в Println.
fmt.Println("%d"+` ≡ x (mod 2)`+"\n", x%2)

Runtime

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

Среда выполнения (runtime) теперь более эффективно возвращает память операционной системе и в результате настроена на более агрессивную работу.

Go 1.17 в целом улучшил форматирование аргументов в трассировке стека, но мог выводить неточные значения для аргументов, переданных в регистрах. В Go 1.18 это улучшено за счет печати вопросительного знака (?) после каждого значения, которое может быть неточным.

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

Компилятор

В Go 1.17 реализован новый способ передачи аргументов и результатов функций с использованием регистров вместо стека в 64-битной архитектуре x86 в некоторых операционных системах. Go 1.18 расширяет список поддерживаемых платформ, включая 64-разрядные ARM (GOARCH=arm64), 64-разрядные PowerPC с обратным порядком байтов и прямым порядком байтов (GOARCH=ppc64, ppc64le), а также 64-разрядную архитектуру x86 (GOARCH=amd64) на все операционные системы. В 64-разрядных системах ARM и 64-разрядных системах PowerPC бенчмаркинг показывает типичное повышение производительности на 10% и более.

Как упоминалось в примечаниях к релизу Go 1.17, это изменение не влияет на функциональность какого-либо безопасного кода Go и не влияет на большинство ассемблерных кодов.

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

Новая опция компилятора -asan поддерживает новую опцию команды go -asan.

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

Из-за изменений в компиляторе, связанных с поддержкой дженериков, скорость компиляции Go 1.18 может быть примерно на 15% ниже, чем скорость компиляции Go 1.17. На время выполнения скомпилированного кода это не влияет. Планируется улучшить скорость компилятора в будущих релизах.

Линкер (компоновщик)

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

Новая опция компоновщика -asan поддерживает новую опцию команды go -asan.

Начальная загрузка

При сборке релиза Go из исходного кода и не заданном GOROOT_BOOTSTRAP предыдущие версии Go искали цепочку инструментов начальной загрузки Go 1.4 или более поздней версии в каталоге $HOME/go1.4 (%HOMEDRIVE%%HOMEPATH%\go1.4 в Windows). Теперь Go сначала ищет $HOME/go1.17 или $HOME/sdk/go1.17, а затем возвращается к $HOME/go1.4. Предполагается, что Go 1.19 потребует Go 1.17 или более поздней версии для начальной загрузки, и это изменение должно сделать переход более плавным.

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


Новый пакет debug/buildinfo

Новый пакет debug/buildinfo предоставляет доступ к версиям модулей, информации о контроле версий и флагам сборки, встроенным в исполняемые файлы, созданные командой go. Та же информация также доступна через runtime/debug.ReadBuildInfo для текущего исполняемого файла и через go version -m в командной строке.

Новый пакет net/netip

Новый пакет net/netip определяет новый тип IP-адреса, Addr. По сравнению с существующим типом net.IP тип netip.Addr занимает меньше памяти, является неизменяемым и сравнимым, поэтому он поддерживает == и может использоваться в качестве ключа сопоставления.

В дополнение к Addr пакет определяет AddrPort, представляющий IP-адрес и порт, и Prefix, представляющий префикс сети CIDR.

Пакет также определяет несколько функций для создания и проверки этих новых типов: AddrFrom4, AddrFrom16, AddrFromSlice, AddrPortFrom, IPv4Unspecified, IPv6LinkLocalAllNodes, IPv6Unspecified, MustParseAddr, MustParseAddrPort, MustParsePrefix, ParseAddr, ParseAddrPort, ParsePrefix, PrefixFrom.

Пакет net включает новые методы, которые параллельны существующим методам, но возвращают netip.AddrPort вместо более тяжелых типов net.IP или *net.UDPAddr: Resolver.LookupNetIP, UDPConn.ReadFromUDPAddrPort, UDPConn.ReadMsgUDPAddrPort, UDPConn.WriteToUDPAddrPort, UDPConn.WriteMsgUDPAddrPort. Новые методы UDPConn поддерживают ввод-вывод без аллокаций.

Пакет net также теперь включает функции и методы для преобразования между существующими типами TCPAddr/UDPAddr и netip.AddrPort: TCPAddrFromAddrPort, UDPAddrFromAddrPort, TCPAddr.AddrPort, UDPAddr.AddrPort.

TLS 1.0 и 1.1 отключены по умолчанию на стороне клиента

Если Config.MinVersion не задан, теперь по умолчанию используется TLS 1.2 для клиентских подключений. Ожидается, что любой безопасно обновленный сервер будет поддерживать TLS 1.2, а браузеры требуют его с 2020 года. TLS 1.0 и 1.1 по-прежнему поддерживаются, если для параметра Config.MinVersion установлено значение VersionTLS10. Значение по умолчанию на стороне сервера не изменилось в TLS 1.0.

По умолчанию можно временно вернуться к TLS 1.0, установив переменную среды GODEBUG=tls10default=1. Эта опция будет удалена в Go 1.19.

Отклонение сертификатов SHA-1

crypto/x509 теперь будет отклонять сертификаты, подписанные с помощью хеш-функции SHA-1. Это не относится к самозаверяющим корневым сертификатам. Практические атаки на SHA-1 были продемонстрированы с 2017 года, а общедоступные доверенные центры сертификации не выпускали сертификаты SHA-1 с 2015 года.

Это можно временно отменить, установив переменную среды GODEBUG=x509sha1=1. Эта опция будет удалена в Go 1.19.

Мелкие изменения в библиотеке

Как всегда, в библиотеку внесены различные незначительные изменения и обновления, сделанные с учетом обещания совместимости с Go 1.

bufio

Новый метод Writer.AvailableBuffer возвращает пустой буфер с возможно непустой емкостью для использования с API-интерфейсами, подобными append. После добавления буфер может быть предоставлен для последующего вызова Write и возможно позволит избежать копирования.

Методы Reader.Reset и Writer.Reset теперь используют размер буфера по умолчанию при вызове объектов с нулевым буфером.

bytes

Новая функция Cut нарезает []byte вокруг разделителя. Она может заменить и упростить многие распространенные способы использования Index, IndexByte, IndexRune и SplitN.

Trim, TrimLeft и TrimRight теперь не требуют выделения памяти и, особенно для небольших фрагментов ASCII, работают до 10 раз быстрее.

Функция Title устарела. Она не обрабатывает пунктуацию Unicode и правила использования заглавных букв для конкретного языка и заменяется пакетом golang.org/x/text/cases.

crypto/elliptic

Реализации кривых P224, P384 и P521 теперь поддерживаются кодом, сгенерированным проектами addchain и fiat-crypto, последний из которых основан на формально проверенной модели арифметических операций. Теперь они используют более безопасные полные формулы и внутренние API. Р-224 и Р-384 теперь примерно в четыре раза быстрее. Все конкретные реализации кривых теперь работают с постоянным временем.

Работа с недопустимыми точками кривой (теми, для которых метод IsOnCurve возвращает false и которые никогда не возвращаются Unmarshal или методом Curve, работающим с допустимой точкой) всегда было неопределенным поведением, может привести к атакам восстановления ключа и теперь не поддерживается новым бэкендом. Если для метода P224, P384 или P521 указана недопустимая точка, этот метод теперь будет возвращать случайную точку. Поведение может измениться на явную панику в будущем релизе.

crypto/tls

Новый метод Conn.NetConn позволяет получить доступ к базовому net.Conn.

crypto/x509

Certificate.Verify теперь использует API-интерфейсы платформы для проверки действительности сертификата в macOS и iOS, когда он вызывается с нулевым значением VerifyOpts.Roots или при использовании корневого пула, возвращенного из SystemCertPool.

SystemCertPool теперь доступен в Windows.

В Windows, macOS и iOS, когда в CertPool, возвращенный SystemCertPool, добавлены дополнительные сертификаты, Certificate.Verify выполнит две проверки: одну с использованием API-интерфейсов верификатора платформы и системных корней (roots), а другую с использованием верификатора Go и дополнительных корней (roots). Цепочки, возвращаемые API-интерфейсами верификатора платформы, будут иметь приоритет.

CertPool.Subjects устарел. В Windows, macOS и iOS CertPool, возвращаемый SystemCertPool, будет возвращать пул, который не включает системные корни в срезе, возвращаемом Subjects, поскольку статический список не может должным образом представлять политики платформы и может быть вообще недоступен из API платформы.

Поддержка подписания сертификатов с использованием алгоритмов подписи, зависящих от хэшей MD5 и SHA-1 (MD5WithRSA, SHA1WithRSA и ECDSAWithSHA1), может быть удалена в Go 1.19.

debug/dwarf

Структуры StructField и BasicType теперь имеют поле DataBitOffset, которое содержит значение атрибута DW_AT_data_bit_offset, если он присутствует.

debug/elf

Добавлена константа R_PPC64_RELATIVE.

debug/plan9obj

Метод File.Symbols теперь возвращает новое экспортированное значение ошибки ErrNoSymbols, если в файле нет раздела символов.

embed

Директива go:embed теперь может начинаться с all: для включения файлов, имена которых начинаются с точки или подчеркивания.

go/ast

Согласно предложению Дополнения к go/ast и go/token для поддержки параметризованных функций и типов, в пакет go/ast внесены следующие дополнения:

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

go/constant

Новый метод Kind.String возвращает удобочитаемое имя для типа получателя.

go/token

Новая константа TILDE представляет токен ~ согласно предложению Дополнения к go/ast и go/token для поддержки параметризованных функций и типов.

go/types

В новом поле Config.GoVersion задается принятая языковая версия Go.

В соответствии с предложением Дополнения к go/types для поддержки параметров типа в пакет go/types внесены следующие дополнения:

  • Новый тип TypeParam, фабричная функция NewTypeParam и связанные методы добавляются для представления параметра типа.
  • Новый тип TypeParamList содержит список параметров типа.
  • Новый тип TypeList содержит список типов.
  • Новая фабричная функция NewSignatureType выделяет сигнатуру с параметрами типа (получателя или функции). Чтобы получить доступ к этим параметрам типа, у типа Signature есть два новых метода Signature.RecvTypeParams и Signature.TypeParams.
  • Named типы имеют четыре новых метода: Named.Origin для получения исходных параметризованных типов экземпляров типов, Named.TypeArgs и Named.TypeParams для получения аргументов типа или параметров типа для экземпляра или параметризованного типа и Named.SetTypeParams для установки типа параметров (например, при импорте именованного типа, когда выделение именованного типа и установка параметров типа не могут выполняться одновременно из-за возможных циклов).
  • Тип Interface имеет четыре новых метода: Interface.IsComparable и Interface.IsMethodSet для запроса свойств набора типов, определенного интерфейсом, и Interface.MarkImplicit и Interface.IsImplicit для установки и проверки того, является ли интерфейс неявным интерфейсом вокруг литерала ограничения типа.
  • Добавлены новые типы Union и Term, фабричные функции NewUnion и NewTerm и связанные методы для представления наборов типов в интерфейсах.
  • Новая функция Instantiate создает экземпляр параметризованного типа.
  • Новая карта Info.Instances записывает функции и экземпляры типов через новый тип Instance.
  • Добавлен новый тип ArgumentError и связанные с ним методы для представления ошибки, связанной с аргументом типа.
  • Новый контекст типа и фабричная функция NewContext добавляются для облегчения совместного использования экземпляров идентичных типов в пакетах с проверкой типов через новое поле Config.Context.

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

html/template

В range пайплайне новая команда {{break}} завершает цикл досрочно, а новая команда {{continue}} немедленно запускает следующую итерацию цикла.

Функция and больше не всегда оценивает все аргументы; она прекращает оценку аргументов после первого аргумента, который оценивается как false. Точно так же функция or теперь прекращает оценку аргументов после первого аргумента, который оценивается как true. Это имеет значение, если какой-либо из аргументов является вызовом функции.

image/draw

Резервные реализации Draw и DrawMask (используемые, когда аргументы не являются наиболее распространенными типами изображений) теперь работают быстрее, когда эти аргументы реализуют необязательные интерфейсы draw.RGBA64Image и image.RGBA64Image, которые были добавлены в Go 1.17.

net

net.Error.Temporary устарела.

net/http

В целях WebAssembly поля методов Dial, DialContext, DialTLS и DialTLSContext в Transport теперь будут правильно использоваться, если они указаны, для выполнения HTTP-запросов.

Новый метод Cookie.Valid сообщает, действителен ли файл cookie.

Новая функция MaxBytesHandler создает обработчик, который оборачивает свои ResponseWriter и Request.Body в MaxBytesReader.

При поиске доменного имени, содержащего символы, отличные от ASCII, преобразование Unicode в ASCII теперь выполняется в соответствии с непереходной обработкой, как определено в стандарте Unicode IDNA Compatibility Processing (UTS #46). Изменена интерпретация четырех различных рун: ß, ς, соединения нулевой ширины U+200D и соединения нулевой ширины U+200C. Непереходная обработка совместима с большинством приложений и веб-браузеров.

os/user

User.GroupIds теперь использует нативную реализацию Go, когда cgo недоступен.

reflect

Новые методы Value.SetIterKey и Value.SetIterValue устанавливают значение, используя итератор карты в качестве источника. Они эквивалентны Value.Set(iter.Key()) и Value.Set(iter.Value()), но выполняют меньше аллокаций.

Новый метод Value.UnsafePointer возвращает значение Value как unsafe.Pointer. Это позволяет вызывающим объектам переходить от Value.UnsafeAddr и Value.Pointer, чтобы исключить необходимость выполнения преобразований uintptr в unsafe.Pointer на месте вызова (как того требуют правила unsafe.Pointer).

Новый метод MapIter.Reset изменяет свой приемник для перебора другой карты. Использование MapIter.Reset позволяет выполнять итерацию без распределения по многим картам.

В Value добавлен ряд методов ( Value.CanInt, Value.CanUint, Value.CanFloat, Value.CanComplex ) для проверки безопасности преобразования.

Value.FieldByIndexErr был добавлен, чтобы избежать паники, возникающей в Value.FieldByIndex при переходе через нулевой указатель к встроенной структуре.

Reflect.Ptr и Reflect.PtrTo были переименованы в Reflect.Pointer и Reflect.PointerTo соответственно для обеспечения согласованности с остальной частью пакета Reflect. Старые имена продолжат работать, но в будущем релизе Go они будут объявлены устаревшими.

regexp

regexp теперь обрабатывает каждый недопустимый байт строки UTF-8 как U+FFFD.

runtime/debug

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

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

runtime/pprof

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

strconv

strconv.Unquote теперь отклоняет суррогатные половинки Unicode.

strings

Новая функция Cut разрезает строку вокруг разделителя. Он может заменить и упростить многие распространенные способы использования Index, IndexByte, IndexRune и SplitN.

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

Trim, TrimLeft и TrimRight теперь не требуют выделения памяти и, особенно для небольших фрагментов ASCII, работают до 10 раз быстрее.

Функция Title устарела. Она не обрабатывает пунктуацию Unicode и правила использования заглавных букв для конкретного языка и заменяется пакетом golang.org/x/text/cases.

sync

Новые методы Mutex.TryLock, RWMutex.TryLock и RWMutex.TryRLock получат блокировку, если она в данный момент не удерживается.

syscall

Для Windows была введена новая функция SyscallN, позволяющая выполнять вызовы с произвольным количеством аргументов. В результате Syscall, Syscall6, Syscall9, Syscall12, Syscall15 и Syscall18 устарели в пользу SyscallN.

SysProcAttr.Pdeathsig теперь поддерживается во FreeBSD.

syscall/js

Интерфейс Wrapper был удален.

testing

Увеличен приоритет / в аргументе для -run и -bench. A/B|C/D раньше рассматривались как A/(B|C)/D, а теперь рассматриваются как (A/B)|(C/D).

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

Новый тип testing.F используется новой поддержкой фаззинга. Тесты также теперь поддерживают параметры командной строки -test.fuzz, -test.fuzztime и -test.fuzzminimizetime.

text/template

В range пайплайне новая команда {{break}} завершает цикл досрочно, а новая команда {{continue}} немедленно запускает следующую итерацию цикла.

Функция and больше не всегда оценивает все аргументы; она прекращает оценку аргументов после первого аргумента, который оценивается как false. Точно так же функция or теперь прекращает оценку аргументов после первого аргумента, который оценивается как true. Это имеет значение, если какой-либо из аргументов является вызовом функции.

text/template/parse

Пакет поддерживает новую команду text/template и html/template {{break}} с помощью новой константы NodeBreak и нового типа BreakNode, а также поддерживает новую команду {{continue}} с помощью новой константы NodeContinue и нового типа ContinueNode.

unicode/utf8

Новая функция AppendRune добавляет руну в кодировке UTF-8 к []byte.


Читайте также: