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

Обновлено: 21.11.2024

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

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

Сообщения в этой серии:

Вот программа приветствия, использующая модули C++:

Это очень простой пример. У нас есть единственный исходный файл, который предоставляет модуль voice. Наш main.cpp импортирует речь и использует единственную функцию, определенную в ней: get_phrase .

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

Несколько вопросов (на которые будут даны ответы ниже): Зачем экспортировать модуль, а не просто модуль? Как называются модули? Каковы правила ввоза? Что дает экспорт? Что это за «разделы», о которых люди продолжают говорить?

Модуль Имена

Модуль идентифицируется по правильному названию имя-модуля. Ниже приведена грамматика имя-модуля:

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

Что означает точка . ? Буквально ничего. Это чисто для вас, разработчика. Модуль с именем boost.asio.async_completion упрощает понимание логической иерархии, чем модуль с именем boost_asio_async_completion , но согласно стандарту между двумя стилями именования нет семантической разницы.

В main.cpp вы можете заметить, что у нас есть import ; , и не соответствует правилам для имени модуля. Что дает? Ответ: это не модуль: это блок заголовка. Это важная тема, но мы поговорим о ней в следующем посте.

Новый исходный объект C++

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

В модулях C++ представлен новый тип единицы перевода, который называется модульная единица. Определение довольно простое:

модуль — это модуль перевода, содержащий объявление модуля.

Что такое «объявление модуля»? Мы уже видели один в нашем примере. Грамматика очень проста:

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

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

  • модуль интерфейса — это модуль, в котором декларация модуля содержит ключевое слово экспорта. Их может быть любое количество в модуле.
  • Единица реализации модуля — это любая единица модуля, которая не является единицей интерфейса модуля (не имеет ключевого слова export в объявлении модуля).
  • Раздел модуля – это модуль, в котором объявление модуля содержит компонент раздел модуля.
  • Раздел интерфейса модуля – это блок интерфейса модуля, который также является разделом модуля (содержит как ключевое слово экспорта, так и компонент раздела модуля).
  • Раздел реализации модуля — это единица реализации модуля, которая также является разделом модуля. (Содержит компонент модуля-раздела, но без ключевого слова экспорта).
  • Первичный интерфейсный блок модуля – это нераздельный модуль, который также является модульным интерфейсным блоком. В модуле должен быть ровно один первичный интерфейсный модуль. Все остальные блоки интерфейса модуля должны быть разделами модуля.

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

Модули Разделы

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

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

Вау! Это тонна кода! Давайте разделим его с помощью разделов:

Что здесь происходит?

  • У нас есть один модуль с именем voice
  • Речь имеет два раздела: английский и испанский.
  • Модуль экспорта синтаксиса:

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

(с двоеточием в начале) импортирует раздел с именем

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

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

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

В приведенном выше примере get_phrase_en и get_phrase_es находятся в модуле речи. Разделение на разделы недоступно для пользователей.

Интерфейс модуля определяется как объединение всех модулей интерфейса внутри этого модуля.

Мы можем классифицировать вышеуказанные исходные файлы как таковые:

  • speech.cpp — это основной модуль интерфейса.
  • speech_english.cpp и speech_spanish.cpp являются разделами интерфейса модуля.
  • main.cpp — это обычная единица перевода.

ВАЖНО: основной блок интерфейса для модуля должен экспортировать все разделы интерфейса для модуля (прямо или косвенно) через экспорт-импорт:

<р>. В противном случае программа имеет неправильный формат, диагностика не требуется.

«Подмодули» — это не вещь (технически)

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

Вместо использования разделов мы перемещаем объявления get_phrase_en и get_phrase_es в их собственные модули, а модуль экспорта речи импортирует их. Синтаксис экспорта-импорта объявляет, что пользователи, которые импортируют модуль, будут транзитивно импортировать модуль с заданным именем.

Содержимое main.cpp не меняется между двумя макетами. Он неявно импортирует речь.english и speech.spanish посредством импорта речи .

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

Пользователям Python может быть знаком такой синтаксис, как квалифицированный относительный импорт, где начальный . указывает механизму модуля искать родственный модуль с заданным именем. Модули C++ не работают так, поскольку у них нет внутренней иерархии. Компилятор не видит связи между модулями speech, speech.english и speech.spanish. Приведенный выше фрагмент совершенно бессмысленен с точки зрения языка.

speech.english и speech.spanish не являются подмодулями речи . Это полностью непересекающиеся модули.

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

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

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

Модуль Реализация Единицы

До сих пор мы рассматривали модули interface модуля, но есть еще один тип модуля: модуль implementation.

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

Вот как выглядел бы наш пример, если бы мы использовали единицы реализации:

Это похоже на то, что было раньше, но с некоторыми изменениями:

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

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

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

