├── .editorconfig ├── .gitignore └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 4 space indentation 12 | [*.md] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### macOS ### 2 | # General 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must end with two \r 8 | Icon 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | .com.apple.timemachine.donotpresent 21 | 22 | # Directories potentially created on remote AFP share 23 | .AppleDB 24 | .AppleDesktop 25 | Network Trash Folder 26 | Temporary Items 27 | .apdisk 28 | 29 | ### GoLand ### 30 | .idea 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Часто задаваемые вопросы о Go (FAQ, перевод на русский) 2 | 3 | _Официальный FAQ по языку Go, переведённый на русский язык._ 4 | 5 | 📖 [Удобная версия для чтения](https://akrisanov.github.io/golang_faq_ru/) доступна на GitHub Pages 6 | (подходит и для мобильных устройств). 7 | 8 | ## Содержание 9 | 10 | - [Истоки](#истоки) 11 | - [Какова цель проекта?](#какова-цель-проекта) 12 | - [Какова история проекта?](#какова-история-проекта) 13 | - [Каково происхождение маскота?](#каково-происхождение-маскота) 14 | - [Язык называется Go или Golang?](#язык-называется-go-или-golang) 15 | - [Зачем вы создали новый язык?](#зачем-вы-создали-новый-язык) 16 | - [Каковы предки Go?](#каковы-предки-go) 17 | - [Каковы основные принципы дизайна?](#каковы-основные-принципы-дизайна) 18 | - [Использование](#использование) 19 | - [Используется ли Go внутри Google?](#используется-ли-go-внутри-google) 20 | - [Какие еще компании используют Go?](#какие-ещё-компании-используют-go) 21 | - [Какие ещё компании используют Go?](#какие-ещё-компании-используют-go) 22 | - [Можно ли связывать программы на Go с программами на C/C++?](#можно-ли-связывать-программы-на-go-с-программами-на-cc) 23 | - [Какие IDE поддерживают Go?](#какие-ide-поддерживают-go) 24 | - [Поддерживает ли Go Google Protocol Buffers?](#поддерживает-ли-go-google-protocol-buffers) 25 | - [Дизайн](#дизайн) 26 | - [Есть ли у Go рантайм?](#есть-ли-у-go-рантайм) 27 | - [Что насчёт идентификаторов с Unicode?](#что-насчёт-идентификаторов-с-unicode) 28 | - [Почему в Go нет возможности X?](#почему-в-go-нет-возможности-x) 29 | - [Когда в Go появились обобщённые типы?](#когда-в-go-появились-обобщённые-типы) 30 | - [Почему Go изначально вышел без обобщённых типов?](#почему-go-изначально-вышел-без-обобщённых-типов) 31 | - [Почему в Go нет исключений?](#почему-в-go-нет-исключений) 32 | - [Почему в Go нет assert?](#почему-в-go-нет-assert) 33 | - [Почему конкурентность в Go построена на идеях CSP?](#почему-конкурентность-в-go-построена-на-идеях-csp) 34 | - [Почему горутины, а не потоки?](#почему-горутины-а-не-потоки) 35 | - [Почему операции с map не являются атомарными?](#почему-операции-с-map-не-являются-атомарными) 36 | - [Примете ли вы мои изменения в язык?](#примете-ли-вы-мои-изменения-в-язык) 37 | - [Типы](#типы) 38 | - [Является ли Go объектно-ориентированным языком?](#является-ли-go-объектно-ориентированным-языком) 39 | - [Как в Go получить динамическую диспетчеризацию методов?](#как-в-go-получить-динамическую-диспетчеризацию-методов) 40 | - [Почему в Go нет наследования типов?](#почему-в-go-нет-наследования-типов) 41 | - [Почему `len` — это функция, а не метод?](#почему-len--это-функция-а-не-метод) 42 | - [Почему в Go нет перегрузки методов и операторов?](#почему-в-go-нет-перегрузки-методов-и-операторов) 43 | - [Почему в Go нет ключевого слова `implements`?](#почему-в-go-нет-ключевого-слова-implements) 44 | - [Как гарантировать, что мой тип реализует интерфейс?](#как-гарантировать-что-мой-тип-реализует-интерфейс) 45 | - [Почему тип `T` не реализует интерфейс `Equal`?](#почему-тип-t-не-реализует-интерфейс-equal) 46 | - [Можно ли преобразовать `[]T` в `[]interface{}`?](#можно-ли-преобразовать-t-в-interface) 47 | - [Можно ли преобразовать `[]T1` в `[]T2`, если у `T1` и `T2` одинаковый базовый тип?](#можно-ли-преобразовать-t1-в-t2-если-у-t1-и-t2-одинаковый-базовый-тип) 48 | - [Почему моё значение ошибки `nil` не равно `nil`?](#почему-моё-значение-ошибки-nil-не-равно-nil) 49 | - [Почему нулевые типы (zero-size types) ведут себя странно?](#почему-нулевые-типы-zero-size-types-ведут-себя-странно) 50 | - [Почему в Go нет нетегированных объединений (unions), как в C?](#почему-в-go-нет-нетегированных-объединений-unions-как-в-c) 51 | - [Почему в Go нет вариантных типов?](#почему-в-go-нет-вариантных-типов) 52 | - [Почему в Go нет ковариантных возвращаемых типов?](#почему-в-go-нет-ковариантных-возвращаемых-типов) 53 | - [Значения](#значения) 54 | - [Почему в Go нет неявных числовых преобразований?](#почему-в-go-нет-неявных-числовых-преобразований) 55 | - [Как работают константы в Go?](#как-работают-константы-в-go) 56 | - [Почему `map` встроен в язык?](#почему-map-встроен-в-язык) 57 | - [Почему в Go нельзя использовать срезы в качестве ключей map?](#почему-в-go-нельзя-использовать-срезы-в-качестве-ключей-map) 58 | - [Почему `map`, срезы и каналы являются ссылками, а массивы — значениями?](#почему-map-срезы-и-каналы-являются-ссылками-а-массивы--значениями) 59 | - [Написание кода](#написание-кода) 60 | - [Как документируются библиотеки?](#как-документируются-библиотеки) 61 | - [Есть ли у Go руководство по стилю программирования?](#есть-ли-у-go-руководство-по-стилю-программирования) 62 | - [Как отправить патчи в библиотеки Go?](#как-отправить-патчи-в-библиотеки-go) 63 | - [Почему `go get` использует HTTPS при клонировании репозитория?](#почему-go-get-использует-https-при-клонировании-репозитория) 64 | - [Как управлять версиями пакетов с помощью `go get`?](#как-управлять-версиями-пакетов-с-помощью-go-get) 65 | - [Указатели и выделение памяти](#указатели-и-выделение-памяти) 66 | - [Когда параметры функций передаются по значению?](#когда-параметры-функций-передаются-по-значению) 67 | - [Когда стоит использовать указатель на интерфейс?](#когда-стоит-использовать-указатель-на-интерфейс) 68 | - [Следует ли определять методы для значений или для указателей?](#следует-ли-определять-методы-для-значений-или-для-указателей) 69 | - [В чём разница между `new` и `make`?](#в-чём-разница-между-new-и-make) 70 | - [Каков размер `int` на 64-битной машине?](#каков-размер-int-на-64-битной-машине) 71 | - [Как узнать, выделена ли переменная в куче или на стеке?](#как-узнать-выделена-ли-переменная-в-куче-или-на-стеке) 72 | - [Почему мой Go-процесс использует так много виртуальной памяти?](#почему-мой-go-процесс-использует-так-много-виртуальной-памяти) 73 | - [Конкурентность](#конкурентность) 74 | - [Какие операции атомарны? А как насчёт мьютексов?](#какие-операции-атомарны-а-как-насчёт-мьютексов) 75 | - [Почему моя программа не работает быстрее на большем числе CPU?](#почему-моя-программа-не-работает-быстрее-на-большем-числе-cpu) 76 | - [Как управлять количеством CPU?](#как-управлять-количеством-cpu) 77 | - [Почему у горутин нет уникального идентификатора (ID)?](#почему-у-горутин-нет-уникального-идентификатора-id) 78 | - [Функции и методы](#функции-и-методы) 79 | - [Почему у `T` и `*T` разные наборы методов?](#почему-у-t-и-t-разные-наборы-методов) 80 | - [Что происходит с замыканиями, запущенными как горутины?](#что-происходит-с-замыканиями-запущенными-как-горутины) 81 | - [Управление потоком выполнения](#управление-потоком-выполнения) 82 | - [Почему в Go нет оператора `?:`?](#почему-в-go-нет-оператора-) 83 | - [Параметры типов](#параметры-типов) 84 | - [Зачем Go нужны параметры типов?](#зачем-go-нужны-параметры-типов) 85 | - [Как дженерики реализованы в Go?](#как-дженерики-реализованы-в-go) 86 | - [Как дженерики в Go сравниваются с дженериками в других языках?](#как-дженерики-в-go-сравниваются-с-дженериками-в-других-языках) 87 | - [Почему в Go для списка параметров типов используются квадратные скобки?](#почему-в-go-для-списка-параметров-типов-используются-квадратные-скобки) 88 | - [Почему Go не поддерживает методы с параметрами типов?](#почему-go-не-поддерживает-методы-с-параметрами-типов) 89 | - [Почему нельзя использовать более специфичный тип для получателя параметризованного типа?](#почему-нельзя-использовать-более-специфичный-тип-для-получателя-параметризованного-типа) 90 | - [Почему компилятор не может вывести аргумент типа в моей программе?](#почему-компилятор-не-может-вывести-аргумент-типа-в-моей-программе) 91 | - [Пакеты и тестирование](#пакеты-и-тестирование) 92 | - [Как создать пакет из нескольких файлов?](#как-создать-пакет-из-нескольких-файлов) 93 | - [Как написать модульный тест?](#как-написать-модульный-тест) 94 | - [Где моя любимая вспомогательная функция для тестирования?](#где-моя-любимая-вспомогательная-функция-для-тестирования) 95 | - [Почему _X_ нет в стандартной библиотеке?](#почему-x-нет-в-стандартной-библиотеке) 96 | - [Имплементация](#имплементация) 97 | - [Какая технология компилятора используется для сборки компиляторов?](#какая-технология-компилятора-используется-для-сборки-компиляторов) 98 | - [Как реализована поддержка времени выполнения?](#как-реализована-поддержка-времени-выполнения) 99 | - [Почему мой тривиальный код компилируется в такой большой бинарник?](#почему-мой-тривиальный-код-компилируется-в-такой-большой-бинарник) 100 | - [Могу ли я отключить жалобы на неиспользуемые переменные/импорты?](#могу-ли-я-отключить-жалобы-на-неиспользуемые-переменныеимпорты) 101 | - [Почему мой антивирус считает, что дистрибутив Go или скомпилированный бинарник заражён?](#почему-мой-антивирус-считает-что-дистрибутив-go-или-скомпилированный-бинарник-заражён) 102 | - [Производительность](#производительность) 103 | - [Почему Go показывает плохие результаты в бенчмарке X?](#почему-go-показывает-плохие-результаты-в-бенчмарке-x) 104 | - [Отличия от C](#отличия-от-c) 105 | - [Почему синтаксис так отличается от C?](#почему-синтаксис-так-отличается-от-c) 106 | - [Почему объявления «наоборот»?](#почему-объявления-наоборот) 107 | - [Почему в Go нет арифметики указателей?](#почему-в-go-нет-арифметики-указателей) 108 | - [Почему `++` и `--` — это инструкции, а не выражения? И почему только постфиксная форма?](#почему--и-----это-инструкции-а-не-выражения-и-почему-только-постфиксная-форма) 109 | - [Почему используются фигурные скобки, но нет точек с запятой? И почему нельзя ставить открывающую скобку на новой строке?](#почему-используются-фигурные-скобки-но-нет-точек-с-запятой-и-почему-нельзя-ставить-открывающую-скобку-на-новой-строке) 110 | - [Зачем нужна сборка мусора? Разве она не слишком дорогая?](#зачем-нужна-сборка-мусора-разве-она-не-слишком-дорогая) 111 | 112 | ## Истоки 113 | 114 | ### Какова цель проекта? 115 | 116 | Когда в 2007 году появился Go, мир программирования выглядел иначе. Продакшен системы чаще всего писали на 117 | **C++** или **Java**, **GitHub** ещё не существовал, большинство компьютеров были однопроцессорными, а кроме 118 | **Visual Studio** и **Eclipse** почти не было **IDE** или других высокоуровневых инструментов, 119 | тем более бесплатных и доступных в Интернете. 120 | 121 | Мы были разочарованы излишней сложностью, с которой приходилось сталкиваться при разработке больших проектов на тех 122 | языках и их системах сборки. С тех пор, как появились **C**, **C++** и **Java**, компьютеры стали значительно быстрее, 123 | но сам процесс программирования почти не изменился. Кроме того, было ясно, что многопроцессорные 124 | системы становятся стандартом, но большинство языков не предоставляли удобных и безопасных средств для эффективного 125 | использования этой мощности. 126 | 127 | Мы решили сделать шаг назад и подумать, какие ключевые задачи будут определять развитие разработки программного 128 | обеспечения в будущем и как новый язык может помочь их решить. Например, рост количества многоядерных процессоров 129 | (**multicore CPUs**) означал, что язык должен предоставлять встроенную (**first-class**) поддержку какой-то формы 130 | конкурентности (**concurrency**) или параллелизма. А чтобы управление ресурсами оставалось посильной задачей в больших 131 | конкурентных программах, нужен был сборщик мусора (**garbage collection**) или хотя бы безопасное 132 | автоматическое управление памятью. 133 | 134 | Эти соображения привели к [серии обсуждений](https://commandcenter.blogspot.com/2017/09/go-ten-years-and-climbing.html), 135 | из которых родился Go — сначала как набор идей и требований, а затем как язык. Главная цель заключалась в том, 136 | чтобы Go помогал практикующему разработчику: давал хорошие инструменты, автоматизировал рутинные задачи вроде 137 | форматирования кода и убирал препятствия при работе с большими кодовыми базами. 138 | 139 | Более развёрнутое описание целей Go и того, как они достигаются (или к чему стремятся), доступно в статье: 140 | [**Go at Google: Language Design in the Service of Software Engineering**](https://go.dev/talks/2012/splash.article). 141 | 142 | ### Какова история проекта? 143 | 144 | 21 сентября 2007 года Роберт Гриземер (Robert Griesemer), Роб Пайк (Rob Pike) и Кен Томпсон (Ken Thompson) начали 145 | набрасывать на доске цели для нового языка. Спустя несколько дней цели оформились в план действий и общее понимание того, 146 | каким будет язык. Работа над дизайном языка продолжалась в свободное время параллельно с другими задачами. 147 | 148 | К январю 2008 года Кен начал работу над компилятором для проверки идей — на выходе он генерировал код на **C**. 149 | К середине года язык стал полноценным проектом и достаточно устоялся, чтобы попробовать создать промышленный компилятор. 150 | В мае 2008 года Иэн Тейлор (Ian Taylor) независимо начал разработку фронтенда для **GCC** по черновой спецификации. 151 | В конце 2008 года к проекту присоединился Расс Кокс (Russ Cox) и помог перевести язык и библиотеки из прототипа в реальность. 152 | 153 | Go стал публичным open source-проектом 10 ноября 2009 года. Огромное количество людей из сообщества внесли идеи, 154 | участвовали в обсуждениях и писали код. 155 | 156 | Сегодня в мире уже миллионы разработчиков на Go — «gophers» — и их число постоянно растёт. 157 | Успех Go намного превзошёл наши ожидания. 158 | 159 | ### Каково происхождение маскота? 160 | 161 | Талисман (маскот) и логотип были созданы [Рене Френч (Renée French)](https://reneefrench.blogspot.com), которая также 162 | придумала [Гленду (Glenda)](https://9p.io/plan9/glenda.html) — кролика из **Plan 9**. 163 | [Пост в блоге](https://go.dev/blog/gopher) про гофера объясняет, что образ был основан на персонаже, использованном 164 | ею несколько лет назад для дизайна футболки радиостанции [WFMU](https://wfmu.org/). 165 | 166 | Логотип и талисман распространяются по лицензии [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/). 167 | 168 | У гофера есть [model sheet](https://go.dev/doc/gopher/modelsheet.jpg), который показывает его особенности и то, как 169 | правильно их изображать. Впервые он был представлен Рене на [докладе](https://www.youtube.com/watch?v=4rw_B4yY69k) 170 | на **Gophercon 2016**. У него есть уникальные черты — это именно _гофер Go_, а не просто какой-то суслик. 171 | 172 | ### Язык называется Go или Golang? 173 | 174 | Язык называется **Go**. Название «golang» появилось из-за того, что сайт изначально был **golang.org** (тогда ещё не 175 | существовало домена **.dev**). Многие до сих пор используют слово _golang_, и оно удобно как метка. Например, хэштег 176 | языка в соцсетях — **#golang**. Но официальное название языка — просто **Go**. 177 | 178 | К слову: хотя [официальный логотип](https://go.dev/blog/go-brand) написан заглавными буквами, название языка пишется 179 | как **Go**, а не **GO**. 180 | 181 | ### Зачем вы создали новый язык? 182 | 183 | Go появился как реакция на неудобство существующих языков и сред, с которыми мы работали в Google. Программирование 184 | стало слишком сложным, и выбор языка отчасти был в этом виноват. Нужно было выбирать: либо быстрая компиляция, либо 185 | быстрая работа программы, либо простота разработки — все три свойства одновременно не были доступны ни в одном из 186 | популярных языков. Многие разработчики предпочитали простоту безопасности и эффективности, переходя на динамически 187 | типизированные языки вроде **Python** и **JavaScript**, вместо **C++** или, в меньшей степени, **Java**. 188 | 189 | Мы были не единственными, кого это беспокоило. После многих лет относительного затишья в мире языков программирования, 190 | Go стал одним из первых в новой волне языков — вместе с **Rust**, **Elixir**, **Swift** и другими, — которые снова 191 | сделали разработку языков активной и почти мейнстримной областью. 192 | 193 | Go решал эти проблемы, пытаясь объединить простоту разработки на интерпретируемом динамически типизированном языке с 194 | эффективностью и безопасностью статически типизированного компилируемого языка. Он также был ориентирован на 195 | современное оборудование, с поддержкой сетевых и многопроцессорных (**multicore**) вычислений. Наконец, работа с Go 196 | должна быть _быстрой_: сборка большого исполняемого файла на одном компьютере должна занимать не больше нескольких секунд. 197 | 198 | Чтобы достичь этих целей, пришлось пересмотреть подходы, унаследованные от существующих языков, и реализовать: 199 | 200 | - композиционную, а не иерархическую систему типов 201 | - поддержку конкурентности (**concurrency**) и сборку мусора (**garbage collection**) 202 | - строгую спецификацию зависимостей 203 | - и многое другое 204 | 205 | Эти задачи невозможно было решить просто с помощью библиотек или инструментов — нужен был новый язык. 206 | 207 | Подробнее о предпосылках и мотивации создания Go, а также о многих аспектах его дизайна, можно прочитать в статье 208 | [**Go at Google**](https://go.dev/talks/2012/splash.article). 209 | 210 | ### Каковы предки Go? 211 | 212 | Go в основном относится к семейству **C** (базовый синтаксис), но также унаследовал многое от семейства 213 | **Pascal/Modula/Oberon** (объявления, пакеты) и заимствовал идеи из языков, вдохновлённых **CSP** Тони Хоара 214 | (Tony Hoare), таких как **Newsqueak** и **Limbo** (конкурентность). 215 | 216 | Однако в целом Go — это новый язык. Во всех аспектах он был спроектирован, исходя из того, что делают программисты и 217 | как сделать программирование, по крайней мере того типа, которым занимаемся мы, более эффективным, 218 | а значит — более увлекательным. 219 | 220 | ### Каковы основные принципы дизайна? 221 | 222 | Когда проектировался Go, наиболее распространёнными языками для написания серверных программ 223 | (по крайней мере в Google) были **Java** и **C++**. Мы считали, что эти языки требуют слишком много рутины и повторений. 224 | Некоторые разработчики в ответ переходили на более динамичные и гибкие языки вроде **Python**, жертвуя при этом 225 | эффективностью и безопасностью типов. Мы же верили, что можно совместить эффективность, безопасность и гибкость в одном языке. 226 | 227 | Go стремится уменьшить количество _typing_ — и в смысле набора текста, и в смысле работы с типами. На протяжении всего 228 | дизайна мы старались сократить загромождённость и сложность. Нет ни предварительных объявлений, ни заголовочных файлов: 229 | всё объявляется ровно один раз. Инициализация выразительная, автоматическая и простая в использовании. Синтаксис чистый 230 | и небогатый ключевыми словами. Повторения (например, `foo.Foo* myFoo = new(foo.Foo)`) устраняются с помощью простого 231 | вывода типа при объявлении и инициализации через конструкцию `:=`. И, пожалуй, наиболее радикально: в Go нет иерархии 232 | типов — типы просто _есть_, им не нужно явно описывать свои отношения. Эти упрощения делают Go одновременно 233 | выразительным и понятным, без потери продуктивности. 234 | 235 | Другой важный принцип — сохранение ортогональности концепций. Методы можно реализовать для любого типа; структуры 236 | описывают данные, а интерфейсы задают абстракцию и т. д. Ортогональность упрощает понимание того, как разные 237 | элементы работают вместе. 238 | 239 | ## Использование 240 | 241 | ### Используется ли Go внутри Google? 242 | 243 | Да. Go широко применяется в продакшене внутри Google. Один из примеров — сервер загрузок **dl.google.com**, который 244 | раздаёт бинарные файлы **Chrome** и другие крупные установочные пакеты, например пакеты для **apt-get**. 245 | 246 | Go, конечно, не единственный язык, используемый в Google, но он является ключевым для ряда направлений, включая 247 | [site reliability engineering (SRE)](https://go.dev/talks/2013/go-sreops.slide) и обработку данных в крупном масштабе. 248 | Кроме того, Go — важная часть программного обеспечения, на котором работает **Google Cloud**. 249 | 250 | ### Какие ещё компании используют Go? 251 | 252 | Использование Go растёт во всём мире, особенно — но вовсе не исключительно — в сфере облачных вычислений. Несколько 253 | крупных проектов облачной инфраструктуры, написанных на Go, — это **Docker** и **Kubernetes**, но их гораздо больше. 254 | 255 | При этом Go применяется не только в облаке. На [сайте go.dev](https://go.dev/) есть список компаний и 256 | [истории успеха](https://go.dev/solutions/case-studies). Кроме того, в Go Wiki есть 257 | [страница GoUsers](https://go.dev/wiki/GoUsers), которая регулярно обновляется и перечисляет многие компании, 258 | использующие Go. 259 | 260 | В Wiki также есть [страница с дополнительными историями успеха](https://go.dev/wiki/SuccessStories) о компаниях и 261 | проектах, которые применяют этот язык. 262 | 263 | ### Можно ли связывать программы на Go с программами на C/C++? 264 | 265 | Да, использовать **C** и **Go** в одном адресном пространстве возможно, но это неестественная связка и часто требует 266 | дополнительного интерфейсного кода. Кроме того, при линковке C с кодом Go теряются свойства безопасности работы с 267 | памятью и управления стеком, которые предоставляет Go. Иногда использование библиотек на C действительно необходимо, 268 | но всегда нужно помнить, что это добавляет риски, отсутствующие в чистом Go-коде, 269 | — поэтому применять такой подход стоит осторожно. 270 | 271 | Если всё же нужно использовать C вместе с Go, то способ зависит от реализации компилятора Go. 272 | «Стандартный» компилятор, входящий в официальный тулчейн Go и поддерживаемый командой Google, называется **gc**. 273 | Кроме него существуют компилятор на базе **GCC** (**gccgo**) и компилятор на базе **LLVM** (**gollvm**), а также всё 274 | большее количество специализированных компиляторов для разных целей (иногда реализующих только подмножество языка), 275 | например [**TinyGo**](https://tinygo.org/). 276 | 277 | Компилятор **gc** использует собственный calling convention и линковщик, поэтому его код нельзя напрямую вызывать из 278 | C-программ (и наоборот). Для этого существует утилита [**cgo**](https://go.dev/cmd/cgo/), которая реализует 279 | _foreign function interface_ и позволяет безопасно вызывать C-библиотеки из Go-кода. Для работы с C++ библиотеками 280 | возможности **cgo** расширяет **SWIG**. 281 | 282 | **cgo** и **SWIG** можно использовать также с **gccgo** и **gollvm**. Так как они применяют традиционный ABI, 283 | теоретически (с большой осторожностью) можно напрямую линковать код этих компиляторов с программами на C или C++, 284 | собранными GCC/LLVM. Однако делать это безопасно можно только при полном понимании calling convention всех 285 | участвующих языков, а также с учётом ограничений стека при вызове C или C++ кода из Go. 286 | 287 | ### Какие IDE поддерживают Go? 288 | 289 | Проект Go не включает собственную IDE, но язык и стандартные библиотеки были спроектированы так, чтобы упрощать 290 | анализ исходного кода. Благодаря этому большинство известных редакторов и IDE хорошо поддерживают 291 | Go — напрямую или через плагины. 292 | 293 | Команда Go также поддерживает язык-сервер для протокола LSP под названием 294 | [**gopls**](https://pkg.go.dev/golang.org/x/tools/gopls#section-readme). Любые инструменты, работающие с LSP, могут 295 | использовать **gopls** для интеграции поддержки Go. 296 | 297 | В список популярных IDE и редакторов с хорошей поддержкой Go входят **Emacs**, **Vim**, **VS Code**, **Atom**, 298 | **Eclipse**, **Sublime**, **IntelliJ** (через отдельный продукт **GoLand**) и многие другие. Скорее всего, ваша 299 | любимая среда разработки также отлично подходит для программирования на Go. 300 | 301 | ### Поддерживает ли Go Google Protocol Buffers? 302 | 303 | Необходимый плагин для компилятора и библиотека доступны в отдельном open source-проекте: 304 | [github.com/golang/protobuf](https://github.com/golang/protobuf/). 305 | 306 | ## Дизайн 307 | 308 | ### Есть ли у Go рантайм? 309 | 310 | У Go есть обширная библиотека времени выполнения, часто называемая просто _runtime_, которая включается в каждую 311 | программу на Go. Эта библиотека реализует сборку мусора (**garbage collection**), конкурентность (**concurrency**), 312 | управление стеком и другие критически важные возможности языка Go. Хотя она играет более центральную роль, чем в C, 313 | рантайм Go можно сравнить с **libc** — стандартной библиотекой C. 314 | 315 | Важно понимать, что рантайм Go **не включает виртуальную машину**, подобную той, что используется в Java. 316 | Программы на Go компилируются заранее в нативный машинный код (или в JavaScript/WebAssembly для некоторых реализаций). 317 | Поэтому, хотя термин _runtime_ часто используют для описания виртуальной среды выполнения, в Go он обозначает 318 | библиотеку, которая предоставляет ключевые сервисы языка. 319 | 320 | ### Что насчёт идентификаторов с Unicode? 321 | 322 | При проектировании Go мы хотели избежать чрезмерной привязки к ASCII, поэтому расширили пространство идентификаторов 323 | за пределы 7-битного ASCII. Правило в Go простое: символы идентификаторов должны быть буквами или цифрами в 324 | определении **Unicode**. Оно легко понимается и реализуется, но имеет ограничения. Например, комбинирующие символы 325 | исключены изначально, а это означает, что некоторые языки (например, деванагари) нельзя использовать в идентификаторах. 326 | 327 | У этого правила есть ещё одно неприятное следствие. Так как экспортируемый идентификатор должен начинаться с заглавной 328 | буквы, идентификаторы, созданные из символов некоторых языков, по определению не могут быть экспортируемыми. На данный 329 | момент единственное решение — использовать что-то вроде `X日本語`, что явно неудовлетворительно. 330 | 331 | С самого раннего этапа разработки языка мы много думали о том, как лучше расширить пространство идентификаторов для 332 | программистов, использующих другие родные языки. Вопрос до сих пор активно обсуждается, и будущие версии языка, 333 | возможно, будут более гибкими в определении идентификатора. Например, можно принять некоторые идеи из 334 | [рекомендаций Unicode](http://unicode.org/reports/tr31/) для идентификаторов. Какой бы путь ни был выбран, он должен 335 | быть совместимым и при этом сохранять (или даже расширять) один из любимых принципов Go — управление видимостью 336 | идентификаторов через регистр букв. 337 | 338 | На данный момент у нас есть простое правило, которое можно будет расширить в будущем без ломки существующих программ, 339 | и которое помогает избежать ошибок, неизбежных при более двусмысленных правилах. 340 | 341 | ### Почему в Go нет возможности X? 342 | 343 | Каждый язык содержит новые возможности и при этом не имеет каких-то «любимых» фич у отдельных разработчиков. 344 | Go проектировался с акцентом на удобство программирования, скорость компиляции, ортогональность концепций и 345 | необходимость поддержки таких возможностей, как конкурентность (**concurrency**) и сборка мусора (**garbage collection**). 346 | Возможно, вашей любимой возможности нет потому, что она плохо вписывается в язык, замедляет компиляцию, 347 | ухудшает ясность дизайна или делает фундаментальную модель системы слишком сложной. 348 | 349 | Если вас расстраивает отсутствие возможности _X_ в Go — простите нас 🙂 Попробуйте изучить те возможности, которые в 350 | Go есть: возможно, они компенсируют отсутствие _X_ неожиданным и интересным образом. 351 | 352 | ### Когда в Go появились обобщённые типы? 353 | 354 | В релизе **Go 1.18** в язык были добавлены _type parameters_ (параметры типов). Это позволило использовать форму 355 | полиморфного или обобщённого программирования (**generic programming**). Подробнее см. в 356 | [спецификации языка](https://go.dev/ref/spec) и [предложении](https://go.dev/design/43651-type-parameters). 357 | 358 | ### Почему Go изначально вышел без обобщённых типов? 359 | 360 | Go задумывался как язык для написания серверных программ, которые должны быть простыми в сопровождении со временем. 361 | (См. [эту статью](https://go.dev/talks/2012/splash.article) для подробностей.) Дизайн был сосредоточен на таких вещах, 362 | как масштабируемость, читаемость и конкурентность (**concurrency**). Полиморфное программирование тогда не казалось 363 | важным для целей языка, поэтому для простоты оно было исключено. 364 | 365 | Обобщения удобны, но вносят дополнительную сложность в систему типов и рантайм. Понадобилось время, чтобы разработать 366 | дизайн, который, на наш взгляд, даёт пользу, соразмерную этой сложности. 367 | 368 | ### Почему в Go нет исключений? 369 | 370 | Мы считаем, что связывание исключений с управляющими конструкциями (как в идиоме `try-catch-finally`) приводит к 371 | запутанному коду. Кроме того, это часто побуждает разработчиков помечать как «исключительные» слишком многие обычные 372 | ошибки, например, невозможность открыть файл. 373 | 374 | Go использует другой подход. Для обычной обработки ошибок многозначные возвращаемые значения позволяют легко сообщить 375 | об ошибке, не перегружая основное возвращаемое значение. 376 | [Канонический тип ошибки вместе с другими возможностями Go](https://go.dev/doc/articles/error_handling.html) делает 377 | обработку ошибок удобной, но сильно отличающейся от других языков. 378 | 379 | В Go также есть несколько встроенных функций для генерации и обработки действительно исключительных ситуаций. 380 | Механизм восстановления выполняется только при сворачивании состояния функции после ошибки. Этого достаточно для 381 | обработки катастрофических случаев, при этом не нужны дополнительные управляющие конструкции, а при правильном 382 | использовании код обработки ошибок остаётся чистым. 383 | 384 | Подробнее см. статью [**Defer, Panic, and Recover**](https://go.dev/doc/articles/defer_panic_recover.html). 385 | Также [пост в блоге «Errors are values»](https://go.dev/blog/errors-are-values) описывает подход к чистой обработке 386 | ошибок в Go, показывая, что раз ошибки — это такие же значения, то для их обработки можно использовать всю мощь языка. 387 | 388 | ### Почему в Go нет assert? 389 | 390 | В Go нет механизма assert. Он, безусловно, удобен, но наш опыт показывает, что разработчики часто используют его как 391 | костыль, чтобы не продумывать корректную обработку и сообщение об ошибках. Правильная обработка ошибок позволяет 392 | серверным программам продолжать работу вместо того, чтобы падать из-за нефатальной ошибки. А корректное сообщение об 393 | ошибках делает их прямыми и понятными, избавляя разработчика от необходимости разбирать длинный трейс 394 | аварийного завершения. Точные сообщения особенно важны, когда ошибки видит программист, не знакомый с кодом. 395 | 396 | Мы понимаем, что это спорный момент. В языке Go и его библиотеках есть немало вещей, которые отличаются от современных 397 | практик — просто потому, что мы считаем, что иногда стоит попробовать иной подход. 398 | 399 | ### Почему конкурентность в Go построена на идеях CSP? 400 | 401 | Со временем конкурентное и многопоточное программирование приобрели репутацию сложной области. Мы считаем, что отчасти 402 | это связано со слишком запутанными моделями вроде [**pthreads**](https://en.wikipedia.org/wiki/POSIX_Threads), 403 | а отчасти — с излишним вниманием к низкоуровневым деталям вроде мьютексов, условных переменных и барьеров памяти. 404 | Более высокоуровневые интерфейсы позволяют писать куда более простой код, даже если «под капотом» всё равно есть 405 | мьютексы и прочее. 406 | 407 | Одной из самых успешных моделей высокоуровневой поддержки конкурентности стал подход Хоара — 408 | **Communicating Sequential Processes (CSP)**. Языки **Occam** и **Erlang** — известные примеры, выросшие из CSP. 409 | 410 | Примитивы конкурентности в Go происходят от другой ветви этой идеи, главной ценностью которой стало мощное понятие 411 | каналов (**channels**) как объектов первого класса. Опыт работы с несколькими предыдущими языками показал, 412 | что модель CSP хорошо вписывается в процедурную парадигму. 413 | 414 | ### Почему горутины, а не потоки? 415 | 416 | Горутины сделаны для того, чтобы конкурентность была простой в использовании. Идея, существующая уже давно, состоит в 417 | мультиплексировании независимо выполняющихся функций — корутин (**coroutines**) — поверх набора потоков. 418 | Когда корутина блокируется, например при вызове блокирующего системного вызова, рантайм автоматически переносит 419 | другие корутины, работающие в том же системном потоке, на другой поток, готовый к выполнению, чтобы они не простаивали. 420 | Программист всего этого не видит — и в этом суть. В результате горутины получаются очень «лёгкими»: они требуют лишь 421 | память под стек размером всего несколько килобайт. 422 | 423 | Чтобы стеки были маленькими, рантайм Go использует динамически изменяемые ограниченные стеки. Новая горутина получает 424 | всего несколько килобайт — и этого почти всегда достаточно. Если нет, рантайм автоматически увеличивает (и уменьшает) 425 | память под стек. Это позволяет сотням тысяч горутины сосуществовать в одном адресном пространстве, 426 | занимая скромный объём памяти. 427 | 428 | Средние накладные расходы на CPU — примерно три недорогие инструкции на каждый вызов функции. На практике можно 429 | запускать сотни тысяч горутины в одном процессе. Если бы горутины были обычными потоками, системные ресурсы закончились 430 | бы намного раньше. 431 | 432 | ### Почему операции с map не являются атомарными? 433 | 434 | После долгих обсуждений было решено, что типичное использование `map` не требует безопасного доступа из нескольких 435 | горутин. А в тех случаях, когда это действительно необходимо, `map` обычно является частью более крупной структуры 436 | данных или вычисления, которые уже синхронизированы. Поэтому требование блокировать мьютекс при каждой операции с `map` 437 | замедлило бы большинство программ и дало бы безопасность лишь в немногих случаях. Это решение далось нелегко, ведь 438 | неконтролируемый доступ к `map` может привести к краху программы. 439 | 440 | Язык не исключает атомарные обновления `map`. Когда это нужно (например, при запуске недоверенного кода), реализация 441 | может синхронизировать доступ к `map`. 442 | 443 | Доступ к `map` небезопасен только тогда, когда происходят изменения. Если все горутины только читают данные — ищут 444 | элементы или обходят `map` с помощью цикла `for range` — и не изменяют его (не присваивают элементы и не удаляют их), 445 | то такой доступ безопасен даже без синхронизации. 446 | 447 | Чтобы помочь корректному использованию `map`, некоторые реализации языка содержат специальную проверку, которая в 448 | рантайме автоматически сообщает, если `map` был небезопасно изменён конкурентно. Кроме того, в пакете **sync** есть 449 | тип [`sync.Map`](https://pkg.go.dev/sync#Map), который хорошо подходит для некоторых сценариев 450 | (например, для статических кэшей), хотя и не является полноценной заменой встроенному типу `map`. 451 | 452 | ### Примете ли вы мои изменения в язык? 453 | 454 | Разработчики часто предлагают улучшения языка — [список рассылки](https://groups.google.com/group/golang-nuts) 455 | хранит богатую историю таких обсуждений, — но лишь немногие из этих изменений были приняты. 456 | 457 | Хотя Go — это open source-проект, язык и стандартные библиотеки защищены 458 | [обещанием совместимости](https://go.dev/doc/go1compat), которое запрещает изменения, ломающие существующие программы 459 | (по крайней мере на уровне исходного кода; иногда программы нужно просто перекомпилировать, чтобы они продолжали работать). 460 | Если ваше предложение нарушает спецификацию Go 1, мы даже не можем его рассматривать, каким бы ценным оно ни было. 461 | В будущем может выйти крупная версия Go, несовместимая с Go 1, но обсуждения на эту тему только начались, 462 | и одно ясно точно: таких несовместимостей будет крайне мало. Более того, обещание совместимости обязывает нас 463 | предоставить автоматический путь миграции старых программ, если такая ситуация возникнет. 464 | 465 | Даже если ваше предложение совместимо со спецификацией Go 1, оно может противоречить целям дизайна языка. 466 | Статья [_Go at Google: Language Design in the Service of Software Engineering_](https://go.dev/talks/2012/splash.article) 467 | объясняет происхождение Go и мотивацию его архитектурных решений. 468 | 469 | ## Типы 470 | 471 | ### Является ли Go объектно-ориентированным языком? 472 | 473 | И да, и нет. В Go есть типы и методы, и он позволяет писать в объектно-ориентированном стиле, но в языке 474 | нет иерархии типов. Концепция **interface** в Go предлагает иной подход, который, на наш взгляд, проще в использовании 475 | и в некоторых отношениях более общий. Также есть возможность встраивать одни типы в другие, что даёт аналог, 476 | но не идентичный, наследованию. 477 | 478 | Кроме того, методы в Go более универсальны, чем в **C++** или **Java**: их можно определять для любых данных, 479 | даже для встроенных типов, таких как обычные «неупакованные» (**unboxed**) целые числа. Методы не ограничены только 480 | структурами (аналогами классов). 481 | 482 | К тому же отсутствие иерархии типов делает «объекты» в Go гораздо более лёгкими, чем в языках вроде C++ или Java. 483 | 484 | ### Как в Go получить динамическую диспетчеризацию методов? 485 | 486 | Единственный способ динамической диспетчеризации методов в Go — через **interface**. Методы, определённые у структуры 487 | или любого другого конкретного типа, всегда разрешаются статически. 488 | 489 | ### Почему в Go нет наследования типов? 490 | 491 | Объектно-ориентированное программирование, по крайней мере в самых известных языках, уделяет слишком много внимания 492 | связям между типами — связям, которые во многих случаях могли бы выводиться автоматически. Go выбирает другой путь. 493 | 494 | Вместо того чтобы заставлять программиста заранее объявлять, что два типа связаны, в Go любой тип автоматически 495 | удовлетворяет интерфейсу (**interface**), если реализует подмножество его методов. Помимо снижения «бумажной работы», 496 | этот подход даёт реальные преимущества. Тип может реализовывать несколько интерфейсов сразу, без сложностей 497 | традиционного множественного наследования. Интерфейсы могут быть очень лёгкими — даже интерфейс с одним методом или 498 | вовсе без методов может выражать полезную концепцию. Интерфейсы можно добавлять задним числом — если появилась новая 499 | идея или для целей тестирования — без модификации исходных типов. Так как между типами и интерфейсами нет явных связей, 500 | в Go нет иерархии типов, которую нужно поддерживать или обсуждать. 501 | 502 | Эти идеи можно использовать для построения конструкций, аналогичных безопасным по типам пайпам Unix. Например, 503 | `fmt.Fprintf` позволяет форматированно печатать в любой вывод, а не только в файл; пакет `bufio` может существовать 504 | совершенно отдельно от файлового ввода-вывода; пакеты `image` умеют генерировать сжатые изображения. 505 | Все эти возможности опираются на один интерфейс — `io.Writer`, который описывает всего один метод — `Write`. 506 | И это лишь вершина айсберга: интерфейсы Go глубоко влияют на то, как структурируются программы. 507 | 508 | К этому стилю неявных зависимостей типов нужно привыкнуть, но именно он делает Go столь продуктивным языком. 509 | 510 | ### Почему `len` — это функция, а не метод? 511 | 512 | Мы обсуждали этот вопрос, но решили, что реализация `len` и других подобных операций в виде функций вполне подходит 513 | на практике и не усложняет вопросы интерфейсов (в смысле Go-типов) для базовых типов. 514 | 515 | ### Почему в Go нет перегрузки методов и операторов? 516 | 517 | Диспетчеризация методов упрощается, если ей не нужно дополнительно сопоставлять типы. Опыт работы с другими языками 518 | показал, что наличие множества методов с одинаковым именем, но разными сигнатурами, иногда бывает полезным, 519 | но на практике часто оказывается запутанным и хрупким решением. Поэтому решение в Go — сопоставлять только по имени 520 | и требовать согласованности в типах — стало важным упрощением системы типов. 521 | 522 | Что касается перегрузки операторов, она скорее даёт удобство, чем является необходимостью. И снова — без неё всё проще. 523 | 524 | ### Почему в Go нет ключевого слова `implements`? 525 | 526 | Тип в Go реализует интерфейс автоматически — просто реализуя все методы этого интерфейса, и ничего больше. 527 | Это свойство позволяет определять и использовать интерфейсы без необходимости изменять существующий код. 528 | Такой подход реализует [структурную типизацию](https://en.wikipedia.org/wiki/Structural_type_system), 529 | которая способствует разделению ответственности, улучшает повторное использование кода и облегчает построение 530 | новых паттернов по мере развития программы. 531 | 532 | Семантика интерфейсов — одна из главных причин того, что Go ощущается таким простым и лёгким языком. 533 | 534 | См. также [вопрос о наследовании типов](#наследование) для подробностей. 535 | 536 | ### Как гарантировать, что мой тип реализует интерфейс? 537 | 538 | Можно попросить компилятор проверить, что тип `T` реализует интерфейс `I`, попытавшись выполнить присваивание с 539 | использованием нулевого значения для `T` или указателя на `T` (в зависимости от ситуации): 540 | 541 | ```go 542 | type T struct{} 543 | var _ I = T{} // Проверка, что T реализует I. 544 | var _ I = (*T)(nil) // Проверка, что *T реализует I. 545 | ``` 546 | 547 | Если `T` (или `*T`) не реализует интерфейс `I`, ошибка будет обнаружена на этапе компиляции. 548 | 549 | Если вы хотите, чтобы пользователи интерфейса явно указывали, что они его реализуют, можно добавить в методный набор 550 | интерфейса специальный метод с описательным именем. Например: 551 | 552 | ```go 553 | type Fooer interface { 554 | Foo() 555 | ImplementsFooer() 556 | } 557 | ``` 558 | 559 | Теперь тип обязан реализовать метод `ImplementsFooer`, чтобы считаться `Fooer`. Это явно документирует факт реализации 560 | и отображается в выводе команды [`go doc`](https://go.dev/cmd/go/#hdr-Show_documentation_for_package_or_symbol). 561 | 562 | ```go 563 | type Bar struct{} 564 | func (b Bar) ImplementsFooer() {} 565 | func (b Bar) Foo() {} 566 | ``` 567 | 568 | Большинство кода в Go не использует такие ограничения, так как они уменьшают гибкость интерфейсов. 569 | Но иногда они необходимы, чтобы устранить неоднозначность между похожими интерфейсами. 570 | 571 | ### Почему тип `T` не реализует интерфейс `Equal`? 572 | 573 | Рассмотрим простой интерфейс, представляющий объект, который может сравнивать себя с другим значением: 574 | 575 | ```go 576 | type Equaler interface { 577 | Equal(Equaler) bool 578 | } 579 | ``` 580 | 581 | и тип `T`: 582 | 583 | ```go 584 | type T int 585 | func (t T) Equal(u T) bool { return t == u } // не реализует Equaler 586 | ``` 587 | 588 | В отличие от аналогичной ситуации в некоторых полиморфных системах типов, `T` **не** реализует `Equaler`. 589 | Аргумент метода `T.Equal` имеет тип `T`, а не буквально требуемый тип `Equaler`. 590 | 591 | В Go система типов не делает автоматического «повышения» аргумента метода — это обязанность программиста. 592 | Например, тип `T2` действительно реализует `Equaler`: 593 | 594 | ```go 595 | type T2 int 596 | func (t T2) Equal(u Equaler) bool { return t == u.(T2) } // реализует Equaler 597 | ``` 598 | 599 | Однако и это отличается от других систем типов, потому что в Go _любой_ тип, удовлетворяющий интерфейсу `Equaler`, 600 | может быть передан в аргумент метода `T2.Equal`. Поэтому во время выполнения нужно проверять, что аргумент имеет 601 | именно тип `T2`. В некоторых языках такая проверка гарантируется на этапе компиляции. 602 | 603 | Есть и обратный пример: 604 | 605 | ```go 606 | type Opener interface { 607 | Open() Reader 608 | } 609 | 610 | func (t T3) Open() *os.File 611 | ``` 612 | 613 | В Go тип `T3` не реализует `Opener`, хотя в другом языке это могло бы быть допустимо. 614 | 615 | Хотя система типов Go делает меньше за программиста в таких случаях, отсутствие подтипов делает правила соответствия 616 | интерфейсам очень простыми: имена функций и их сигнатуры должны в точности совпадать с определёнными в интерфейсе. 617 | Это правило также легко и эффективно реализовать. Мы считаем, что эти преимущества компенсируют отсутствие 618 | автоматического повышения типов. 619 | 620 | ### Можно ли преобразовать `[]T` в `[]interface{}`? 621 | 622 | Напрямую — нет. 623 | Это запрещено спецификацией языка, потому что у этих двух типов разное представление в памяти. 624 | Нужно копировать элементы по одному в новый срез. 625 | 626 | Пример: преобразование среза `[]int` в срез `[]interface{}`: 627 | 628 | ```go 629 | t := []int{1, 2, 3, 4} 630 | s := make([]interface{}, len(t)) 631 | for i, v := range t { 632 | s[i] = v 633 | } 634 | ``` 635 | 636 | ### Можно ли преобразовать `[]T1` в `[]T2`, если у `T1` и `T2` одинаковый базовый тип? 637 | 638 | Последняя строка в этом примере кода не компилируется: 639 | 640 | ```go 641 | type T1 int 642 | type T2 int 643 | var t1 T1 644 | var x = T2(t1) // OK 645 | var st1 []T1 646 | var sx = ([]T2)(st1) // NOT OK 647 | ``` 648 | 649 | В Go типы тесно связаны с методами: каждый именованный тип имеет (возможно, пустой) набор методов. 650 | Общее правило такое: можно менять имя самого типа при преобразовании (и вместе с этим — его набор методов), 651 | но нельзя менять имя (и набор методов) элементов составного типа. В Go требуется явное указание при преобразованиях типов. 652 | 653 | ### Почему моё значение ошибки `nil` не равно `nil`? 654 | 655 | Под капотом интерфейсы реализованы как пара: тип `T` и значение `V`. 656 | `V` — это конкретное значение (например, `int`, `struct` или указатель), никогда не интерфейс, и оно имеет тип `T`. 657 | 658 | Например, если сохранить значение `int` равное 3 в интерфейсе, результатом будет интерфейсное значение вида: 659 | (`T=int`, `V=3`). Значение `V` также называют _динамическим значением_ интерфейса, так как в процессе выполнения 660 | программа может помещать в интерфейс разные значения `V` (и соответствующие им типы `T`). 661 | 662 | Интерфейсное значение считается `nil` только если и `T`, и `V` не заданы (`T=nil`, `V` не установлено). В частности, 663 | «пустой» интерфейс всегда хранит `nil`-тип. Если же мы сохраним внутри интерфейса нулевой указатель типа `*int`, 664 | то тип будет `*int` независимо от значения указателя: (`T=*int`, `V=nil`). Такое интерфейсное значение не будет равно 665 | `nil`, _даже если указатель внутри равен `nil`_. 666 | 667 | Эта ситуация часто сбивает с толку и возникает, когда в интерфейсное значение, например `error`, сохраняется `nil`-указатель: 668 | 669 | ```go 670 | func returnsError() error { 671 | var p *MyError = nil 672 | if bad() { 673 | p = ErrBad 674 | } 675 | return p // всегда вернёт ненулевой error 676 | } 677 | ``` 678 | 679 | Если всё прошло хорошо, функция вернёт `nil`-указатель `p`. Но результат будет интерфейс `error`, содержащий (`T=*MyError`, `V=nil`). 680 | Поэтому при сравнении возвращённого значения с `nil` вызовущий код решит, что ошибка есть, хотя её на самом деле не было. 681 | 682 | Чтобы вернуть настоящий `nil`-интерфейс, функция должна явно вернуть `nil`: 683 | 684 | ```go 685 | func returnsError() error { 686 | if bad() { 687 | return ErrBad 688 | } 689 | return nil 690 | } 691 | ``` 692 | 693 | Хорошая практика для функций, возвращающих ошибки, — всегда использовать тип `error` в сигнатуре (как в примере выше), 694 | а не конкретный тип вроде `*MyError`. Это гарантирует корректное создание значения ошибки. Например, 695 | [`os.Open`](https://go.dev/pkg/os/#Open) возвращает `error`, хотя если оно не равно `nil`, то всегда имеет конкретный 696 | тип [`*os.PathError`](https://go.dev/pkg/os/#PathError). 697 | 698 | Похожие ситуации могут возникать всякий раз, когда используются интерфейсы. Нужно помнить: если в интерфейсе сохранено 699 | какое-либо конкретное значение, сам интерфейс не будет `nil`. Подробнее см. статью 700 | [**The Laws of Reflection**](https://go.dev/doc/articles/laws_of_reflection.html). 701 | 702 | ### Почему нулевые типы (zero-size types) ведут себя странно? 703 | 704 | Go поддерживает нулевые типы, такие как структура без полей (`struct{}`) или массив без элементов (`[0]byte`). 705 | В нулевой тип нельзя записать никакого значения, но они бывают полезны, когда само значение не нужно. 706 | Например, `map[int]struct{}` или тип, у которого есть методы, но нет данных. 707 | 708 | Разные переменные нулевого типа могут располагаться в одной и той же области памяти. Это безопасно, 709 | так как хранить в них всё равно нечего. 710 | 711 | Кроме того, язык не гарантирует, будут ли указатели на разные переменные нулевого типа равны или нет. 712 | Такое сравнение может вернуть `true` в одном месте программы и `false` в другом — в зависимости от того, 713 | как именно программа скомпилирована и выполнена. 714 | 715 | Есть и другая особенность: указатель на поле нулевого типа в структуре не должен пересекаться с указателем 716 | на другой объект в памяти. Это могло бы вызвать путаницу в работе сборщика мусора. Поэтому если последним полем в 717 | структуре идёт нулевой тип, структура будет дополнена (padded), чтобы указатель на это поле не пересекался с памятью, 718 | расположенной сразу после структуры. 719 | 720 | Таким образом, программа: 721 | 722 | ```go 723 | func main() { 724 | type S struct { 725 | f1 byte 726 | f2 struct{} 727 | } 728 | fmt.Println(unsafe.Sizeof(S{})) 729 | } 730 | ``` 731 | 732 | в большинстве реализаций Go выведет `2`, а не `1`. 733 | 734 | ### Почему в Go нет нетегированных объединений (unions), как в C? 735 | 736 | Нетегированные объединения нарушили бы гарантии безопасности памяти в Go. 737 | 738 | ### Почему в Go нет вариантных типов? 739 | 740 | Вариантные типы (также известные как алгебраические типы) позволяют задать, что значение может принимать один из 741 | набора других типов, но только из этого набора. Классический пример в системном программировании: ошибка может быть 742 | сетевой, связанной с безопасностью или прикладной, и вызывающий код может определить источник проблемы по типу ошибки. 743 | Другой пример — синтаксическое дерево, где каждый узел может быть разного типа: объявление, выражение, присваивание и т. д. 744 | 745 | Мы рассматривали возможность добавить вариантные типы в Go, но после обсуждения решили отказаться, так как они 746 | пересекаются с интерфейсами и создают путаницу. Например, что делать, если элементы вариантного типа сами являются интерфейсами? 747 | 748 | Кроме того, часть задач, решаемых вариантными типами, уже покрыта самим языком. Пример с ошибками легко выразить через 749 | значение интерфейса, хранящее ошибку, и `type switch` для различения случаев. Пример с синтаксическим деревом также 750 | можно реализовать, хотя и не так элегантно. 751 | 752 | ### Почему в Go нет ковариантных возвращаемых типов? 753 | 754 | Ковариантные возвращаемые типы означали бы, что интерфейс 755 | 756 | ```go 757 | type Copyable interface { 758 | Copy() interface{} 759 | } 760 | ``` 761 | 762 | считается реализованным методом 763 | 764 | ```go 765 | func (v Value) Copy() Value 766 | ``` 767 | 768 | потому что `Value` реализует пустой интерфейс. В Go же сигнатуры методов должны совпадать _точно_, 769 | поэтому `Value` не реализует `Copyable`. 770 | 771 | Go разделяет понятие того, _что делает тип_ (его методы), и его реализацию. Если два метода возвращают разные типы, 772 | это значит, что они делают разные вещи. 773 | 774 | Разработчики, которые хотят ковариантные возвращаемые типы, часто пытаются выразить иерархию типов через интерфейсы. 775 | В Go же более естественно сохранять чёткое разделение между интерфейсом и реализацией. 776 | 777 | ## Значения 778 | 779 | ### Почему в Go нет неявных числовых преобразований? 780 | 781 | Удобство автоматических преобразований числовых типов в C перевешивается тем замешательством, которое они вызывают. 782 | Когда выражение становится беззнаковым? Каков его размер? Происходит ли переполнение? Является ли результат переносимым, 783 | независимо от машины, на которой он выполняется? 784 | 785 | Кроме того, такие преобразования усложняют работу компилятора: «обычные арифметические преобразования» в C трудно 786 | реализовать, и они ведут себя непоследовательно на разных архитектурах. 787 | 788 | Из соображений переносимости мы решили сделать всё ясным и однозначным, ценой необходимости явных преобразований в коде. 789 | Определение констант в Go — это значения произвольной точности, свободные от знака и ограничения по размеру — 790 | значительно смягчает этот недостаток. 791 | 792 | Отдельная деталь: в отличие от C, типы `int` и `int64` в Go — это разные типы, даже если `int` реализован как 64-битный. 793 | Тип `int` считается «общим», а если важен точный размер целого числа, Go поощряет явное указание. 794 | 795 | ### Как работают константы в Go? 796 | 797 | Хотя Go строго относится к преобразованиям между переменными разных числовых типов, с константами язык гораздо гибче. 798 | Литералы вроде `23`, `3.14159` и [`math.Pi`](https://go.dev/pkg/math/#pkg-constants) принадлежат к некоему «идеальному 799 | числовому пространству»: они имеют произвольную точность и не подвержены переполнению или потере точности. 800 | 801 | Например, значение `math.Pi` в исходниках задано с точностью до 63 знаков после запятой, и выражения с этой константой 802 | сохраняют точность выше, чем способен хранить `float64`. Только при присваивании константы (или выражения с ней) 803 | переменной — то есть размещении в памяти — она становится «обычным» числом с привычными свойствами и ограничениями 804 | точности для данного типа. 805 | 806 | Кроме того, поскольку константы — это просто числа, а не значения конкретного типа, их можно использовать свободнее, 807 | чем переменные. Это смягчает строгие правила преобразования типов. Например, выражение 808 | 809 | ```go 810 | sqrt2 := math.Sqrt(2) 811 | ``` 812 | 813 | не вызывает жалоб компилятора, потому что идеальное число 2 может быть безопасно и точно преобразовано в `float64` для 814 | вызова `math.Sqrt`. 815 | 816 | Подробнее о константах см. в [блог-посте Constants](https://go.dev/blog/constants). 817 | 818 | ### Почему `map` встроен в язык? 819 | 820 | По той же причине, что и строки: это настолько мощная и важная структура данных, что предоставление одной качественной 821 | реализации с синтаксической поддержкой делает программирование проще и приятнее. 822 | 823 | Мы считаем, что реализация `map` в Go достаточно сильна, чтобы покрыть подавляющее большинство случаев. Если конкретное 824 | приложение может выиграть от собственной реализации, её всегда можно написать, но синтаксически это будет менее удобно. 825 | Такой компромисс нам кажется разумным. 826 | 827 | ### Почему в Go нельзя использовать срезы в качестве ключей map? 828 | 829 | Поиск в `map` требует оператора равенства, которого у срезов нет. 830 | Равенство для срезов не реализовано, потому что оно не имеет чёткой семантики: возникают вопросы поверхностного или 831 | глубокого сравнения, сравнения по указателю или по значению, обработки рекурсивных типов и т. д. 832 | 833 | Мы можем вернуться к этой теме позже (и добавление равенства для срезов не сломает существующие программы), но пока, 834 | без ясного понимания того, что именно должно означать равенство срезов, проще было оставить это поведение как есть. 835 | 836 | Для структур и массивов равенство определено, поэтому их можно использовать как ключи в `map`. 837 | 838 | ### Почему `map`, срезы и каналы являются ссылками, а массивы — значениями? 839 | 840 | У этого решения длинная история. Изначально `map` и каналы синтаксически были указателями, и было невозможно объявить 841 | или использовать их без указателя. Также долго обсуждалось, как именно должны работать массивы. 842 | 843 | В итоге мы решили, что строгое разделение указателей и значений делает язык менее удобным. Изменение этих типов так, 844 | чтобы они вели себя как ссылки на связанные общие структуры данных, решило проблему. Да, это добавило немного лишней 845 | сложности в язык, но сильно повысило удобство: Go стал более продуктивным и комфортным языком. 846 | 847 | ## Написание кода 848 | 849 | ### Как документируются библиотеки? 850 | 851 | Для доступа к документации из командной строки инструмент [go](https://go.dev/pkg/cmd/go/) имеет подкоманду 852 | [doc](https://go.dev/pkg/cmd/go/#hdr-Show_documentation_for_package_or_symbol), которая выводит текстовую документацию 853 | для объявлений, файлов, пакетов и т. д. 854 | 855 | Глобальная страница поиска пакетов — [pkg.go.dev/pkg/](https://pkg.go.dev/pkg/) — запускает сервер, который извлекает 856 | документацию пакетов из исходного кода Go в интернете и отображает её в виде HTML со ссылками на объявления и 857 | связанные элементы. Это самый простой способ узнать о существующих библиотеках Go. 858 | 859 | В ранние дни проекта существовала похожая программа — `godoc`, которую можно было запускать для извлечения документации 860 | из файлов на локальной машине; [pkg.go.dev/pkg/](https://pkg.go.dev/pkg/) по сути является её потомком. 861 | Другой потомок — команда [`pkgsite`](https://pkg.go.dev/golang.org/x/pkgsite/cmd/pkgsite), которая, как и `godoc`, 862 | может работать локально, хотя пока не интегрирована в результаты, которые показывает `go doc`. 863 | 864 | ### Есть ли у Go руководство по стилю программирования? 865 | 866 | Явного «гайдлайна» по стилю нет, хотя существует узнаваемый «стиль Go». 867 | 868 | В Go установились соглашения, которые направляют решения по именованию, форматированию и организации файлов. 869 | Документ [Effective Go](https://go.dev/doc/effective_go) содержит советы по этим темам. 870 | 871 | Более того, программа `gofmt` — это pretty-printer, созданный для того, чтобы автоматически применять правила 872 | оформления кода. Она заменила собой традиционные своды правил и оговорок, которые оставляли место для интерпретаций. 873 | Весь код в репозитории Go, а также подавляющее большинство open source-кода, проходит через `gofmt`. 874 | 875 | Также полезен документ [Go Code Review Comments](https://go.dev/s/comments) — это сборник коротких заметок об идиомах 876 | Go, которые часто упускают программисты. Это удобный справочник для тех, кто делает code review Go-проектов. 877 | 878 | ### Как отправить патчи в библиотеки Go? 879 | 880 | Исходный код библиотек находится в каталоге `src` репозитория. 881 | Если вы хотите внести значительное изменение, пожалуйста, обсудите его в списке рассылки перед началом работы. 882 | 883 | Подробнее о том, как участвовать в развитии проекта, см. в документе 884 | [Contributing to the Go project](https://go.dev/doc/contribute.html). 885 | 886 | ### Почему `go get` использует HTTPS при клонировании репозитория? 887 | 888 | Во многих компаниях исходящий трафик разрешён только через стандартные TCP-порты 80 (HTTP) и 443 (HTTPS), 889 | а трафик на другие порты блокируется, включая TCP-порт 9418 (git) и TCP-порт 22 (SSH). 890 | 891 | При использовании HTTPS вместо HTTP, `git` по умолчанию проверяет сертификаты, обеспечивая защиту от атак 892 | «man-in-the-middle», подслушивания и подмены данных. Поэтому команда `go get` использует HTTPS — это безопаснее. 893 | 894 | `git` можно настроить так, чтобы он аутентифицировался через HTTPS или использовал SSH вместо HTTPS. 895 | Для аутентификации через HTTPS можно добавить строку в файл `$HOME/.netrc`, который читает git: 896 | 897 | ```shell 898 | machine github.com login *USERNAME* password *APIKEY* 899 | ``` 900 | 901 | Для аккаунтов GitHub в качестве пароля можно использовать 902 | [personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/). 903 | 904 | Также `git` можно настроить так, чтобы он использовал SSH вместо HTTPS для всех URL с определённым префиксом. 905 | Например, для GitHub добавьте в `~/.gitconfig`: 906 | 907 | ```shell 908 | [url “ssh://git@github.com/”] 909 | insteadOf = https://github.com/ 910 | ``` 911 | 912 | При работе с приватными модулями, но с использованием публичного прокси для зависимостей, может потребоваться задать 913 | переменную окружения `GOPRIVATE`. Подробнее см. в разделе [private modules](https://go.dev/ref/mod#private-modules). 914 | 915 | ### Как управлять версиями пакетов с помощью `go get`? 916 | 917 | В тулчейн Go встроена система управления версиями наборов связанных пакетов — **модули**. 918 | Модули появились в [Go 1.11](https://go.dev/doc/go1.11#modules) и считаются готовыми к продакшену начиная с 919 | [Go 1.14](https://go.dev/doc/go1.14#introduction). 920 | 921 | Чтобы создать проект с модулями, выполните команду [`go mod init`](https://go.dev/ref/mod#go-mod-init). 922 | Она создаст файл `go.mod`, в котором будут отслеживаться версии зависимостей: 923 | 924 | ```shell 925 | go mod init example/project 926 | ``` 927 | 928 | Чтобы добавить, обновить или откатить зависимость, используйте [`go get`](https://go.dev/ref/mod#go-get): 929 | 930 | ```shell 931 | go get golang.org/x/text@v0.3.5 932 | ``` 933 | 934 | Подробнее см. в [учебнике «Create a module»](https://go.dev/doc/tutorial/create-module.html). 935 | См. также раздел [Developing modules](https://go.dev/doc/#developing-modules) — он посвящён управлению зависимостями 936 | с помощью модулей. 937 | 938 | Пакеты внутри модулей должны сохранять обратную совместимость по мере развития, следуя 939 | [правилу совместимости импортов](https://research.swtch.com/vgo-import): 940 | 941 | > Если старый и новый пакет имеют один и тот же путь импорта, 942 | > новый пакет обязан быть обратно совместимым со старым. 943 | 944 | Хорошая справка по этому поводу — [руководство по совместимости Go 1](https://go.dev/doc/go1compat): не удаляйте 945 | экспортируемые имена, используйте именованные литералы композитных типов и т. д. Если нужна новая функциональность, 946 | лучше добавить новое имя, а не изменять старое. 947 | 948 | Модули закрепляют это правило через [семантическое версионирование](https://semver.org/lang/ru/) и 949 | semantic import versioning. Если необходима несовместимая смена API, следует выпускать новый мажорный релиз модуля. 950 | Для модулей с мажорной версией 2 и выше требуется [суффикс версии](https://go.dev/ref/mod#major-version-suffixes) 951 | в пути импорта (например, `/v2`). Это сохраняет правило совместимости импортов: пакеты разных мажорных версий одного 952 | модуля имеют разные пути импорта. 953 | 954 | ## Указатели и выделение памяти 955 | 956 | ### Когда параметры функций передаются по значению? 957 | 958 | Как и во всех языках семейства C, в Go всё передаётся **по значению**. 959 | То есть функция всегда получает копию передаваемого объекта, как если бы выполнялось присваивание значения параметру. 960 | 961 | Например, при передаче значения `int` функция получает копию этого числа. При передаче указателя копируется сам 962 | указатель, но не данные, на которые он ссылается. 963 | (См. [раздел о методах со значениями и указателями](https://go.dev/doc/faq#methods_on_values_or_pointers), 964 | где обсуждается, как это влияет на получателей методов.) 965 | 966 | Значения `map` и срезов ведут себя как указатели: это дескрипторы, которые содержат ссылки на данные. 967 | Копирование `map` или среза не копирует данные, на которые они ссылаются. Копирование интерфейсного значения создаёт 968 | копию объекта, хранящегося в интерфейсе: 969 | 970 | - если внутри интерфейса структура, копируется сама структура 971 | - если внутри интерфейса указатель, копируется указатель, но не данные, на которые он указывает 972 | 973 | Важно понимать: речь идёт о **семантике** операций. Конкретные реализации компилятора могут применять оптимизации, 974 | чтобы избежать лишних копирований, но только если они не меняют семантику. 975 | 976 | ### Когда стоит использовать указатель на интерфейс? 977 | 978 | Практически никогда. Указатели на интерфейсные значения возникают только в редких, хитрых ситуациях, например когда 979 | нужно скрыть тип интерфейсного значения для отложенной обработки. 980 | 981 | Распространённая ошибка — передавать в функцию, ожидающую интерфейс, указатель на интерфейсное значение. 982 | Компилятор выдаст ошибку, но ситуация может быть запутанной, потому что иногда 983 | [указатель действительно нужен, чтобы удовлетворить интерфейсу](https://go.dev/doc/faq#different_method_sets). 984 | 985 | Важно понять: хотя указатель на конкретный тип может удовлетворять интерфейсу, за одним исключением 986 | _указатель на интерфейс никогда не может удовлетворить интерфейсу_. 987 | 988 | Рассмотрим пример: 989 | 990 | ```go 991 | var w io.Writer 992 | ``` 993 | 994 | Функция fmt.Fprintf принимает первым аргументом значение, реализующее io.Writer — то есть что-то с методом Write. 995 | Поэтому корректный вызов выглядит так: 996 | 997 | ```go 998 | fmt.Fprintf(w, "hello, world\n") 999 | ``` 1000 | 1001 | Но если мы передадим адрес w, программа не скомпилируется: 1002 | 1003 | ```go 1004 | fmt.Fprintf(&w, "hello, world\n") // Ошибка компиляции 1005 | ``` 1006 | 1007 | Единственное исключение: любое значение, даже указатель на интерфейс, можно присвоить переменной пустого 1008 | интерфейсного типа – `interface{}`. Но даже в этом случае это почти наверняка ошибка: результат будет сбивать с толку. 1009 | 1010 | ### Следует ли определять методы для значений или для указателей? 1011 | 1012 | ```go 1013 | func (s *MyStruct) pointerMethod() { } // метод для указателя 1014 | func (s MyStruct) valueMethod() { } // метод для значения 1015 | ``` 1016 | 1017 | Для программистов, не привыкших к указателям, разница между этими примерами может быть запутанной, 1018 | но на самом деле всё просто. При определении метода для типа его получатель (`s` в примере) ведёт себя точно так же, 1019 | как если бы это был аргумент функции. Таким образом, вопрос «делать получатель значением или указателем» равнозначен 1020 | вопросу «делать аргумент функции значением или указателем». 1021 | 1022 | Есть несколько критериев выбора: 1023 | 1024 | 1. **Необходимость изменять получатель.** 1025 | Если метод должен модифицировать получатель, то он _обязан_ быть указателем 1026 | (Срезы и `map` ведут себя как ссылки, но, например, чтобы изменить длину среза внутри метода, 1027 | получатель всё равно должен быть указателем.) В примере выше, если `pointerMethod` изменяет поля `s`, эти изменения 1028 | будут видны вызывающему коду. А `valueMethod` получает копию аргумента (так определяется передача по значению), 1029 | поэтому изменения останутся невидимыми. 1030 | 1031 | Кстати, в Java получатели методов всегда являются указателями, хотя эта особенность несколько скрыта 1032 | (и в последнее время в язык добавляют методы со значениями). В Go же именно получатели-значения — это необычное явление. 1033 | 1034 | 2. **Эффективность.** 1035 | Если получатель — это крупная структура (`struct`), то дешевле использовать указатель. 1036 | 1037 | 3. **Согласованность.** 1038 | Если часть методов типа должна иметь указатель-получатель, остальные тоже стоит сделать такими — чтобы набор 1039 | методов был единообразным вне зависимости от того, как используется тип. Подробнее см. раздел о 1040 | [наборах методов](https://go.dev/doc/faq#different_method_sets). 1041 | 1042 | 4. **Простота для мелких типов.** 1043 | Для базовых типов, срезов и небольших структур передача значения очень дёшева. Если семантика метода не требует 1044 | указателя, то получатель-значение будет и эффективен, и ясен. 1045 | 1046 | ### В чём разница между `new` и `make`? 1047 | 1048 | Кратко: `new` просто выделяет память, а `make` инициализирует срезы, `map` и каналы. 1049 | 1050 | Подробнее см. в [соответствующем разделе Effective Go](https://go.dev/doc/effective_go#allocation_new). 1051 | 1052 | ### Каков размер `int` на 64-битной машине? 1053 | 1054 | Размеры типов `int` и `uint` зависят от реализации, но на одной платформе они всегда одинаковы. 1055 | Для переносимости кода, который зависит от конкретного размера, следует использовать типы с явным указанием размера, 1056 | например `int64`. 1057 | 1058 | На 32-битных машинах компиляторы по умолчанию используют 32-битные целые числа, а на 64-битных — 64-битные. 1059 | (Исторически это было не всегда так.) 1060 | 1061 | С другой стороны, скаляры с плавающей точкой и комплексные типы всегда имеют фиксированный размер 1062 | (в Go нет базовых типов `float` или `complex`), потому что программист должен осознавать точность при работе 1063 | с числами с плавающей точкой. 1064 | 1065 | Типом по умолчанию для (нетипизированной) вещественной константы является `float64`. 1066 | Таким образом, запись 1067 | 1068 | ```go 1069 | foo := 3.0 1070 | ``` 1071 | 1072 | объявит переменную foo типа float64. 1073 | 1074 | Если же нужна переменная float32, то её тип должен быть указан явно: 1075 | 1076 | ```go 1077 | var foo float32 = 3.0 1078 | ``` 1079 | 1080 | Или константе можно задать тип с помощью приведения: 1081 | 1082 | ```go 1083 | foo := float32(3.0) 1084 | ``` 1085 | 1086 | ### Как узнать, выделена ли переменная в куче или на стеке? 1087 | 1088 | С точки зрения корректности программы это знать не нужно. 1089 | Каждая переменная в Go существует, пока на неё есть ссылки. 1090 | Где именно она хранится — не имеет значения для семантики языка. 1091 | 1092 | Однако место хранения влияет на эффективность. 1093 | Когда возможно, компилятор Go размещает локальные переменные в стековом фрейме функции. 1094 | Но если компилятор не может доказать, что переменная не будет использоваться после возврата из функции, он обязан 1095 | выделить её в куче, управляемой сборщиком мусора, чтобы избежать ошибок с висячими указателями. 1096 | Кроме того, если локальная переменная очень большая, её также может быть разумнее хранить в куче, а не на стеке. 1097 | 1098 | В текущих компиляторах переменные, у которых берут адрес, считаются кандидатами на размещение в куче. 1099 | Однако базовый механизм _escape analysis_ умеет распознавать случаи, когда такие переменные не «живут» дольше функции, 1100 | и позволяет хранить их в стеке. 1101 | 1102 | ### Почему мой Go-процесс использует так много виртуальной памяти? 1103 | 1104 | Аллокатор памяти Go резервирует большую область виртуальной памяти как арену для выделений. 1105 | Эта виртуальная память локальна для конкретного процесса Go; её резервирование не отбирает память у других процессов. 1106 | 1107 | Чтобы узнать фактический объём памяти, выделенный процессу Go, используйте команду Unix `top` и смотрите колонку 1108 | `RES` (Linux) или `RSIZE` (macOS). 1109 | 1110 | ## Конкурентность 1111 | 1112 | ### Какие операции атомарны? А как насчёт мьютексов? 1113 | 1114 | Описание атомарности операций в Go можно найти в документе [Модель памяти Go](/ref/mem). 1115 | 1116 | Низкоуровневые средства синхронизации и атомарные примитивы доступны в пакетах [`sync`](/pkg/sync) и 1117 | [`sync/atomic`](/pkg/sync/atomic). Они хорошо подходят для простых задач — например, увеличения счётчиков ссылок или 1118 | организации мелкомасштабного взаимного исключения. 1119 | 1120 | Для более высокоуровневых задач, таких как координация работы множества конкурентных серверов, лучше использовать 1121 | более выразительные приёмы. Go поддерживает такой подход через горутины и каналы. Например, можно структурировать 1122 | программу так, чтобы только одна горутина в каждый момент времени отвечала за конкретный участок данных. 1123 | 1124 | Этот подход отражён в оригинальной [Go-притче](https://www.youtube.com/watch?v=PAAkCSZUG1c): 1125 | 1126 | > Не общайтесь через общую память. Вместо этого разделяйте память через общение. 1127 | 1128 | См. также код-пример [Share Memory By Communicating](/doc/codewalk/sharemem/) и связанный с ним 1129 | [блог-пост](/blog/share-memory-by-communicating) для подробного разбора этой идеи. 1130 | 1131 | Крупные конкурентные программы, как правило, комбинируют оба подхода. 1132 | 1133 | ### Почему моя программа не работает быстрее на большем числе CPU? 1134 | 1135 | То, будет ли программа работать быстрее на нескольких CPU, зависит от решаемой задачи. 1136 | Язык Go предоставляет примитивы конкурентности — горутины и каналы, 1137 | но конкурентность даёт параллелизм только тогда, когда сама задача по своей природе параллельна. 1138 | 1139 | Задачи, которые являются строго последовательными, не могут быть ускорены добавлением CPU. 1140 | А вот задачи, которые можно разбить на независимые части, способные выполняться параллельно, 1141 | могут заметно выиграть в производительности. 1142 | 1143 | Иногда добавление CPU может даже замедлить программу. 1144 | На практике это происходит, если программа тратит больше времени на синхронизацию или обмен данными, 1145 | чем на полезные вычисления. В таких случаях использование нескольких потоков ОС может ухудшить производительность, 1146 | так как передача данных между потоками требует переключения контекста — достаточно дорогой операции, 1147 | стоимость которой растёт при увеличении числа CPU. 1148 | 1149 | Например, [пример решета Эратосфена](/ref/spec#An_example_package) из спецификации Go запускает множество горутин, 1150 | но реального параллелизма там нет; увеличение числа потоков (CPU) скорее замедлит выполнение, чем ускорит его. 1151 | 1152 | Подробнее об этом см. доклад [Concurrency is not Parallelism](/blog/concurrency-is-not-parallelism). 1153 | 1154 | ### Как управлять количеством CPU? 1155 | 1156 | Количество CPU, доступных одновременно для выполнения горутин, контролируется переменной окружения `GOMAXPROCS`. 1157 | По умолчанию её значение равно числу доступных ядер CPU. Поэтому программы, способные выполняться параллельно, 1158 | будут использовать все ядра автоматически. 1159 | 1160 | Чтобы изменить количество CPU для параллельного выполнения, нужно задать переменную окружения или вызвать функцию 1161 | [`runtime.GOMAXPROCS`](/pkg/runtime/#GOMAXPROCS), которая настраивает рантайм на использование другого числа потоков. 1162 | Установка значения `1` полностью исключает параллелизм, заставляя горутины выполняться по очереди. 1163 | 1164 | При этом рантайм может выделять больше потоков, чем указано в `GOMAXPROCS`, чтобы обрабатывать несколько 1165 | одновременных I/O-запросов. `GOMAXPROCS` определяет только количество горутин, которые могут выполняться _одновременно_; 1166 | однако заблокированных в системных вызовах горутин может быть гораздо больше. 1167 | 1168 | Планировщик горутин в Go достаточно хорошо балансирует горутины и потоки и даже умеет прерывать выполнение горутины, 1169 | чтобы другие на том же потоке не оставались без внимания. Тем не менее он не идеален. Если вы наблюдаете проблемы с 1170 | производительностью, попробуйте настраивать `GOMAXPROCS` для конкретного приложения. 1171 | 1172 | ### Почему у горутин нет уникального идентификатора (ID)? 1173 | 1174 | Горутины не имеют имён — это просто анонимные рабочие. Они не предоставляют программисту уникального идентификатора, 1175 | имени или структуры данных. Многие ожидают, что оператор `go` вернёт какой-то объект, с помощью которого можно будет 1176 | управлять горутиной позже, но это не так. 1177 | 1178 | Основная причина анонимности горутин в том, чтобы при написании конкурентного кода был доступен весь язык Go, 1179 | без ограничений. Когда у потоков или горутин есть имена, возникают шаблоны использования, которые могут ограничить то, 1180 | что может сделать библиотека, работающая с ними. 1181 | 1182 | Например, если дать горутине имя и построить вокруг неё модель, она становится «особенной», и появляется соблазн 1183 | связывать всю обработку именно с этой горутиной. Это мешает использовать несколько горутин (возможно, общих) 1184 | для выполнения задачи. Если бы пакет `net/http` связывал состояние запроса с конкретной горутиной, клиенты не могли бы 1185 | использовать больше горутин при обработке запроса. 1186 | 1187 | Опыт с библиотеками — например, для графических систем, где всё выполнение жёстко привязано к «главному потоку», — 1188 | показывает, насколько неудобен и ограничивающ такой подход в языке с поддержкой конкурентности. Сам факт существования 1189 | «особого» потока или горутины заставляет искажать архитектуру программы, чтобы избежать падений и других проблем, 1190 | связанных с выполнением кода «не в том» потоке. 1191 | 1192 | В тех случаях, когда горутина действительно особенная, в языке есть механизмы, такие как каналы, которые позволяют 1193 | гибко взаимодействовать с ней. 1194 | 1195 | ## Функции и методы 1196 | 1197 | ### Почему у `T` и `*T` разные наборы методов? 1198 | 1199 | Согласно [спецификации Go](/ref/spec#Types), набор методов типа `T` включает все методы с получателем (`receiver`) типа `T`, 1200 | а набор методов указателя `*T` — все методы с получателем `*T` или `T`. То есть набор методов `*T` включает методы 1201 | `T`, но не наоборот. 1202 | 1203 | Эта разница возникает потому, что если интерфейсное значение содержит указатель `*T`, вызов метода может получить 1204 | значение через разыменование указателя. Но если интерфейсное значение содержит саму структуру `T`, безопасного способа 1205 | получить её указатель нет. (Это позволило бы методу изменять содержимое значения внутри интерфейса, 1206 | что запрещено спецификацией языка.) 1207 | 1208 | Даже в случаях, когда компилятор мог бы взять адрес значения и передать его в метод, изменения были бы потеряны для 1209 | вызывающего кода, если метод меняет данные. 1210 | 1211 | Например, если бы следующий код был допустим: 1212 | 1213 | ```go 1214 | var buf bytes.Buffer 1215 | io.Copy(buf, os.Stdin) 1216 | ``` 1217 | 1218 | он скопировал бы стандартный ввод во временную копию `buf`, а не в сам `buf`. 1219 | Такое поведение почти никогда не является ожидаемым и поэтому запрещено в Go. 1220 | 1221 | ### Что происходит с замыканиями, запущенными как горутины? 1222 | 1223 | Из-за особенностей работы переменных цикла до версии Go 1.22 (см. обновление в конце) возникала путаница при 1224 | использовании замыканий вместе с конкурентностью. 1225 | 1226 | Рассмотрим пример: 1227 | 1228 | ```go 1229 | func main() { 1230 | done := make(chan bool) 1231 | 1232 | values := []string{"a", "b", "c"} 1233 | for _, v := range values { 1234 | go func() { 1235 | fmt.Println(v) 1236 | done <- true 1237 | }() 1238 | } 1239 | 1240 | // ждём завершения всех горутин перед выходом 1241 | for _ = range values { 1242 | <-done 1243 | } 1244 | } 1245 | ``` 1246 | 1247 | Можно было ожидать вывод `a, b, c`. Но на деле чаще всего выводился `c, c, c`. Причина в том, что на каждой итерации 1248 | цикла используется одна и та же переменная `v`. Все замыкания разделяют её, и когда горутина выполняется, значение `v` 1249 | уже могло измениться с момента запуска. 1250 | 1251 | Чтобы обнаруживать такие и похожие проблемы заранее, используйте [`go vet`](/cmd/go/#hdr-Run_go_tool_vet_on_packages). 1252 | 1253 | Чтобы «привязать» текущее значение `v` к замыканию в каждой итерации, нужно создать новую переменную. Есть два способа. 1254 | 1255 | Первый — передать её как аргумент в анонимную функцию: 1256 | 1257 | ```go 1258 | for _, v := range values { 1259 | go func(u string) { 1260 | fmt.Println(u) 1261 | done <- true 1262 | }(v) 1263 | } 1264 | ``` 1265 | 1266 | Здесь значение `v` передаётся в функцию и становится доступным внутри как `u`. 1267 | 1268 | Второй способ — явно объявить новую переменную: 1269 | 1270 | ```go 1271 | for _, v := range values { 1272 | v := v // создаём новую 'v' 1273 | go func() { 1274 | fmt.Println(v) 1275 | done <- true 1276 | }() 1277 | } 1278 | ``` 1279 | 1280 | Такое поведение языка — не создавать новую переменную на каждой итерации — впоследствии было признано ошибкой. 1281 | Начиная с [Go 1.22](/wiki/LoopvarExperiment), для каждой итерации действительно создаётся новая переменная, 1282 | и эта проблема устранена. 1283 | 1284 | ## Управление потоком выполнения 1285 | 1286 | ### Почему в Go нет оператора `?:`? 1287 | 1288 | В Go нет тернарного оператора. Того же результата можно добиться с помощью конструкции `if-else`: 1289 | 1290 | ```go 1291 | if expr { 1292 | n = trueVal 1293 | } else { 1294 | n = falseVal 1295 | } 1296 | ``` 1297 | 1298 | Причина отсутствия `?:` в Go в том, что его часто используют для создания чрезмерно сложных и трудночитаемых выражений. 1299 | Форма с `if-else`, хоть и длиннее, но однозначно яснее. Языку достаточно одной конструкции условного управления потоком. 1300 | 1301 | ## Параметры типов 1302 | 1303 | ### Зачем Go нужны параметры типов? 1304 | 1305 | Параметры типов позволяют использовать _обобщённое программирование_ (generic programming), когда функции и структуры 1306 | данных определяются через типы, которые указываются позже — в момент их использования. 1307 | 1308 | Например, можно написать функцию, возвращающую минимум из двух значений любого упорядоченного типа, без необходимости 1309 | писать отдельную реализацию для каждого типа. 1310 | 1311 | Подробное объяснение с примерами см. в статье [Why Generics?](/blog/why-generics). 1312 | 1313 | ### Как дженерики реализованы в Go? 1314 | 1315 | Компилятор может выбирать: 1316 | 1317 | - компилировать каждую конкретную инстанциацию отдельно 1318 | - или объединять похожие инстанциации в одну реализацию 1319 | 1320 | Второй подход похож на функцию с параметром-интерфейсом. Разные компиляторы могут принимать разные решения в 1321 | зависимости от ситуации. 1322 | 1323 | Стандартный компилятор Go обычно создаёт одну инстанциацию для всех аргументов типов с одинаковой _формой_. 1324 | Форма определяется свойствами типа, такими как размер и расположение указателей. 1325 | 1326 | В будущих версиях возможны эксперименты с балансом между временем компиляции, эффективностью выполнения и размером кода. 1327 | 1328 | ### Как дженерики в Go сравниваются с дженериками в других языках? 1329 | 1330 | Базовая функциональность во всех языках схожа – можно писать типы и функции, использующие типы, которые указываются позже. 1331 | Однако есть различия. 1332 | 1333 | #### Java 1334 | 1335 | В Java компилятор проверяет дженерики на этапе компиляции, но удаляет информацию о типах во время выполнения. 1336 | Это называется [type erasure](https://en.wikipedia.org/wiki/Generics_in_Java#Problems_with_type_erasure). 1337 | Например, тип `List` на этапе компиляции превращается в недженерик `List` во время выполнения. 1338 | Поэтому при использовании рефлексии в Java невозможно отличить `List` от `List`. 1339 | В Go же информация о полном типе сохраняется и доступна через рефлексию. 1340 | 1341 | Java также использует подстановочные знаки (`List`, `List`) для реализации 1342 | ковариантности и контравариантности. В Go таких концепций нет, что делает дженерики проще. 1343 | 1344 | #### C++ 1345 | 1346 | Традиционно шаблоны в C++ не накладывали ограничений на типовые аргументы, но начиная с C++20 появились 1347 | [concepts](https://en.wikipedia.org/wiki/Concepts_(C%2B%2B)), позволяющие задавать ограничения. 1348 | В Go ограничения обязательны для всех параметров типов. В C++ concepts описываются как маленькие фрагменты кода, 1349 | которые должны компилироваться с типовыми аргументами. В Go же ограничения — это интерфейсы, задающие допустимые типы. 1350 | 1351 | Кроме того, C++ поддерживает метапрограммирование шаблонов, а Go — нет. На практике все компиляторы C++ компилируют 1352 | шаблон при каждой инстанциации. Go может использовать разные подходы в зависимости от ситуации. 1353 | 1354 | #### Rust 1355 | 1356 | В Rust ограничения называются _trait bounds_. Ассоциация между _trait_ и типом должна быть явно определена 1357 | либо в crate с _trait_, либо в crate с самим типом. В Go же типы удовлетворяют ограничениям неявно, как и при 1358 | реализации интерфейсов. 1359 | 1360 | В стандартной библиотеке Rust есть _traits_ для операций вроде сравнения или сложения. В Go стандартная библиотека 1361 | таких интерфейсов не содержит — их можно описать в пользовательском коде. Единственное исключение — встроенный 1362 | интерфейс `comparable`, описывающий свойство, которое нельзя выразить в системе типов. 1363 | 1364 | #### Python 1365 | 1366 | Python не является статически типизированным языком, поэтому можно сказать, что все его функции _всегда дженерики_: 1367 | их можно вызвать с любыми типами, а ошибки будут выявлены только во время выполнения. 1368 | 1369 | ### Почему в Go для списка параметров типов используются квадратные скобки? 1370 | 1371 | В Java и C++ для списка параметров типов применяются угловые скобки – `List` в Java или 1372 | `std::vector` в C++. 1373 | 1374 | Для Go этот вариант был недоступен из-за синтаксической неоднозначности. При разборе кода внутри функции, например: 1375 | 1376 | ```go 1377 | v := F 1378 | ``` 1379 | 1380 | в момент, когда парсер встречает символ `<`, непонятно — это инстанциация обобщённой функции или выражение с оператором `<`. 1381 | Разрешить это без информации о типах крайне сложно. 1382 | 1383 | Рассмотрим пример: 1384 | 1385 | ```go 1386 | a, b = w < x, y > (z) 1387 | ``` 1388 | 1389 | Без информации о типах невозможно понять, что находится справа: 1390 | 1391 | - пара выражений (`w < x` и `y > z`), 1392 | - или вызов обобщённой функции (`(w)(z)`), возвращающей два значения. 1393 | 1394 | Ключевое решение в дизайне Go — возможность разбора синтаксиса _без знания типов_. С угловыми скобками этого достичь нельзя. 1395 | 1396 | Go не единственный язык, использующий квадратные скобки для дженериков – например, в Scala тоже применяются квадратные скобки. 1397 | 1398 | ### Почему Go не поддерживает методы с параметрами типов? 1399 | 1400 | Go допускает, что обобщённый (generic) тип может иметь методы, но кроме получателя (receiver) аргументы этих методов 1401 | не могут использовать параметризованные типы. Мы не предполагаем, что в Go когда-либо появятся обобщённые методы. 1402 | 1403 | #### Проблема реализации 1404 | 1405 | Основная сложность — как реализовать их на практике. Например, как проверить, реализует ли значение в интерфейсе другой интерфейс с дополнительными методами? 1406 | 1407 | Рассмотрим этот тип — пустую структуру с обобщённым методом `Nop`, который возвращает свой аргумент для любого типа: 1408 | 1409 | ```go 1410 | type Empty struct{} 1411 | 1412 | func (Empty) Nop[T any](x T) T { 1413 | return x 1414 | } 1415 | ``` 1416 | 1417 | Теперь представим, что значение `Empty` сохранено в `any` и передано в функцию, которая проверяет, что оно может делать: 1418 | 1419 | ```go 1420 | func TryNops(x any) { 1421 | if x, ok := x.(interface{ Nop(string) string }); ok { 1422 | fmt.Printf("string %s\n", x.Nop("hello")) 1423 | } 1424 | if x, ok := x.(interface{ Nop(int) int }); ok { 1425 | fmt.Printf("int %d\n", x.Nop(42)) 1426 | } 1427 | if x, ok := x.(interface{ Nop(io.Reader) io.Reader }); ok { 1428 | data, err := io.ReadAll(x.Nop(strings.NewReader("hello world"))) 1429 | fmt.Printf("reader %q %v\n", data, err) 1430 | } 1431 | } 1432 | ``` 1433 | 1434 | Как должен работать этот код, если `x` имеет тип `Empty`? Получается, что `x` должен удовлетворять всем трём проверкам, 1435 | а также любым другим возможным вариантам. 1436 | 1437 | #### Что происходит при вызове таких методов? 1438 | 1439 | - Для **необобщённых методов** компилятор генерирует код всех реализаций и включает их в итоговую программу 1440 | - Для **обобщённых методов** количество реализаций может быть бесконечным, поэтому нужен другой подход 1441 | 1442 | #### Возможные варианты 1443 | 1444 | 1. **Компиляция на этапе линковки** 1445 | После линковки собрать список всех возможных проверок интерфейсов, найти типы, которые могли бы их удовлетворить, 1446 | но у которых нет скомпилированных методов, и снова вызвать компилятор для генерации кода. 1447 | Это сильно замедлит сборку (особенно инкрементальную) и может даже привести к бесконечному циклу перекомпиляций. 1448 | 1449 | 2. **JIT-компиляция во время выполнения** 1450 | Реализовать JIT, чтобы компилировать необходимые методы на лету. 1451 | Но Go выигрывает за счёт простоты и предсказуемости AOT-компиляции, и внедрение JIT ради одной возможности слишком усложнит язык. 1452 | 1453 | 3. **Медленный fallback** 1454 | Сгенерировать медленную реализацию для каждого обобщённого метода с таблицей функций для всех возможных операций 1455 | над параметрами типов и использовать её при динамических проверках. 1456 | Это сделает производительность непредсказуемой: методы с неожиданными типами будут сильно медленнее. 1457 | 1458 | 4. **Запретить обобщённым методам удовлетворять интерфейсам** 1459 | Но интерфейсы — ключевая часть Go. 1460 | Такой запрет недопустим с точки зрения дизайна. 1461 | 1462 | #### Вывод 1463 | 1464 | Ни один из вариантов не является хорошим, поэтому было выбрано решение «ни один из вышеперечисленных». 1465 | 1466 | Вместо методов с параметрами типов используйте **функции верхнего уровня** с параметрами типов или добавляйте параметры 1467 | типов **к самому типу-получателю**. 1468 | 1469 | Подробнее (с примерами) см. [proposal](https://go.dev/design/43651-type-parameters#no-parameterized-methods). 1470 | 1471 | ## Почему нельзя использовать более специфичный тип для получателя параметризованного типа? 1472 | 1473 | Объявления методов обобщённого типа пишутся с получателем, который включает имена параметров типа. 1474 | Возможно, из-за схожести синтаксиса со спецификацией типов в месте вызова, некоторые считают, что это позволяет 1475 | создавать метод, специализированный для определённых аргументов типа, указывая конкретный тип в получателе, например `string`: 1476 | 1477 | ```go 1478 | type S[T any] struct { f T } 1479 | 1480 | func (s S[string]) Add(t string) string { 1481 | return s.f + t 1482 | } 1483 | ``` 1484 | 1485 | Однако это не работает, потому что слово `string` воспринимается компилятором как имя параметра типа в методе. 1486 | Сообщение об ошибке компилятора будет примерно таким: 1487 | 1488 | > operator + not defined on s.f (variable of type string) 1489 | 1490 | Это может сбивать с толку, потому что оператор `+` корректно работает с предопределённым типом `string`. 1491 | Но объявление переопределило, для этого метода, определение `string`, и оператор не работает с этой новой, 1492 | не связанной версией `string`. 1493 | 1494 | Переопределять предопределённые имена допустимо, но это странная практика и часто является ошибкой. 1495 | 1496 | ## Почему компилятор не может вывести аргумент типа в моей программе? 1497 | 1498 | Существует много случаев, когда программисту легко понять, какой аргумент типа для обобщённого типа или функции должен 1499 | быть использован, но язык не позволяет компилятору вывести его автоматически. 1500 | 1501 | Вывод типов намеренно ограничен, чтобы гарантировать, что никогда не будет неоднозначности в том, какой тип был выведен. 1502 | Опыт работы с другими языками показывает, что неожиданный вывод типов может привести к значительной путанице 1503 | при чтении и отладке программы. 1504 | 1505 | Всегда можно явно указать аргумент типа, который должен использоваться в вызове. 1506 | 1507 | В будущем могут быть добавлены новые формы вывода типов, при условии, что правила останутся простыми и понятными. 1508 | 1509 | ## Пакеты и тестирование 1510 | 1511 | ### Как создать пакет из нескольких файлов? 1512 | 1513 | Поместите все файлы исходного кода пакета в отдельный каталог. Файлы исходного кода могут свободно обращаться к 1514 | элементам из других файлов; никаких предварительных объявлений или заголовочных файлов не требуется. 1515 | 1516 | Кроме того, что код разбит на несколько файлов, пакет будет компилироваться и тестироваться точно так же, 1517 | как и пакет из одного файла. 1518 | 1519 | ### Как написать модульный тест? 1520 | 1521 | Создайте новый файл, имя которого оканчивается на `_test.go`, в том же каталоге, что и исходные файлы пакета. 1522 | Внутри этого файла подключите пакет `testing`: 1523 | 1524 | ```go 1525 | import "testing" 1526 | ``` 1527 | 1528 | и напишите функции следующего вида: 1529 | 1530 | ```go 1531 | func TestFoo(t *testing.T) { 1532 | ... 1533 | } 1534 | ``` 1535 | 1536 | Запустите `go test` в этом каталоге. Эта команда находит функции, начинающиеся с `Test`, собирает тестовый бинарник 1537 | и выполняет его. 1538 | 1539 | Подробнее см. в документации: 1540 | 1541 | - [How to Write Go Code](https://go.dev/doc/code) 1542 | - [Пакет testing](https://pkg.go.dev/testing) 1543 | - [Подкоманда go test](https://pkg.go.dev/cmd/go#hdr-Test_packages) 1544 | 1545 | ### Где моя любимая вспомогательная функция для тестирования? 1546 | 1547 | Стандартный пакет Go [`testing`](https://pkg.go.dev/testing) упрощает написание модульных тестов, но в нём отсутствуют 1548 | функции, предоставляемые фреймворками тестирования других языков, например функции утверждений (assertion functions). 1549 | 1550 | В [раннем разделе](#почему-в-go-нет-assert) этого документа уже объяснялось, почему в Go нет утверждений, 1551 | и те же аргументы применимы к использованию `assert` в тестах. 1552 | 1553 | Правильная обработка ошибок означает, что после сбоя одного теста остальные тесты всё равно выполняются, чтобы 1554 | разработчик получил полную картину того, что именно пошло не так. Гораздо полезнее, если тест сообщит, что `isPrime` 1555 | даёт неверный результат для 2, 3, 5 и 7 (или для 2, 4, 8 и 16), чем если он сообщит только про ошибку на 2 1556 | и прекратит работу. Программист, запустивший тест, может вовсе не знать код, в котором произошла ошибка. 1557 | Время, потраченное сейчас на хорошее сообщение об ошибке, потом окупится, когда тест снова «сломается». 1558 | 1559 | Связанная мысль: фреймворки тестирования часто превращаются в мини-языки со своими условными конструкциями, 1560 | управляющими структурами и механизмами вывода. Но в Go всё это уже есть. Зачем изобретать заново? 1561 | Мы предпочитаем писать тесты на Go: это один язык вместо двух, и тесты остаются простыми и понятными. 1562 | 1563 | Если кажется, что дополнительный код для хороших сообщений об ошибках избыточен, можно использовать табличные тесты 1564 | — запуск теста по списку входных и выходных значений, заданных в структуре данных 1565 | (Go отлично поддерживает литералы структур данных). Тогда работа по написанию одного теста и сообщений об ошибках 1566 | «распределяется» на множество кейсов. 1567 | 1568 | Стандартная библиотека Go полна наглядных примеров, например 1569 | [тесты форматирования для пакета `fmt`](https://cs.opensource.google/go/go/+/refs/tags/go1.23.1:src/fmt/fmt_test.go). 1570 | 1571 | ### Почему _X_ нет в стандартной библиотеке? 1572 | 1573 | Назначение стандартной библиотеки — поддерживать рантайм, обеспечивать связь с операционной системой и предоставлять 1574 | ключевую функциональность, которая требуется многим Go-программам, например форматированный ввод/вывод 1575 | и сетевые возможности. В неё также входят элементы, важные для веб-разработки, включая криптографию и поддержку 1576 | стандартов вроде HTTP, JSON и XML. 1577 | 1578 | Нет чётких критериев, определяющих, что именно включается в стандартную библиотеку, так как долгое время это была 1579 | _единственная_ библиотека Go. Однако сегодня существуют критерии, определяющие добавление новых пакетов. 1580 | 1581 | Новые дополнения в стандартную библиотеку редки, и планка для включения очень высока. Код в стандартной библиотеке 1582 | несёт значительные расходы на поддержку (часто за счёт тех, кто не был его автором), подпадает под 1583 | [обещание совместимости Go 1](https://go.dev/doc/go1compat) (что блокирует исправления любых изъянов в API), 1584 | и следует [графику релизов Go](https://go.dev/s/releasesched), что мешает быстро доставлять исправления пользователям. 1585 | 1586 | Большинство нового кода должно жить за пределами стандартной библиотеки и быть доступным через команду 1587 | [`go get`](https://pkg.go.dev/cmd/go). Такой код может иметь собственных мейнтейнеров, цикл релизов 1588 | и собственные гарантии совместимости. Найти пакеты и прочитать их документацию можно на [pkg.go.dev](https://pkg.go.dev/). 1589 | 1590 | Хотя в стандартной библиотеке есть элементы, которые туда не совсем вписываются (например, `log/syslog`), 1591 | мы продолжаем их поддерживать из-за обещания совместимости Go 1. Но при этом мы поощряем, чтобы новый код 1592 | публиковался вне стандартной библиотеки. 1593 | 1594 | ## Имплементация 1595 | 1596 | ### Какая технология компилятора используется для сборки компиляторов? 1597 | 1598 | Существует несколько промышленных компиляторов для Go, а также ряд других в разработке для различных платформ. 1599 | 1600 | Компилятор по умолчанию — `gc`, он входит в дистрибутив Go как часть поддержки команды [`go`](https://pkg.go.dev/cmd/go). 1601 | Изначально `gc` был написан на C из-за сложностей с бутстраппингом — нужен был бы компилятор Go, чтобы поднять среду Go. 1602 | Но с релиза Go 1.5 компилятор стал программой на Go. Переписывание из C в Go было выполнено с помощью инструментов 1603 | автоматического преобразования, что описано в [дизайн-документе](https://go.dev/s/go13compiler) и в 1604 | [докладе](https://talks.golang.org/2015/gogo.slide#1). 1605 | 1606 | Таким образом, компилятор теперь "сам себя компилирует" (self-hosting), что возвращает нас к проблеме бутстраппинга. 1607 | Решение — иметь рабочую установку Go уже заранее, как обычно бывает с рабочим C-компилятором. 1608 | История того, как поднять новую среду Go из исходников, описана [здесь](https://go.dev/s/go15bootstrap) 1609 | и [здесь](https://go.dev/doc/install/source). 1610 | 1611 | `gc` написан на Go, использует рекурсивный спускающийся парсер и собственный загрузчик (loader), 1612 | также написанный на Go, но основанный на загрузчике Plan 9, для генерации ELF/Mach-O/PE бинарников. 1613 | 1614 | Компилятор **Gccgo** — это фронтенд, написанный на C++ с рекурсивным спускающимся парсером, 1615 | подключённый к стандартному бэкенду GCC. Экспериментальный [LLVM-бэкенд](https://go.googlesource.com/gollvm/) 1616 | использует тот же фронтенд. 1617 | 1618 | В начале проекта рассматривалась возможность использования LLVM для `gc`, но было решено, что он слишком громоздкий 1619 | и медленный, чтобы достичь целей по производительности. Более того, начиная с LLVM, было бы сложнее внедрить 1620 | изменения ABI и связанные вещи (например, управление стеком), которые нужны Go, но отсутствуют в стандартной C-среде. 1621 | 1622 | Go оказался отличным языком для написания компилятора Go, хотя изначально это не планировалось. То, что проект не был 1623 | self-hosting с самого начала, позволило Go сосредоточиться на своём первоначальном назначении — сетевых серверах. 1624 | Если бы мы решили, что Go должен компилировать сам себя ещё на ранней стадии, мы могли бы получить язык, больше 1625 | ориентированный на построение компиляторов — достойная цель, но не та, которую мы ставили изначально. 1626 | 1627 | Хотя у `gc` есть собственная реализация, в стандартной библиотеке доступны [лексер и парсер](https://pkg.go.dev/go/parser), 1628 | а также [проверка типов](https://pkg.go.dev/go/types). Компилятор `gc` использует модифицированные версии этих библиотек. 1629 | 1630 | ### Как реализована поддержка времени выполнения? 1631 | 1632 | Снова из-за проблем бутстраппинга код рантайма изначально был написан в основном на C (с небольшим количеством ассемблера), 1633 | но позже был переведён на Go (за исключением некоторых ассемблерных частей). 1634 | 1635 | Рантайм Gccgo использует glibc. Компилятор gccgo реализует горутины с помощью техники сегментированных стеков 1636 | (segmented stacks), поддержка которых была добавлена в результате недавних изменений в компоновщике gold. 1637 | Gollvm аналогично построен на соответствующей инфраструктуре LLVM. 1638 | 1639 | ### Почему мой тривиальный код компилируется в такой большой бинарник? 1640 | 1641 | Линкер в инструментальной цепочке gc по умолчанию создаёт статически слинкованные бинарники. 1642 | Поэтому все Go-бинарники включают в себя Go runtime, а также информацию о типах времени выполнения, 1643 | необходимую для поддержки динамических проверок типов, рефлексии и даже трассировки стека при `panic`. 1644 | 1645 | Для сравнения: простой C-пример "hello, world", скомпилированный и статически слинкованный с помощью gcc на Linux, 1646 | весит примерно 750 КБ, включая реализацию `printf`. 1647 | 1648 | Эквивалентная Go-программа, использующая `fmt.Printf`, весит пару мегабайт, 1649 | но при этом включает куда более мощную поддержку времени выполнения, а также информацию о типах и отладке. 1650 | 1651 | Go-программу, скомпилированную gc, можно слинковать с флагом: 1652 | 1653 | ```shell 1654 | -ldflags=-w 1655 | ``` 1656 | 1657 | Этот флаг отключает генерацию DWARF, удаляя отладочную информацию из бинарника без потери функциональности. 1658 | Это может существенно уменьшить размер итогового файла. 1659 | 1660 | ### Могу ли я отключить жалобы на неиспользуемые переменные/импорты? 1661 | 1662 | Наличие неиспользуемой переменной может указывать на баг, а лишние импорты замедляют компиляцию, 1663 | и это замедление может стать существенным по мере роста проекта и команды. 1664 | Поэтому Go отказывается компилировать программы с неиспользуемыми переменными или импортами, 1665 | жертвуя краткосрочным удобством ради долгосрочной скорости сборки и ясности кода. 1666 | 1667 | Тем не менее, при разработке такие ситуации часто возникают временно, 1668 | и бывает раздражающе удалять их вручную перед компиляцией. 1669 | 1670 | Некоторые просили добавить опцию компилятора, чтобы отключить эти проверки 1671 | или хотя бы превратить их в предупреждения. 1672 | Однако этого не сделали, потому что флаги компилятора не должны менять семантику языка, 1673 | а компилятор Go не выдаёт предупреждений — только ошибки, которые останавливают сборку. 1674 | 1675 | Причины отсутствия предупреждений: 1676 | 1677 | 1. Если о чём-то стоит пожаловаться, это стоит исправить в коде. 1678 | (И наоборот: если это не стоит исправлять, то и не стоит упоминать.) 1679 | 2. Предупреждения создают «шум» — множество малозначимых сообщений могут замаскировать настоящие ошибки. 1680 | 1681 | Решение: используйте пустой идентификатор `_`, 1682 | чтобы временно заглушить неиспользуемые элементы при разработке. 1683 | 1684 | ```golang 1685 | import "unused" 1686 | 1687 | // Эта декларация помечает импорт как использованный. 1688 | var _ = unused.Item // TODO: удалить перед коммитом! 1689 | 1690 | func main() { 1691 | debugData := debug.Profile() 1692 | _ = debugData // Используется только во время отладки. 1693 | .... 1694 | } 1695 | ``` 1696 | 1697 | Сегодня большинство Go-разработчиков используют утилиту [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports). 1698 | 1699 | Она автоматически правит импорты, устраняя проблему неиспользуемых импортов. 1700 | Её можно легко подключить к редактору или IDE для автозапуска при сохранении файла. 1701 | Такая функциональность также встроена в [gopls](https://pkg.go.dev/golang.org/x/tools/gopls). 1702 | 1703 | ### Почему мой антивирус считает, что дистрибутив Go или скомпилированный бинарник заражён? 1704 | 1705 | Это довольно распространённая ситуация, особенно на Windows, и почти всегда это ложное срабатывание. 1706 | Коммерческие антивирусные программы часто «путаются» в структуре Go-бинарников, 1707 | так как встречают их гораздо реже, чем программы, собранные на других языках. 1708 | 1709 | Если вы только что установили дистрибутив Go и система сообщает об инфекции — это наверняка ошибка. 1710 | Для полной уверенности можно проверить загрузку, сравнив контрольную сумму с указанной на странице загрузок: 1711 | https://go.dev/dl/ 1712 | 1713 | В любом случае, если вы считаете, что срабатывание ошибочное, сообщите об этом производителю антивируса. 1714 | Возможно, со временем антивирусные системы научатся правильно обрабатывать Go-программы. 1715 | 1716 | ## Производительность 1717 | 1718 | ### Почему Go показывает плохие результаты в бенчмарке X? 1719 | 1720 | Одной из целей дизайна Go было приближение к производительности C для сопоставимых программ. 1721 | Однако на некоторых бенчмарках результаты Go заметно хуже, включая несколько из набора: 1722 | 1723 | 1724 | Самые медленные тесты зависят от библиотек, для которых в Go нет равноценных по скорости реализаций. 1725 | Например, [pidigits.go](https://go.googlesource.com/exp/+/master/shootout/pidigits.go) использует пакет для вычислений 1726 | с произвольной точностью, а C-версии применяют [GMP](https://gmplib.org/), написанную на оптимизированном ассемблере. 1727 | 1728 | Бенчмарки, завязанные на регулярные выражения 1729 | (например, [regex-dna.go](https://go.googlesource.com/exp/+/master/shootout/regex-dna.go)) фактически сравнивают 1730 | Go-пакет `regexp` с зрелыми и сильно оптимизированными библиотеками вроде PCRE. 1731 | 1732 | В «играх с бенчмарками» обычно выигрывают за счёт тонкой оптимизации, 1733 | а версии большинства тестов на Go ещё требуют доработки. 1734 | Если сравнивать действительно сопоставимые программы на C и Go 1735 | (пример: [reverse-complement.go](https://go.googlesource.com/exp/+/master/shootout/reverse-complement.go)), 1736 | то разрыв в производительности будет значительно меньше, чем показывает этот набор тестов. 1737 | 1738 | Тем не менее, есть куда расти: 1739 | 1740 | - компиляторы хорошие, но могут быть лучше 1741 | - многие библиотеки требуют серьёзной оптимизации 1742 | - сборщик мусора пока недостаточно быстрый 1743 | 1744 | (Причём даже если бы он был мгновенным, избежание лишнего мусора всё равно давало бы большой выигрыш.) 1745 | 1746 | В целом Go часто может быть весьма конкурентоспособным. 1747 | С развитием языка и инструментов производительность многих программ значительно улучшилась. 1748 | 1749 | См. [блог-пост о профилировании Go-программ](https://go.dev/blog/profiling-go-programs). 1750 | Он довольно старый, но до сих пор содержит полезную информацию. 1751 | 1752 | ## Отличия от C 1753 | 1754 | ### Почему синтаксис так отличается от C? 1755 | 1756 | Кроме синтаксиса объявлений, различия незначительны и продиктованы двумя целями: 1757 | 1758 | 1. Синтаксис должен быть «лёгким» — без множества обязательных ключевых слов, избыточности и сложных конструкций 1759 | 2. Язык должен быть простым для анализа и парсинга **без таблицы символов** 1760 | Это значительно упрощает создание инструментов вроде отладчиков, анализаторов зависимостей, генераторов документации, плагинов для IDE и т. д. 1761 | C и его потомки известны как крайне сложные в этом плане. 1762 | 1763 | ### Почему объявления «наоборот»? 1764 | 1765 | Они выглядят «наоборот» только для тех, кто привык к C. 1766 | 1767 | В C идея в том, что переменная объявляется так, будто это выражение, описывающее её тип. 1768 | Это изящно, но грамматики типов и выражений плохо сочетаются, что делает объявления запутанными 1769 | (особенно с указателями на функции). 1770 | 1771 | Go в основном разделяет синтаксис типов и выражений, и это упрощает ситуацию 1772 | (префикс `*` для указателей — редкое исключение). 1773 | 1774 | Пример на C: 1775 | 1776 | ```c 1777 | int* a, b; 1778 | ``` 1779 | 1780 | Здесь `a` — указатель, а `b` — нет. 1781 | 1782 | Пример на Go: 1783 | 1784 | ```go 1785 | var a, b *int 1786 | ``` 1787 | 1788 | Здесь и `a`, и `b` — указатели. Это понятнее и логичнее. 1789 | 1790 | Кроме того, краткая форма объявления `:=` диктует, что полное объявление переменной должно следовать той же логике: 1791 | 1792 | ```go 1793 | var a uint64 = 1 1794 | ``` 1795 | 1796 | эквивалентно 1797 | 1798 | ```go 1799 | a := uint64(1) 1800 | ``` 1801 | 1802 | Также парсинг упрощается за счёт отдельной грамматики для типов, которая не совпадает с грамматикой выражений; 1803 | ключевые слова вроде `func` и `chan` сохраняют ясность. 1804 | 1805 | См. статью: 1806 | [Go’s Declaration Syntax](https://go.dev/doc/articles/gos_declaration_syntax.html) 1807 | 1808 | ### Почему в Go нет арифметики указателей? 1809 | 1810 | Безопасность. Без арифметики указателей можно создать язык, в котором невозможно получить неправильный адрес, 1811 | который случайно будет работать «корректно». Современные компиляторы и процессоры умеют оптимизировать циклы с 1812 | индексами массивов так же эффективно, как и циклы с арифметикой указателей. Кроме того, отсутствие арифметики 1813 | указателей упрощает реализацию сборщика мусора. 1814 | 1815 | ### Почему `++` и `--` — это инструкции, а не выражения? И почему только постфиксная форма? 1816 | 1817 | Без арифметики указателей ценность префиксной и постфиксной формы операторов инкремента снижается. 1818 | Вынеся их из иерархии выражений, мы упростили синтаксис языка и устранили двусмысленности с порядком вычислений 1819 | (например, в случаях `f(i++)` или `p[i] = q[++i]`). Упрощение здесь оказалось существенным. 1820 | 1821 | Что касается постфиксной формы, то теоретически можно было выбрать любую, но постфикс традиционно используется дольше. 1822 | Интересно, что настойчивость в использовании префикса появилась с STL — библиотекой для языка, в названии которого 1823 | сам по себе используется постфиксный инкремент. 1824 | 1825 | ### Почему используются фигурные скобки, но нет точек с запятой? И почему нельзя ставить открывающую скобку на новой строке? 1826 | 1827 | Go использует фигурные скобки для группировки операторов, что привычно программистам из мира C-подобных языков. 1828 | 1829 | Точки с запятой нужны парсерам, но не людям. Поэтому Go максимально избавился от них, позаимствовав приём из BCPL: 1830 | в формальной грамматике точки с запятой присутствуют, но лексер вставляет их автоматически в конце каждой строки, 1831 | которая может завершать оператор. Это отлично работает на практике, но накладывает ограничение на стиль скобок 1832 | — открывающая фигурная скобка не может стоять на отдельной строке. 1833 | 1834 | Иногда предлагают разрешить лексеру делать lookahead и позволить скобку на следующей строке. Мы с этим не согласны. 1835 | Так как код Go форматируется автоматически с помощью [`gofmt`](https://pkg.go.dev/cmd/gofmt), стиль должен быть единым. 1836 | Возможно, он отличается от привычного в C или Java, но Go — другой язык, и стиль `gofmt` не хуже других. 1837 | Гораздо важнее то, что единый и машинно навязанный стиль для всех программ на Go приносит огромные преимущества. 1838 | 1839 | Кроме того, выбранный стиль позволяет легко использовать стандартный синтаксис построчно 1840 | в интерактивных реализациях Go, без особых правил. 1841 | 1842 | ### Зачем нужна сборка мусора? Разве она не слишком дорогая? 1843 | 1844 | Одна из самых трудоёмких частей системного программирования — управление временем жизни объектов в памяти. 1845 | В C это делается вручную, что часто становится источником сложных ошибок. Даже в C++ и Rust, где есть вспомогательные 1846 | механизмы, они сильно влияют на проектирование программ и создают дополнительную нагрузку на программиста. 1847 | 1848 | Мы посчитали критически важным убрать эти накладные расходы, и развитие технологий сборки мусора в последние годы 1849 | показало, что это можно сделать достаточно дёшево и с низкими задержками, чтобы быть жизнеспособным 1850 | решением для сетевых систем. 1851 | 1852 | Многие трудности параллельного программирования связаны именно с проблемой управления временем жизни объектов: 1853 | когда они передаются между потоками, становится сложно гарантировать корректное освобождение. Автоматическая сборка 1854 | мусора сильно упрощает написание конкурентного кода. Конечно, реализация сборки мусора в многопоточной среде — сама по 1855 | себе непростая задача, но решить её один раз в языке лучше, чем решать её в каждой программе отдельно. 1856 | 1857 | Кроме того, сборка мусора делает интерфейсы проще — им не нужно описывать, как управлять памятью. 1858 | 1859 | Это не значит, что работа в других языках (например, Rust) по новым способам управления ресурсами не имеет смысла. 1860 | Мы поддерживаем эти исследования и с интересом следим за их развитием. Но Go выбрал более традиционный путь — 1861 | управление временем жизни исключительно через сборку мусора. 1862 | 1863 | Текущая реализация — это mark-and-sweep. На многопроцессорных машинах сборщик работает параллельно с программой 1864 | на отдельном ядре. В последние годы он был сильно улучшен: задержки сократились до субмиллисекунд даже для больших куч, 1865 | что устранило один из главных доводов против GC в серверных приложениях. Работа над улучшением алгоритмов, снижением 1866 | накладных расходов и задержек продолжается. Подробнее об этом рассказывает 1867 | [доклад ISMM 2018](https://go.dev/blog/ismmkeynote) Рика Хадсона из команды Go. 1868 | 1869 | Важно отметить, что Go даёт программисту больше контроля над расположением данных в памяти и выделением объектов, 1870 | чем большинство языков со сборкой мусора. Аккуратное использование языка позволяет существенно снизить нагрузку на GC. 1871 | Подробнее см. статью [Profiling Go programs](https://go.dev/blog/profiling-go-programs). 1872 | 1873 | --- 1874 | 1875 | (с) 2025, Андрей Крисанов (автор перевода) 1876 | --------------------------------------------------------------------------------