Синтаксис раздела исчез, и у нас есть только два исходных файла для нашего модуля. Модуль речи; объявление (без экспорта ) объявляет этот модуль как модуль реализации (не раздел). Невозможно импортировать этот файл отдельно, да и нет смысла это делать. Эти два файла будут использоваться для создания речи модуля.

Ограничения на [экспорт] и разделы

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

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

экспорт-импорт разрешен только для разделов интерфейса

Следующее не разрешено:

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

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

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

Что произойдет, если мы добавим еще один модуль, расширяющий Cats?

Запрещено! Модуль экспорта без имени раздела является основным интерфейсным модулем, и он может быть только один.

Нынешняя природа компиляторов не может справиться с таким дизайном. Два вызова компилятора не смогут узнать, должны ли они быть «объединены», или если вы очень быстро переписали и переместили определение Cats в другой файл. Компилятор теоретически может видеть оба файла одновременно и выполнять слияние, но тогда у вас возникают вопросы о том, как эти два файла взаимодействуют. Поддерживать такой дизайн было бы очень сложно и мало пользы.

Кроме того, поддержка нескольких первичных интерфейсных модулей поднимает еще один вопрос: что мешает пользователю внедрить что-то в чужой модуль, определив другой файл Cats?

экспорт запрещен в модулях реализации

Это говорит само за себя. Разрешать единице реализации экспортировать сущность (или импортировать модуль) бессмысленно.

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

Если ваши ограничения не могут быть устранены с помощью предложений, приведенных в разделе «Максимизация скорости MicroPython», написание части или всего вашего модуля на языке C (и/или C++, если он реализован для вашего порта) является приемлемым вариантом.

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

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

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

Структура внешнего модуля C¶

Модуль C пользователя MicroPython представляет собой каталог со следующими файлами:

Файлы исходного кода *.c / *.cpp / *.h для вашего модуля.

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

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

micropython.mk содержит фрагмент Makefile для этого модуля.

$(USERMOD_DIR) доступен в micropython.mk как путь к каталогу вашего модуля. Поскольку он переопределен для каждого модуля c, он должен быть расширен в вашем micropython.mk до локальной переменной make, например, EXAMPLE_MOD_DIR := $(USERMOD_DIR)

Ваш micropython.mk должен добавить исходные файлы ваших модулей относительно вашей расширенной копии $(USERMOD_DIR) в SRC_USERMOD , например, SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c

Если у вас есть специальные параметры компилятора (например, -I для добавления каталогов для поиска файлов заголовков), их следует добавить в CFLAGS_USERMOD для кода C и в CXXFLAGS_USERMOD для кода C++.

micropython.cmake содержит конфигурацию CMake для этого модуля.

В micropython.cmake вы можете использовать $ в качестве пути к текущему модулю.

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

См. ниже полный пример использования.

Базовый пример¶

Этот простой модуль с именем cexample предоставляет единственную функцию cexample.add_ints(a, b), которая складывает вместе два целочисленных аргумента и возвращает результат. Его можно найти в дереве исходных текстов MicroPython в каталоге примеров, и он содержит исходный файл и фрагмент Makefile с содержимым, как описано выше:

Дополнительные пояснения см. в комментариях к этим файлам. Рядом с модулем cexample есть еще cppexample, который работает точно так же, но показывает один из способов смешивания кода C и C++ в MicroPython.

Компиляция cmmodule в MicroPython¶

Чтобы собрать такой модуль, скомпилируйте MicroPython (см. Начало работы), внеся 2 модификации:

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

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

Для создания примеров модулей, которые поставляются с MicroPython, установите USER_C_MODULES в каталог examples/usercmodule для Make или в examples/usercmodule/micropython.cmake для CMake.

Например, вот как собрать порт unix с примерами модулей:

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

Для порта на основе CMake, такого как rp2, это будет выглядеть немного иначе (обратите внимание, что CMake фактически вызывается make ):

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

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

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

При сборке с помощью Make установите USER_C_MODULES в каталог my_project/modules. Например, создание порта stm32:

При сборке с помощью CMake файл micropython.cmake верхнего уровня, который находится непосредственно в каталоге my_project/modules, должен включать все модули, которые вы хотите иметь доступными:

Затем создайте с помощью:

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

Все модули, указанные в переменной USER_C_MODULES (найденные в этом каталоге при использовании Make или добавленные с помощью include при использовании CMake), будут скомпилированы, но для импорта будут доступны только включенные модули.Пользовательские модули обычно включены по умолчанию (это решает разработчик модуля), и в этом случае ничего не остается делать, как установить USER_C_MODULES, как описано выше.

Если модуль не включен по умолчанию, необходимо включить соответствующий макрос препроцессора C. Это имя макроса можно найти, выполнив поиск строки MP_REGISTER_MODULE в исходном коде модуля (обычно оно появляется в конце основного исходного файла). Третий аргумент MP_REGISTER_MODULE — это имя макроса, и его нужно установить в 1 с помощью CFLAGS_EXTRA, чтобы сделать модуль доступным. Если третьим аргументом является только число 1, модуль включен по умолчанию.

Например, модуль examples/usercmodule/cexample включен по умолчанию, поэтому в его исходном коде есть следующая строка:

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

В этом случае модуль включается путем добавления CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 в команду make или редактирования mpconfigport.h или mpconfigboard.h для добавления

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

Использование модуля в MicroPython¶

После того, как модуль был встроен в вашу копию MicroPython, теперь к нему можно получить доступ в Python, как и к любому другому встроенному модулю, например,

© Авторское право — Документация MicroPython защищена авторским правом © 2014–2022, Дэмиен П. Джордж, Пол Соколовский и соавторы. Последнее обновление: 25 марта 2022 г.

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

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

Организация кода

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

Репозиторий содержит один или несколько модулей. A — это набор связанных пакетов Go, выпущенных вместе. Репозиторий Go обычно содержит только один модуль, расположенный в корне репозитория. Файл с именем go.mod объявляет префикс пути импорта : для всех пакетов в модуле. Модуль содержит пакеты в каталоге, содержащем его файл go.mod, а также в подкаталогах этого каталога, вплоть до следующего подкаталога, содержащего другой файл go.mod (если есть).

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

Ваша первая программа

Чтобы скомпилировать и запустить простую программу, сначала выберите путь к модулю (мы будем использовать example/user/hello ) и создайте файл go.mod, который объявляет его:

Первым оператором в исходном файле Go должен быть package . Исполняемые команды всегда должны использовать пакет main .

Затем создайте в этом каталоге файл с именем hello.go, содержащий следующий код Go:

Теперь вы можете собрать и установить эту программу с помощью инструмента go:

Эта команда создает команду hello, создавая исполняемый двоичный файл. Затем он устанавливает этот двоичный файл как $HOME/go/bin/hello (или, в Windows, %USERPROFILE%\go\bin\hello.exe ).

Каталог установки управляется переменными среды GOPATH и GOBIN. Если установлен GOBIN, двоичные файлы устанавливаются в этот каталог. Если задан GOPATH, двоичные файлы устанавливаются в подкаталог bin первого каталога в списке GOPATH. В противном случае двоичные файлы устанавливаются в подкаталог bin GOPATH по умолчанию ( $HOME/go или %USERPROFILE%\go ).

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

Чтобы отключить переменную, ранее установленную с помощью go env -w , используйте go env -u :

Команды вроде go install применяются в контексте модуля, содержащего текущий рабочий каталог. Если рабочий каталог не находится в модуле example/user/hello, установка может завершиться ошибкой.

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

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

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

Импорт пакетов из вашего модуля

Давайте напишем пакет morestrings и воспользуемся им из программы hello. Сначала создайте каталог для пакета с именем $HOME/hello/morestrings , а затем файл с именем reverse.go в этом каталоге со следующим содержимым:

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

Давайте проверим, что пакет компилируется с помощью go build :

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

Убедившись, что пакет morestrings собран, давайте воспользуемся им из программы hello. Для этого измените исходный файл $HOME/hello/hello.go, чтобы использовать пакет morestrings:

Установите программу приветствия:

Запустив новую версию программы, вы должны увидеть новое, перевернутое сообщение:

Импорт пакетов из удаленных модулей

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

Зависимости модуля автоматически загружаются в подкаталог pkg/mod каталога, указанного переменной среды GOPATH. Загруженное содержимое для данной версии модуля совместно используется всеми другими модулями, которым требуется эта версия, поэтому команда go помечает эти файлы и каталоги как доступные только для чтения. Чтобы удалить все загруженные модули, вы можете передать флаг -modcache для очистки:

Тестирование

В Go есть упрощенная среда тестирования, состоящая из команды go test и пакета тестирования.

Вы пишете тест, создавая файл с именем, заканчивающимся на _test.go, который содержит функции с именем TestXXX с сигнатурой func (t *testing.T) . Платформа тестирования запускает каждую такую ​​функцию; если функция вызывает функцию отказа, такую ​​как t.Error или t.Fail , тест считается не пройденным.

Добавьте тест в пакет morestrings, создав файл $HOME/hello/morestrings/reverse_test.go, содержащий следующий код Go.

Затем запустите тест с помощью go test :

Что дальше

Подпишитесь на список рассылки golang-announce, чтобы получать уведомления о выпуске новой стабильной версии Go.

См. статью Effective Go, где приведены советы по написанию четкого идиоматичного кода Go.

Совершите экскурсию по Go, чтобы выучить язык.

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

Получение помощи

Чтобы получить помощь в режиме реального времени, обратитесь к полезным сусликам на управляемом сообществом сервере Gophers Slack (получите приглашение здесь).

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