├── .gitignore ├── LICENSE ├── Original └── effective_go.html ├── README.md └── _config.yml /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.un~ 3 | *.md~ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Konstantin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Effective Go (RU) (Эффективный Go) 2 | 3 | ------ 4 | 5 | > ⚠️ **Внимание!** 6 | > Данный перевод не завершен и устарел, [смотри новый здесь в .md или .pdf формате](https://github.com/0x0FACED/effective-go-ru) 7 | 8 | 9 | Оригинал смотри: https://golang.org/doc/effective_go.html 10 | `go version go1.7.4` 11 | 12 | ------ 13 | 14 | **Список дополнительных материалов:** 15 | * [Руководство сотрудничества](https://github.com/Konstantin8105/Contribution_Guide_RU) 16 | * [Эффективный Go (Свежий перевод)](https://github.com/0x0FACED/effective-go-ru) 17 | * [Эффективный Go](https://github.com/Konstantin8105/Effective_Go_RU) 18 | 19 | ------ 20 | # Оглавление 21 | 22 | - [Effective Go (RU) (Эффективный Go)](#effective-go-ru-эффективный-go) 23 | - [Оглавление](#оглавление) 24 | - [Введение](#введение) 25 | - [Примеры](#примеры) 26 | - [Форматирование](#форматирование) 27 | - [Абзац](#абзац) 28 | - [Длина строки](#длина-строки) 29 | - [Круглые скобки](#круглые-скобки) 30 | - [Комментарии](#комментарии) 31 | - [Именование](#именование) 32 | - [Именование пакетов](#именование-пакетов) 33 | - [Геттеры](#геттеры) 34 | - [Имена интерфейсов](#имена-интерфейсов) 35 | - [MixedCaps](#mixedcaps) 36 | - [Точка с запятой](#точка-с-запятой) 37 | - [Управляющие структуры](#управляющие-структуры) 38 | - [If](#if) 39 | - [Переопределение и переприсваивание](#переопределение-и-переприсваивание) 40 | - [Оператор For](#оператор-for) 41 | - [Switch(переключатель)](#switchпереключатель) 42 | - [Переключатель типов (Типизированный переключатель, Type switch)](#переключатель-типов-типизированный-переключатель-type-switch) 43 | - [Функции и методы(Functions, методы)](#функции-и-методыfunctions-методы) 44 | - [Множественное возвращение результатов](#множественное-возвращение-результатов) 45 | - [Именование параметров результата](#именование-параметров-результата) 46 | - [Отсроченный вызов (Defer)](#отсроченный-вызов-defer) 47 | - [Данные](#данные) 48 | - [Созданные с помощью `new`](#созданные-с-помощью-new) 49 | - [Конструкторы и составные литералы](#конструкторы-и-составные-литералы) 50 | - [Создание с помощью `make`](#создание-с-помощью-make) 51 | - [Массивы](#массивы) 52 | - [Срезы(Slices, слайсы)](#срезыslices-слайсы) 53 | - [Двухмерные срезы](#двухмерные-срезы) 54 | - [Карты(Maps)](#картыmaps) 55 | - [Печать(Printing)](#печатьprinting) 56 | - [Присоединение(Append)](#присоединениеappend) 57 | - [Инициализация(Initialization)](#инициализацияinitialization) 58 | - [Константы(Constants)](#константыconstants) 59 | - [Переменные(Variables)](#переменныеvariables) 60 | - [Функция init](#функция-init) 61 | - [Методы(Methods)](#методыmethods) 62 | - [Указатели или Значения](#указатели-или-значения) 63 | - [Интерфейсы и другие типы](#интерфейсы-и-другие-типы) 64 | - [Интерфейсы](#интерфейсы) 65 | - [Преобразование (Conversions)](#преобразование-conversions) 66 | - [Конвертация интерфейсов и привязка типов](#конвертация-интерфейсов-и-привязка-типов) 67 | - [Общее(Generality)](#общееgenerality) 68 | - [Интерфейсы и методы(функции)](#интерфейсы-и-методыфункции) 69 | - [Пустой идентификатор (The blank identifier **\_**)](#пустой-идентификатор-the-blank-identifier-_) 70 | - [Пустой идентификатор в множественном присваивании (**\_**)](#пустой-идентификатор-в-множественном-присваивании-_) 71 | - [Неиспользуемое импортирование и значения](#неиспользуемое-импортирование-и-значения) 72 | - [Импортирование для побочного эффекта (Import for side effect)](#импортирование-для-побочного-эффекта-import-for-side-effect) 73 | - [Проверка интерфейса (Interface checks)](#проверка-интерфейса-interface-checks) 74 | - [Вложение (Embedding)](#вложение-embedding) 75 | - [Согласованность, параллельная обработка, параллельное выполнение (Concurrency)](#согласованность-параллельная-обработка-параллельное-выполнение--concurrency) 76 | - [Распределение памяти по сообщениям (Share by communicating)](#распределение-памяти-по-сообщениям-share-by-communicating) 77 | - [Го-рутины (Goroutines)](#го-рутины-goroutines) 78 | - [Каналы (Channels)](#каналы-channels) 79 | - [Канал каналов (Channels of channels)](#канал-каналов-channels-of-channels) 80 | - [Параллелизм (Parallelization)](#параллелизм-parallelization) 81 | - [Текущий буфер (A leaky buffer)](#текущий-буфер-a-leaky-buffer) 82 | - [Ошибки (Errors)](#ошибки-errors) 83 | - [Паника (Panic)](#паника-panic) 84 | - [Восстановление (Recover)](#восстановление-recover) 85 | - [Веб-сервер](#веб-сервер) 86 | 87 | 88 | -------------- 89 | 90 | ## Введение 91 | 92 | [^](#Оглавление) 93 | 94 | Go - это новый язык программирования. Хотя, он заимствует идеи из существующих языков, он обладает необычными свойствами, которые позволяют создавать эффективные программы, язык Go отличается по своему характеру от программ, написанных на родственных языках. Прямолинейный перевод C++ или Java программ в Go вряд ли даст удовлетворительный результат, т.к. Java программы написаны на Java, не на Go. С другой стороны, думая о проблеме с точки зрения Go можно добиться успеха, но это уже другая программа. Другими словами, для хорошего написания кода на языке Go, важно понимать его особенности и идиомы. 95 | Также важно знать установленные соглашения для программирования на Go, такие как именование, форматирование, разработка программ и так далее, так чтобы программы написанные Вами были простыми для понимания другими программистами Go. 96 | 97 | 98 | 99 | Этот документ даёт примеры для написания чистого, идеоматичного кода на Go. 100 | Он дополняет [спецификацию языка](https://golang.org/ref/spec), [Тур по Go](https://tour.golang.org/), 101 | и [Как писать на Go](https://golang.org/doc/code.html), каждую из которых необходимо прочитать в первую очередь. 102 | 103 | [^](#Оглавление) 104 | 105 | ### Примеры 106 | 107 | [^](#Оглавление) 108 | 109 | [Go пакеты исходных кодов](https://golang.org/src/) предназначены не только в качестве основных библиотек, но и в качестве примеров использования языка. 110 | Кроме того, многие пакеты имеют работающие, автономные исполняемые примеры и Вы можете запустить напрямую с помощью страницы [golang.org](https://golang.org), такие как [этот](https://golang.org/pkg/strings/#example_Map) (если необходимо, нажмите на слово "Примеры" чтобы открыть их). 111 | Если у Вас есть вопрос о том как решить какую-либо проблему или как что-то реализовать, то документация, исходные коды и примеры в библиотеке могут дать ответ, идею или объяснение. 112 | 113 | [^](#Оглавление) 114 | 115 | ## Форматирование 116 | 117 | [^](#Оглавление) 118 | 119 | Форматирование является наиболее спорным, но не сильно важным вопросом. 120 | Люди могут привыкнуть к различным стилям форматирования, но было бы лучше, если бы этого не приходилось делать и меньше времени придавалось этой теме, если бы все использовали одинаковый стиль. 121 | Проблема данной утопии в том, как это сделать без длинного руководства по стилю. 122 | 123 | В Go мы используем нетипичный подход и передаем машине заботу о форматировании. 124 | Программа `gofmt` (также доступна, как `go fmt`, которая производит действия на уровне пакета, а не на уровне файлов) читает код на Go и выпускает исходный код со стандартным стилем отступов и вертикальным выравниванием, сохраняет, и при необходимости, переформатирует комментарии. 125 | Если Вы хотите знать, как по-новому структурировать код, запустите `gofmt`; если структура неверна, `gofmt` поправит Вашу программу (или файл сообщит об ошибке `gofmt`), не работайте в обход форматирования программой `gofmt`. 126 | 127 | К примеру, нет необходимости тратить время на выравнивание комментариев для полей структур, т.к. `gofmt` сделает это за Вас. 128 | Для данного фрагмента кода 129 | 130 | ```golang 131 | type T struct { 132 | name string // name of the object 133 | value int // its value 134 | } 135 | ``` 136 | 137 | `gofmt` произведет выравнивание по колонкам: 138 | 139 | ```golang 140 | type T struct { 141 | name string // name of the object 142 | value int // its value 143 | } 144 | ``` 145 | 146 | Все стандартные пакеты Go отформатированы с помощью `gofmt`. 147 | 148 | Очень коротко о некоторых деталях форматирования: 149 | 150 | #### Абзац 151 | Мы используем табуляцию для абзацев и `gofmt` делает это по умолчанию. Используйте пробелы только при острой необходимости. 152 | #### Длина строки 153 | Go не имеет предела длины строки. Не беспокойтесь о длинных строках. Если строка кажется слишком длинной, прервите ее и добавьте дополнительный отступ (символ табуляции) на новой строке. 154 | #### Круглые скобки 155 | Go нуждается в меньшем количестве круглых скобок, чем C и Java: структуры ветвления, цикла ( `if` , `for` , `switch`) не имеют круглых скобок в своём синтаксисе. Также, иерархия операторов стала проще и короче. К примеру, выражение 156 | 157 | ``` 158 | x<<8 + y<<16 159 | ``` 160 | не нуждается в добавлении пробелов, в отличии от других языков. 161 | 162 | [^](#Оглавление) 163 | 164 | 165 | ## Комментарии 166 | 167 | [^](#Оглавление) 168 | 169 | Go использует C-стиль `/* */` для блока комментариев и C++-стиль `//` для однострочных комментариев. 170 | Как правило, используются однострочные комментарии. Блок комментариев, в основном, используется при комментировании пакетов, 171 | но также для выразительности или отключения большого участка кода. 172 | 173 | Программа и веб-сервер - `godoc` обрабатывает Go исходники пакета для формирования документации. 174 | Комментарии, расположенные сразу над объявлением (без дополнительных пустых строк), извлекаются вместе с объявлением для пояснения данного элемента. 175 | Характер и стиль комментариев напрямую влияет на качество документации производимой `godoc`. 176 | 177 | Каждый пакет должен иметь *комментарий пакета* - это блок комментариев предшествующий объявлению пакета. 178 | Для пакетов состоящих из нескольких файлов, комментарий пакета может быть расположен в любом из файлов, но только в одном из них. 179 | Комментарий пакета должен представлять информацию о пакете в целом. 180 | Он будет отображен вначале страницы `godoc` и должен представлять из себя детальную информацию, которой можно пользоваться. 181 | 182 | ```golang 183 | /* 184 | Package regexp implements a simple library for regular expressions. 185 | 186 | The syntax of the regular expressions accepted is: 187 | 188 | regexp: 189 | concatenation { '|' concatenation } 190 | concatenation: 191 | { closure } 192 | closure: 193 | term [ '*' | '+' | '?' ] 194 | term: 195 | '^' 196 | '$' 197 | '.' 198 | character 199 | '[' [ '^' ] character-ranges ']' 200 | '(' regexp ')' 201 | */ 202 | package regexp 203 | ``` 204 | 205 | Если пакет простой, то комментарий может быть кратким. 206 | 207 | ```golang 208 | // Package path implements utility routines for 209 | // manipulating slash-separated filename paths. 210 | ``` 211 | 212 | Дополнительное форматирование, к примеру баннер из * (звездочек), не требуется. 213 | Шрифт для сформированного результата не обязательно будет моноширинный, поэтому не полагайтесь на пробелы при выравнивании, `godoc`, также как `gofmt`, позаботятся об этом. 214 | Комментарии интерпретируются как простой текст, поэтому HTML и другие аннотации такие как `_эта_` воспроизводятся *дословно* и поэтому не должны использоваться. Единственное исключение, 215 | которое делает `godoc`, это выделение моноширинным шрифтом участков кода с отступами. 216 | Хорошим примером такого исключения является комментарий к пакету [`fmt`](https://golang.org/pkg/fmt/). 217 | 218 | В зависимости от контекста, `godoc` не может переформатировать комментарии, поэтому убедитесь, что они выглядят хорошо: используйте правильное правописание, знаки препинания, структуру предложения и т.д. 219 | 220 | Любые комментарии внутри пакета, предшествующие объявлению, используются как описание этого объявления. 221 | Каждый экспортируемый объект, название которого начинается с большой буквы, должен иметь комментарий. 222 | 223 | Лучше всего использовать комментарии в виде полных предложений. Это позволяет производить их автоматическую обработку. 224 | Первое предложение должно быть ключевым и начинаться с имени объявления. 225 | 226 | ```golang 227 | // Compile parses a regular expression and returns, if successful, 228 | // a Regexp that can be used to match against text. 229 | func Compile(str string) (*Regexp, error) { 230 | ``` 231 | 232 | Если комментарий начинается с имени, то `godoc` может с использоваться совместно с `grep`. 233 | Представьте, что Вы не можете вспомнить имя "Compile", но Вы ищите *the parsing function* для регулярных выражений и тогда Вы можете выполнить команду: 234 | 235 | ```command 236 | $ godoc regexp | grep -i parse 237 | ``` 238 | 239 | Если все комментарии в пакете начинаются с "This function...", `grep` не сможет помочь с поиском имени. 240 | Если же комментарии начинаются с имени, Вы можете увидеть что-то вроде следующего результата, который напомнит Вам о том, что Вы искали. 241 | 242 | ```command 243 | $ godoc regexp | grep parse 244 | Compile parses a regular expression and returns, if successful, a Regexp 245 | parsed. It simplifies safe initialization of global variables holding 246 | cannot be parsed. It simplifies safe initialization of global variables 247 | $ 248 | ``` 249 | 250 | Синтаксис Go допускает групповое объявление. Для каждой группы констант или переменных может быть представлен один общий комментарий. Однако такое объявление выглядит небрежно. 251 | 252 | ```golang 253 | // Error codes returned by failures to parse an expression. 254 | var ( 255 | ErrInternal = errors.New("regexp: internal error") 256 | ErrUnmatchedLpar = errors.New("regexp: unmatched '('") 257 | ErrUnmatchedRpar = errors.New("regexp: unmatched ')'") 258 | ... 259 | ) 260 | ``` 261 | 262 | Группировка также может показать взаимосвязи между элементами, к примеру, группа переменных защищенных mutex: 263 | 264 | ```golang 265 | var ( 266 | countLock sync.Mutex 267 | inputCount uint32 268 | outputCount uint32 269 | errorCount uint32 270 | ) 271 | ``` 272 | 273 | [^](#Оглавление) 274 | 275 | ## Именование 276 | 277 | [^](#Оглавление) 278 | 279 | Именование очень важно в Go, как и в других языках. 280 | Они имеют семантический эффект: **Видимость имени за пределами пакета, определяется по первой букве имени, которая, если является заглавной, то имя будет видно вне это пакета**. 281 | Именно поэтому стоит уделить время обсуждению соглашения об именовании в программах Go. 282 | 283 | ### Именование пакетов 284 | 285 | [^](#Оглавление) 286 | 287 | Когда пакет импортируется, имя пакета используется для доступа к его содержимому. 288 | После того, как пакет импортирован, 289 | 290 | ```golang 291 | import "bytes" 292 | ``` 293 | 294 | можно использовать `bytes.Buffer`. Это полезно, если все, кто использует пакет, могут использовать одно и то же имя, для обращения к его содержимому, подразумевается, что имя пакета должно быть коротким, четким и запоминающимся. В соответствии с соглашением,имена пакетов состоят из одного слова в нижнем регистре; нет необходимости в использовании подчеркиваний или СмешанногоРегистра. При выборе длинного имени пакета, всем, кто будет его использовать, придётся писать это имя. Но не беспокойтесь об уникальности имени. 295 | Имя пакета только по умолчанию используется при импорте; оно не должно быть глобально уникальным, и в редких случаях, при импорте может быть указано другое имя. В любом случае, 296 | путаница встречается редко, так как имя файла в импорте определяет, какой именно пакет используется. 297 | 298 | Согласно другому соглашению, имя пакета является базовым именем его исходного каталога; пакет `src/encoding/base64` импортируется как `"encoding/base64"` и имеет название `base64`, а не `encoding_base64` и не `encodingBase64`. 299 | 300 | Импортирующий пакет будет использовать имя пакета для обозначения его содержимого, поэтому при экспорте может учитываться этот факт, чтобы избежать повторения. 301 | (Не используйте `import .`, это, конечно, может упростить запуск тестов вне пакета, но в других случаях использоваться не должно). Например, тип *reader* для буферного чтения описанный в пакете `bufio` называется `Reader`, а не `BufReader`, т.к пользователи его видят как `bufio.Reader`, имя которого кратко и понятно. 302 | 303 | 304 | 305 | Более того, т.к. импортируемые объекты адресуются по имени пакета, следовательно `bufio.Reader` не будет конфликтовать с `io.Reader`. 306 | Аналогично, функция для создания нового экземпляра объекта `ring.Ring`, которая объявлена как *конструктор* в Go, может называться `NewRing`, но т.к. `Ring` - это экспортируемый тип из пакета `ring`, функция-конструктор может называться просто `New`, которую, можно будет вызвать как `ring.New`. Используйте структуру пакетов при 307 | выборе имен. 308 | 309 | Другой короткий пример функция `once.Do`; `once.Do(setup)` читается хорошо, и при этом 310 | лучше не станет, если ее переименовать в `once.DoOrWaitUntilDone(setup)`. 311 | Длинные имена не делают названия более читабельными. В то время как комментарии 312 | могут быть более ценным, чем длинные имена. 313 | 314 | ## Геттеры 315 | 316 | [^](#Оглавление) 317 | 318 | Go не предоставляет автоматическую поддержку геттеров и сеттеров. 319 | Но не будет ошибкой создание геттеров и сеттеров самостоятельно, и если это необходимо, то делайте так, но идиоматически нет необходимости добавлять `Get` в имя геттера. 320 | Если у Вас есть поле с именем `owner` (с маленькой буквы, неэкспортируемое), то геттер может называться `Owner` (с большой буквы, экспортируемый), а не `GetOwner`. 321 | Использование имен, начинающихся с заглавной буквы, позволяет отделить экспортируемые методы от неэкспортируемых полей. Cеттер, при необходимости, может быть назван `SetOwner`. 322 | Оба примера в следующем коде: 323 | 324 | ```golang 325 | owner := obj.Owner() 326 | if owner != user { 327 | obj.SetOwner(user) 328 | } 329 | ``` 330 | 331 | ### Имена интерфейсов 332 | 333 | [^](#Оглавление) 334 | 335 | По соглашению, интерфейсы с одним методом должны называться как метод с суффиксом `-er` или подобно этому, для образования существительного: `Reader`, `Writer`, `Formatter`, `CloseNotifier` и т.д. 336 | 337 | Существует целый ряд имен, которыe соблюдают это соглашение и содержат подобные методы. `Read` , `Write` , `Close`, `Flush`, `String` и т.д., имеют канонические подписи и значения. Чтобы избежать путаницы, не давайте методу ни одного из этих имен, если оно не имеет ту же сигнатуру и значение. С другой стороны, если ваш тип реализует метод с тем же значением, как и метод хорошо известного типа, то дайте ему то же имя и значение; назовите Ваш метод конвертации в строку `String` , а не `ToString`. 338 | 339 | ### MixedCaps 340 | 341 | [^](#Оглавление) 342 | 343 | В заключении, Go соглашение использует `MixedCaps` или `mixedCaps` , а не подчеркивание для имен из нескольких слов. 344 | 345 | 346 | ### Точка с запятой 347 | 348 | [^](#Оглавление) 349 | 350 | Как и в С, грамматика Go формально использует точку с запятой для разделения 351 | операций-выражений (инструкций), но в отличии от C, точка с запятой не представлена 352 | в исходном коде. Вместо этого, лексер использует простое правило добавления 353 | точки с запятой автоматически, при сканировани. Таким образом текст на входе 354 | по большей части освобожден от них. 355 | 356 | Правило такое. Если последний токен(лексема) перед символом новой строки - идентификатор (который включает такие слова, как `int` и `float64`), базовый литерал, такой как число или строковая константа, или один из нижеперечисленных токенов 357 | 358 | ```golang 359 | break continue fallthrough return ++ -- ) } 360 | ``` 361 | 362 | то, лексер всегда добавляет точку с запятой после него. Вкратце, это может звучать так: "Если новая строка начинается после токена, который может закрывать операцию-выражение, то добавить точку с запятой". 363 | 364 | Точка с запятой также может быть опущена сразу перед закрывающей скобкой, таким 365 | образом для операции-выражения такой как: 366 | 367 | ```golang 368 | go func() { for { dst <- <-src } }() 369 | ``` 370 | 371 | точка с запятой не требуется. 372 | 373 | Как следствие из правила, вы не можете перенести открывающую скобку управляющих 374 | структур (`if`, `for`, `switch` или `select`) на новую строку. Если перенесете, 375 | точка с запятой будет вставлена перед скобкой, которая может стать причиной 376 | нежелательных эффектов. Пишите так, 377 | 378 | ```golang 379 | if i < f() { 380 | g() 381 | } 382 | ``` 383 | 384 | но не так 385 | 386 | ```golang 387 | if i < f() // ошибка! 388 | { // ошибка! 389 | g() 390 | } 391 | ``` 392 | 393 | [^](#Оглавление) 394 | 395 | 396 | ## Управляющие структуры 397 | 398 | [^](#Оглавление) 399 | 400 | Управляющие структуры в Go аналогичны тем же структурам в C, но имеют ряд важных отличий. Во-первых нет циклов `do` и `while`, есть лишь обобщенный `for`. Во-вторых, `switch` более гибкий. В-третьих `if` и `switch` имеют опциональную инициализацию переменных, как и в `for`. В-четвертых, `break` и `continue` опционально принимают метку, к которой необходимо перейти. В-пятых, есть новые операторы, такие как типизированный `switch` и многоканальный `select`. Синтаксис также немного отличается: отсутствуют круглые скобки в условии, и тело структуры всегда должно быть ограничено фигурными скобками. 401 | 402 | [^](#Оглавление) 403 | 404 | ### If 405 | 406 | [^](#Оглавление) 407 | 408 | В Go простой `if` выглядит так: 409 | 410 | ```golang 411 | if x > 0 { 412 | return y 413 | } 414 | ``` 415 | 416 | Обязательные фигурные скобки упрощают написание простых условий `if` на 417 | несколько строк. Это хороший стиль в любом случае, особенно когда тело содержит управляющие операторы, такие как `return` или `break`. 418 | 419 | Поскольку `if` и `switch` допускают инициализацию переменных, то часто можно 420 | видеть подобную запись: 421 | 422 | ```golang 423 | if err := file.Chmod(0664); err != nil { 424 | log.Print(err) 425 | return err 426 | } 427 | ``` 428 | 429 | В библиотеках Go, вы найдёте подобную запись, если `if` не переходит в следующий блок, т.е. в теле используется `break`, `continue`, `goto` или `return`, а необязательный `else` опускается. 430 | 431 | ```golang 432 | f, err := os.Open(name) 433 | if err != nil { 434 | return err 435 | } 436 | codeUsing(f) 437 | ``` 438 | 439 | В данном примере представлена общая схема, где код защищен от серии ошибок. Код читается хорошо, если выполняется без ошибок, обходя случаи их возникновения. Так как ошибки приводят к завершению выполнения блока с помощью `return`, то блок `else` не требуется. 440 | 441 | ```golang 442 | f, err := os.Open(name) 443 | if err != nil { 444 | return err 445 | } 446 | d, err := f.Stat() 447 | if err != nil { 448 | f.Close() 449 | return err 450 | } 451 | codeUsing(f, d) 452 | ``` 453 | 454 | [^](#Оглавление) 455 | 456 | ### Переопределение и переприсваивание 457 | 458 | [^](#Оглавление) 459 | 460 | Последний пример предыдущего раздела демонстрирует использование краткой формы объявления переменных `:=`. Вызов `os.Open` объявляет сразу две переменных `f` и `err` 461 | 462 | ```golang 463 | f, err := os.Open(name) 464 | ``` 465 | 466 | Несколькими строками ниже вызывается `f.Stat`, 467 | 468 | ```golang 469 | d, err := f.Stat() 470 | ``` 471 | 472 | который выглядит как объявления двух переменных `d` и `err`. Хотя `err` присутствует в обоих объявлениях. Это дублирование вполне законно: `err` объявляется в первом случае, и лишь переприсваивается во втором. Это означает, что `f.Stat` использует уже существующую переменную `err`, определенную выше, и просто присваивает ей новое значение. 473 | 474 | В объявлении `:=` переменная `v` может присутствовать, даже если она уже объявлена, при условии: 475 | 476 | * если объявление происходит в той же самой области видимости, что и существующая переменная `v` (если `v` уже объявлена за пределами видимости, то объявление создаст новую переменную §) 477 | * соответствующее значение, при инициализации, может быть присвоено `v` 478 | * существует хотя бы одна новая переменная в объявлении, которая будет создана заново 479 | 480 | Это необычное свойство - чистая практичность, которая служит для упрощения 481 | использования одной переменной `err`, к примеру, в длинных цепочках `if-else`. 482 | Вы увидите, это используется часто. 483 | 484 | § Нет ничего плохого в том, что в Go область видимости параметров и возвращаемых значений функции - есть само тело функции, хотя они лексически находятся за скобками, ограничивающими тело функции. 485 | 486 | [^](#Оглавление) 487 | 488 | ### Оператор For 489 | 490 | [^](#Оглавление) 491 | 492 | В Go цикл `for` очень похож, но не такой же как в C. Он унифицирует `for` и `while`, при этом отсутствует `do-while` цикл. Существует 3 различных формы, и только в одной из них используется точка с запятой. 493 | 494 | ```golang 495 | // C-подобный for 496 | for init; condition; post { } 497 | 498 | // C-подобный while 499 | for condition { } 500 | 501 | // C-подобный for(;;) 502 | for { } 503 | ``` 504 | 505 | Краткая запись позволяет легко объявить начальные условия прямо в цикле: 506 | 507 | ```golang 508 | sum := 0 509 | for i := 0; i < 10; i++ { 510 | sum += i 511 | } 512 | ``` 513 | 514 | Если Вы итерируетесь по массиву, срезу, строке или map'у, или читаете из канала, то для управления можно использовать `range`. 515 | 516 | ```golang 517 | for key, value := range oldMap { 518 | newMap[key] = value 519 | } 520 | ``` 521 | 522 | 523 | Если необходимо использовать только первый элемент *диапазона* (ключ или индекс), отбросьте второй: 524 | 525 | ```golang 526 | for key := range m { 527 | if key.expired() { 528 | delete(m, key) 529 | } 530 | } 531 | ``` 532 | 533 | Если вам необходим только второй элемент (значение), то используйте *пустой идентификатор* (**_**) в качестве первого элемента: 534 | 535 | ```golang 536 | sum := 0 537 | for _ , value := range array { 538 | sum += value 539 | } 540 | ``` 541 | 542 | Пустой идентификатор используется в разных случаях и будет описан позже. 543 | 544 | Для строк, оператор `range` выполняет ещё больше работы, к примеру разделяет строку по символам Unicode в соответствии с UTF-8. При ошибочном использование кодировки, побайтово заменяет рунами(*rune*) U+FFFD. (`rune` (и одноименный встроенный тип) в терминологии Go используется для работы с символами Unicode. Смотрите детальную информацию в [Спецификации языка](https://golang.org/ref/spec#Rune_literals)). 545 | 546 | Данный цикл: 547 | 548 | ```golang 549 | for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding 550 | fmt.Printf("character %#U starts at byte position %d\n", char, pos) 551 | } 552 | ``` 553 | 554 | Выводит: 555 | 556 | ```command 557 | character U+65E5 '日' starts at byte position 0 558 | character U+672C '本' starts at byte position 3 559 | character U+FFFD '�' starts at byte position 6 560 | character U+8A9E '語' starts at byte position 7 561 | ``` 562 | 563 | И в заключении, в языке Go нет оператора `запятая`, а `++` и `--` являются инструкциями, но не выражениями. Таким образом, если Вам необходимо использовать несколько переменных в цикле `for`, то Вы можете использовать параллельное определение переменных (без использования `++` и `--`). 564 | 565 | ```golang 566 | // Reverse a 567 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 568 | a[i], a[j] = a[j], a[i] 569 | } 570 | ``` 571 | 572 | [^](#Оглавление) 573 | 574 | ### Switch(переключатель) 575 | 576 | [^](#Оглавление) 577 | 578 | В языке Go `switch` более обобщён, нежели в C. Выражения не обязательно должны 579 | быть константами или даже целыми числами, условия проверяются сверху-вниз до нахождения соответствия, и если `switch` не имеет выражений, то переходит в `true`. Следовательно, идиоматически возможно записывать `if-else-if-else` цепочку как `switch`. 580 | 581 | ```golang 582 | func unhex(c byte) byte { 583 | switch { 584 | case '0' <= c && c <= '9': 585 | return c - '0' 586 | case 'a' <= c && c <= 'f': 587 | return c - 'a' + 10 588 | case 'A' <= c && c <= 'F': 589 | return c - 'A' + 10 590 | } 591 | return 0 592 | } 593 | ``` 594 | 595 | Автоматический пропуск условий отсутствует, но, при этом, условия могут быть записаны через запятую: 596 | 597 | ```golang 598 | func shouldEscape(c byte) bool { 599 | switch c { 600 | case ' ', '?', '&', '=', '#', '+', '%': 601 | return true 602 | } 603 | return false 604 | } 605 | ``` 606 | Несмотря на то, что они не столь распространены в Go, как в некоторых других C-подобных языках, `break` может быть использован для досрочного прерывания `switch`. 607 | Хотя, иногда, надо прервать внешний (по отношению к `switch`) цикл, а не сам `switch`, и в Go это может быть достигнуто путём добавления метки перед циклом, и переходом к этой метке в случае вызова `break`. В следующем примере представлены оба случая: 608 | 609 | ```golang 610 | Loop: 611 | for n := 0; n < len(src); n += size { 612 | switch { 613 | case src[n] < sizeOne: 614 | if validateOnly { 615 | break 616 | } 617 | size = 1 618 | update(src[n]) 619 | 620 | case src[n] < sizeTwo: 621 | if n+1 >= len(src) { 622 | err = errShortInput 623 | break Loop 624 | } 625 | if validateOnly { 626 | break 627 | } 628 | size = 2 629 | update(src[n] + src[n+1]< b 642 | func Compare(a, b []byte) int { 643 | for i := 0; i < len(a) && i < len(b); i++ { 644 | switch { 645 | case a[i] > b[i]: 646 | return 1 647 | case a[i] < b[i]: 648 | return -1 649 | } 650 | } 651 | switch { 652 | case len(a) > len(b): 653 | return 1 654 | case len(a) < len(b): 655 | return -1 656 | } 657 | return 0 658 | } 659 | ``` 660 | 661 | [^](#Оглавление) 662 | 663 | ### Переключатель типов (Типизированный переключатель, Type switch) 664 | 665 | [^](#Оглавление) 666 | 667 | `switch` может быть использован для определения динамических типов интерфейсных переменных. Так, типизированный `switch` использует синтаксис приведения типов, 668 | с ключевым словом `type` внутри скобок. Если `switch` объявляет переменную в 669 | выражении, то переменная будет иметь соответствующий тип в каждом пункте. Также, идиоматически верно переиспользовать имена переменных для объявления новых переменных 670 | с тем же именем, но другим типом в каждом случае: 671 | 672 | ```golang 673 | var t interface{} 674 | t = functionOfSomeType() 675 | switch t := t.(type) { 676 | default: 677 | fmt.Printf("unexpected type %T\n", t) // %T prints whatever type t has 678 | case bool: 679 | fmt.Printf("boolean %t\n", t) // t has type bool 680 | case int: 681 | fmt.Printf("integer %d\n", t) // t has type int 682 | case *bool: 683 | fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool 684 | case *int: 685 | fmt.Printf("pointer to integer %d\n", *t) // t has type *int 686 | } 687 | ``` 688 | 689 | ## Функции и методы(Functions, методы) 690 | 691 | [^](#Оглавление) 692 | 693 | ### Множественное возвращение результатов 694 | 695 | [^](#Оглавление) 696 | 697 | Одно из особенностей языка Go - это то, что функции и методы могут возвращать множество значений. 698 | 699 | При использовании языка С, передача ошибки производится через отрицательное значение с описанием причины ошибки в "другом" месте. 700 | 701 | При использовании языка Go, функция `Write` может вернуть одновременно **и** возвращаемое значение **и** ошибку. 702 | Сигнатура метода `Write` в файлах пакета `os`: 703 | 704 | ```golang 705 | func (file *File) Write(b []byte) (n int, err error) 706 | ``` 707 | 708 | и как предусмотрено документацией, он возвращает число записанных байт и ненулевое значение ошибки `error`, когда `n` `!=` `len(b)`. 709 | Это общий стиль, смотрите также раздел посвящённый ошибкам в качестве примера. 710 | 711 | Данный подход исключает необходимость в возращении значимого параметра. 712 | Это очень простой способ возвращения из функции количества байт среза, возвращая число и следующий параметр. 713 | 714 | ```golang 715 | func nextInt(b []byte, i int) (int, int) { 716 | for ; i < len(b) && !isDigit(b[i]); i++ { 717 | } 718 | x := 0 719 | for ; i < len(b) && isDigit(b[i]); i++ { 720 | x = x*10 + int(b[i]) - '0' 721 | } 722 | return x, i 723 | } 724 | ``` 725 | 726 | Вы можете сканировать число чисел во входном срезе `b` следующим образом: 727 | 728 | 729 | ```golang 730 | for i := 0; i < len(b); { 731 | x, i = nextInt(b, i) 732 | fmt.Println(x) 733 | } 734 | ``` 735 | 736 | ### Именование параметров результата 737 | 738 | [^](#Оглавление) 739 | 740 | Возвращаемым "параметрам" в языке Go можно давать имена и это часто используется как входные параметры. 741 | Когда они именованы, то они инициализируются нулевым значением необходимого типа в самом начале функции. 742 | Если функция, в которой определены именованные параметры, вызывает конструкцию возврата без аргументов, то значения именованных параметров будут использованы ей как возвращаемые значения. 743 | Именование не обязательное, но оно может сделать код короче и чище - самодокументированным. 744 | Если имя результата будет `nextInt`, то очевидно что тип результата `int`. 745 | 746 | 747 | ```golang 748 | func nextInt(b []byte, pos int) (value, nextPos int) { 749 | ``` 750 | 751 | На примере `io.ReadFull`: 752 | 753 | 754 | ```golang 755 | func ReadFull(r Reader, buf []byte) (n int, err error) { 756 | for len(buf) > 0 && err == nil { 757 | var nr int 758 | nr, err = r.Read(buf) 759 | n += nr 760 | buf = buf[nr:] 761 | } 762 | return 763 | } 764 | ``` 765 | 766 | [^](#Оглавление) 767 | 768 | ### Отсроченный вызов (Defer) 769 | 770 | [^](#Оглавление) 771 | 772 | В языке Go есть оператор `defer` для управления отложенного вызова функции, который будет вызван, как только функция имеющая `defer` оканчивается. 773 | Это не типичный но эффективный способ, когда необходимо закрыть ресурс после окончания функции. 774 | Канонические примеры - работа с mutex или закрытие файла. 775 | 776 | ```golang 777 | // Contents returns the file's contents as a string. 778 | func Contents(filename string) (string, error) { 779 | f, err := os.Open(filename) 780 | if err != nil { 781 | return "", err 782 | } 783 | defer f.Close() // f.Close will run when we're finished. 784 | 785 | var result []byte 786 | buf := make([]byte, 100) 787 | for { 788 | n, err := f.Read(buf[0:]) 789 | result = append(result, buf[0:n]...) // append is discussed later. 790 | if err != nil { 791 | if err == io.EOF { 792 | break 793 | } 794 | return "", err // f will be closed if we return here. 795 | } 796 | } 797 | return string(result), nil // f will be closed if we return here. 798 | } 799 | ``` 800 | 801 | Отложенный вызов функции `Close` имеет 2 преимущества. Во-первых, гарантирует что не будет забыто закрытие файла - ошибка, которую легко сделать, если в последствии в функции будет изменен параметр на другую папку. Во-вторых, закрытие близко расположено к открытию, что более ясно, чем располагать его в конце функции. 802 | 803 | Аргументы отложенной функции выполняются когда выполняется `defer`, а не когда функция вызвана. 804 | Кроме того , во избежания беспокойства по поводу изменяющихся переменных в функции, одна отложенная функция может отложить вызов множества функций. 805 | 806 | Вот простой пример: 807 | 808 | ```golang 809 | for i := 0; i < 5; i++ { 810 | defer fmt.Printf("%d ", i) 811 | } 812 | ``` 813 | 814 | Откладывание функции в LIFO очередь, приведет к следующей работе функции при печати на экран `4 3 2 1 0` . Более интересный пример - простое отслеживание функции в программе. Мы могли бы написать простое отслеживание, как это: 815 | 816 | 817 | ```golang 818 | func trace(s string) { fmt.Println("entering:", s) } 819 | func untrace(s string) { fmt.Println("leaving:", s) } 820 | 821 | // Use them like this: 822 | func a() { 823 | trace("a") 824 | defer untrace("a") 825 | // do something.... 826 | } 827 | ``` 828 | 829 | Мы могли бы сделать лучше - используя факт отложенных функций для оценки когда будет запущен `defer`. Отслеживаемая функция может настроить аргументы неотслеживаемой функции. 830 | К примеру: 831 | 832 | ```golang 833 | func trace(s string) string { 834 | fmt.Println("entering:", s) 835 | return s 836 | } 837 | 838 | func un(s string) { 839 | fmt.Println("leaving:", s) 840 | } 841 | 842 | func a() { 843 | defer un(trace("a")) 844 | fmt.Println("in a") 845 | } 846 | 847 | func b() { 848 | defer un(trace("b")) 849 | fmt.Println("in b") 850 | a() 851 | } 852 | 853 | func main() { 854 | b() 855 | } 856 | ``` 857 | 858 | 859 | выводит: 860 | 861 | 862 | ``` 863 | entering: b 864 | in b 865 | entering: a 866 | in a 867 | leaving: a 868 | leaving: b 869 | ``` 870 | 871 | Для программистов привыкших к блочному управлению ресурсами в других языках, функция `defer` может показаться странной, но интересной и мощной, так как позволяет уйти от блочного управления к управлению в функции. В разделах `panic` и `recover` будут также рассматриваться несколько примеров. 872 | 873 | [^](#Оглавление) 874 | 875 | 876 | ## Данные 877 | 878 | [^](#Оглавление) 879 | 880 | ### Созданные с помощью `new` 881 | 882 | [^](#Оглавление) 883 | 884 | Для создания примитивов в языке Go используются функции `new` и `make`. 885 | Они разные и применяются для разных типов, это может сбить с толку, но правило очень просто. 886 | Для начала обсудим функцию `new`. 887 | Данная функция резервирует память, но не также как в других языках программирования, она не просто *инициализирует* память, а вместо этого заполняет *нулями*. 888 | 889 | К примеру `new(T)` резервирует память нулями для нового элемента типа `T` и возвращает его указатель на значение типа `*T`. В терминологии Go, он возвращает указатель на новую зарезервированную память заполненная нулями с типом `T`. 890 | 891 | 892 | **TODO** 893 | Since the memory returned by `new` is zeroed, it's helpful to arrange when designing your data structures that the zero value of each type can be used without further initialization. This means a user of the data structure can create one with `new` and get right to work. 894 | For example, the documentation for `bytes.Buffer` states that "the zero value for `Buffer` is an empty buffer ready to use." 895 | Similarly, `sync.Mutex` does not have an explicit constructor or `Init` method. 896 | Instead, the zero value for a `sync.Mutex` is defined to be an unlocked mutex. 897 | The zero-value-is-useful property works transitively. Consider this type declaration. 898 | **-** 899 | 900 | ```golang 901 | type SyncedBuffer struct { 902 | lock sync.Mutex 903 | buffer bytes.Buffer 904 | } 905 | ``` 906 | 907 | **TODO** 908 | Values of type `SyncedBuffer` are also ready to use immediately upon allocation or just declaration. In the next snippet, both `p` and `v` will work correctly without further arrangement. 909 | **-** 910 | 911 | ```golang 912 | p := new(SyncedBuffer) // type *SyncedBuffer 913 | var v SyncedBuffer // type SyncedBuffer 914 | ``` 915 | 916 | ### Конструкторы и составные литералы 917 | 918 | [^](#Оглавление) 919 | 920 | Иногда нулевых значений не достаточно и необходимо иметь конструктор, следующий пример взят из пакета `os`. 921 | 922 | ```golang 923 | func NewFile(fd int, name string) *File { 924 | if fd < 0 { 925 | return nil 926 | } 927 | f := new(File) 928 | f.fd = fd 929 | f.name = name 930 | f.dirinfo = nil 931 | f.nepipe = 0 932 | return f 933 | } 934 | ``` 935 | 936 | Существует много шаблонов. Мы просто можем использовать *составные литералы*, которые будут создавать новые сущности каждый раз. 937 | 938 | ```golang 939 | func NewFile(fd int, name string) *File { 940 | if fd < 0 { 941 | return nil 942 | } 943 | f := File{fd, name, nil, 0} 944 | return &f 945 | } 946 | ``` 947 | 948 | Обратите внимание на то, что в отличии от языка С, это нормально, возвращать адрес локальных переменных, так как переменная уже существует после возвращения из функции. 949 | На самом деле, возвращение адресов составных литералов создает новую сущность каждый раз, как он вычисляется. 950 | Итак мы можем объединить последние две строки: 951 | 952 | ```golang 953 | return &File{fd, name, nil, 0} 954 | ``` 955 | 956 | Поля составных литералов должны быть в порядке объявления и все должны присутствовать. 957 | Однако, используя маркировку как пара *поле*`:`*значение*, могут инициализироваться в любом порядке, с пропущенными полями заполняемые нулями. 958 | Таким образом, можно объявить: 959 | 960 | ```golang 961 | return &File{fd: fd, name: name} 962 | ``` 963 | 964 | В предельном случае, когда составной литерал без полей вообще, то создание нулевым значением будет тип. Выражения `new(File)` и `&File{}` одинаковы. 965 | 966 | 967 | Составные литералы могут также создавать массивы, срезы, карты, с пометкой полей как индексов или ключами карт. 968 | К примеру, инициализированные значения `Enone`, `Eio`, и `Einval` разные. 969 | 970 | 971 | ```golang 972 | a := [...]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} 973 | s := []string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} 974 | m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"} 975 | ``` 976 | 977 | [^](#Оглавление) 978 | 979 | ### Создание с помощью `make` 980 | 981 | [^](#Оглавление) 982 | 983 | Возвращаясь к созданию элементов. 984 | Встроенная функция `make(T, `*args*`)` служит для других целей нежели `new(T)`. 985 | Он создает только срезы, карты и каналы, и возвращают *инициализированные* (не нулевые) значение типа `T` (а не `*T`). 986 | Причиной различия для этих трех типов, в том что внутри они представляют из себя структуры данных, которые необходимо инициализировать перед использованием. 987 | К примеру, срезы - это трехэлементная структура, содержащая указатель на данные(внутри массив), длину, и емкость, причём пока все элементы не инициализированы - срез *нулевой* `nil`. 988 | Для срезов, карт и каналов, встроенная команда `make` инициализирует внутреннюю структуру данных и подготавливает значения к использованию. 989 | 990 | К примеру: 991 | 992 | ```golang 993 | make([]int, 10, 100) 994 | ``` 995 | 996 | создает массив из 100 значений типа `int` и затем создает структуру среза длинной 10 и емкостью 100 со ссылкой только на первые 10 элементов. 997 | (Когда создается слайс, его емкость задавать не обязательно, смотрите раздел посвящённый срезам.) 998 | В противоположность, `new([]int)` возвращает указатель на новый, созданный, заполненный нулями срез, это указатель на значение `nil` среза. 999 | 1000 | Эти примеры показывают различие между `new` и `make`. 1001 | 1002 | 1003 | ```golang 1004 | var p *[]int = new([]int) // allocates slice structure; *p == nil; rarely useful 1005 | var v []int = make([]int, 100) // the slice v now refers to a new array of 100 ints 1006 | 1007 | // Unnecessarily complex: 1008 | var p *[]int = new([]int) 1009 | *p = make([]int, 100, 100) 1010 | 1011 | // Idiomatic: 1012 | v := make([]int, 100) 1013 | ``` 1014 | 1015 | Помните что `make` используется только для карт, срезов и каналов и не возвращают указатель. 1016 | Для получения указателя в явном виде используйте `new` или возьмите указатель в явном виде. 1017 | 1018 | [^](#Оглавление) 1019 | 1020 | ### Массивы 1021 | 1022 | [^](#Оглавление) 1023 | 1024 | Массивы популярны когда точно известно необходимое количество памяти, чтобы не делать излишних пересозданий, но в первую очередь они являются составной частью для срезов, о которых будет описано в следующем разделе. 1025 | 1026 | Какие основные отличия между обращением с массивами между языками Go и C: 1027 | * Массивы значений. Присвоение одно массива другому копирует все элементы. 1028 | * Если вы передаёте массив в функцию, то передаётся копия массива, а не указатель на него. 1029 | * Размер массива является частью массива. Типы `[10]int` и `[20]int` разные. 1030 | 1031 | Массивы могут быть полезными, но дорогими(с точки зрения производительности) и если Вы хотите иметь гибкость и эффективность схожее с поведением в языке C-like, то необходимо использовать в функциях указатели. 1032 | 1033 | 1034 | ```golang 1035 | func Sum(a *[3]float64) (sum float64) { 1036 | for _, v := range *a { 1037 | sum += v 1038 | } 1039 | return 1040 | } 1041 | 1042 | array := [...]float64{7.0, 8.5, 9.1} 1043 | x := Sum(&array) // Note the explicit address-of operator 1044 | ``` 1045 | 1046 | Но данный стиль не подходит Go. 1047 | Используйте срезы вместо массивов. 1048 | 1049 | 1050 | [^](#Оглавление) 1051 | 1052 | ### Срезы(Slices, слайсы) 1053 | 1054 | [^](#Оглавление) 1055 | 1056 | Срезы это обёртка для массивов и при этом более общий и мощный, и предоставляет собой более удобный интерфейс по управлению данными, в случаях, когда не известно точное количество элементов и необходимо преобразование размера массивов. 1057 | Большинство программ на языке Go, выполнены с использованием срезов, а не простых массивов. 1058 | 1059 | Срез хранит ссылку на массив и поэтому если приравнять срез к другому срезу, то будет тот же массив. 1060 | Если срез является аргументом функции, то изменения элементов в срезе будут видны вызывающему данному функцию, это аналогично передаче указателя на базовый массив. 1061 | В функция `Read` может принимать в качестве аргумента срез, что равнозначно указателю на массив и длины массива; длина среза указывает верхний предел количество данных которые необходимо прочитать. 1062 | В данном случае тип `File` пакета `os` имеет следующую сигнатуру метода `Read`: 1063 | 1064 | ```golang 1065 | func (f * File) Read(buf []byte) (n int, err error) 1066 | ``` 1067 | 1068 | Метод возвращает количество прочитанных байт или если есть, то ошибку. 1069 | Для чтения первых 32 байт в буфере `buf`, *получить(срезать) часть* буфера. 1070 | 1071 | ```golang 1072 | n, err := f.Read(buf[0:32]) 1073 | ``` 1074 | 1075 | Такой срез является эффективным. На самом деле, если оставить в стороне эффективность, то следующий пример показывает чтение первых 32 байт из буфера. 1076 | 1077 | ```golang 1078 | var n int 1079 | var err error 1080 | for i := 0; i < 32; i++ { 1081 | nbytes, e := f.Read(buf[i:i+1]) // Read one byte. 1082 | if nbytes == 0 || e != nil { 1083 | err = e 1084 | break 1085 | } 1086 | n += nbytes 1087 | } 1088 | ``` 1089 | 1090 | Длина среза может меняться, пока не исчерпает размер внутреннего массива. 1091 | С помощью встроенной функции `cap` можно узнать *емкость* среза, представляющий максимальную длину среза. 1092 | В следующем примере рассматривается функция для добавления данных в срез. 1093 | Если данные превышают ёмкость среза, то срез необходимо переопределить. 1094 | Функция `Append` возвращает результирующий срез. Функция использует тот факт что использование `len` и `cap` допустимо, даже если у нас имеется нулевой срез `nil` - при этом возвращая 0. 1095 | 1096 | ```golang 1097 | func Append(slice, data []byte) []byte { 1098 | l := len(slice) 1099 | if l + len(data) > cap(slice) { // reallocate 1100 | // Allocate double what's needed, for future growth. 1101 | newSlice := make([]byte, (l+len(data))* 2) 1102 | // The copy function is predeclared and works for any slice type. 1103 | copy(newSlice, slice) 1104 | slice = newSlice 1105 | } 1106 | slice = slice[0:l+len(data)] 1107 | for i, c := range data { 1108 | slice[l+i] = c 1109 | } 1110 | return slice 1111 | } 1112 | ``` 1113 | 1114 | 1115 | **TODO** 1116 | We must return the slice afterwards because, although `Append` 1117 | can modify the elements of `slice`, the slice itself (the run-time data 1118 | structure holding the pointer, length, and capacity) is passed by value. 1119 | **-** 1120 | 1121 | Добавление элементов в срез настолько популярно, что функция `append` стала встроенной. Для того чтобы понять принцип работы данной функции нам необходимо больше информации, поэтому мы вернёмся к этому позже. 1122 | 1123 | [^](#Оглавление) 1124 | 1125 | 1126 | ### Двухмерные срезы 1127 | 1128 | [^](#Оглавление) 1129 | 1130 | Массивы и срезы в Go - одномерные. 1131 | Для создания двухмерного массива или среза, нам необходимо определять массив-массивов или срез-срезов, как в примере: 1132 | 1133 | ```golang 1134 | type Transform [3][3]float64 // A 3x3 array, really an array of arrays. 1135 | type LinesOfText [][]byte // A slice of byte slices. 1136 | ``` 1137 | 1138 | В связи с тем, что срезы переменной длины, то допустимо иметь каждый внутренний срез разной длины. 1139 | Это наиболее общая ситуация, как в примере `LinesOfText`, в котором каждая строка имеет независимую длину. 1140 | 1141 | ```golang 1142 | text := LinesOfText{ 1143 | []byte("Now is the time"), 1144 | []byte("for all good gophers"), 1145 | []byte("to bring some fun to the party."), 1146 | } 1147 | ``` 1148 | 1149 | Иногда необходимо создавать двухмерные срезы, к примеру при обработки пикселей. 1150 | Есть 2 способа для этого: 1151 | * Первый, создание каждого среза независимо 1152 | * Второй, создание простого массива срезов. 1153 | Наилучший способ выбирается в зависимости от программы. 1154 | Если срез можно увеличивать или уменьшать, они должны быть независимы, для того чтобы избежать перезаписи новых строк. Если не требуется изменять размер, то наиболее эффективным был бы способ с создание одним их аллоцированием(инициализацией). 1155 | Рассмотрим оба способа. 1156 | 1157 | ```golang 1158 | // Allocate the top-level slice. 1159 | picture := make([][]uint8, YSize) // One row per unit of y. 1160 | // Loop over the rows, allocating the slice for each row. 1161 | for i := range picture { 1162 | picture[i] = make([]uint8, XSize) 1163 | } 1164 | ``` 1165 | 1166 | с одним созданием: 1167 | 1168 | ```golang 1169 | // Allocate the top-level slice, the same as before. 1170 | picture := make([][]uint8, YSize) // One row per unit of y. 1171 | // Allocate one large slice to hold all the pixels. 1172 | pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8. 1173 | // Loop over the rows, slicing each row from the front of the remaining pixels slice. 1174 | for i := range picture { 1175 | picture[i], pixels = pixels[:XSize], pixels[XSize:] 1176 | } 1177 | ``` 1178 | 1179 | [^](#Оглавление) 1180 | 1181 | ### Карты(Maps) 1182 | 1183 | [^](#Оглавление) 1184 | 1185 | Карты - это удобная и мощная встроенная структура данных, связывающая значение одного типа(*ключ (key)*) со значением другого типа (*элемент (element)* или *значение (value)*). 1186 | Ключ может быть любого типа, для которого определён оператор равно, как для целых чисел, чисел с плавающей точкой или комплексные числа, строки, указатели, интерфейсы (если динамические типы поддерживают равенство), структуры и массивы. 1187 | Срезы не используются в качестве ключа для карт, так как равенство не определено для них. 1188 | Карты, также как и срезы, имеют внутреннюю структуру данных. 1189 | Если Вы передадите карту в функции и измените содержание карты, то изменения останутся для вызывающего. 1190 | Карты могут быть созданы с использованием синтаксиса составных литералов с разделением по колонкам пар ключ-значение, поэтому легко создать начальные данные. 1191 | 1192 | ```golang 1193 | var timeZone = map[string]int{ 1194 | "UTC": 0*60*60, 1195 | "EST": -5*60*60, 1196 | "CST": -6*60*60, 1197 | "MST": -7*60*60, 1198 | "PST": -8*60*60, 1199 | } 1200 | ``` 1201 | 1202 | Добавление и получение значений из карт, синтаксически, выглядит как для массивов или срезов, за тем исключением того что индекс не обязательно должен быть целым числом. 1203 | 1204 | ```golang 1205 | offset := timeZone["EST"] 1206 | ``` 1207 | 1208 | При попытке получения значения из карты по ключу, которого нет в карте, приведёт к возвращению нулевого значения. 1209 | К примеру, если карта содержит целые числа, как описывалось выше, для несуществующего ключа будет возвращено `0`. 1210 | Это можно представить как карту у которой в качестве типа значения используется `bool`. Добавление записи в карту это как добавление со значением `true` в карту и дальнейшая простая проверка на индексирование. 1211 | 1212 | ```golang 1213 | attended := map[string]bool{ 1214 | "Ann": true, 1215 | "Joe": true, 1216 | ... 1217 | } 1218 | 1219 | if attended[person] { // will be false if person is not in the map 1220 | fmt.Println(person, "was at the meeting") 1221 | } 1222 | ``` 1223 | 1224 | Иногда необходимо отличать отсутствие записи от нулевого значения. К примеру, есть ли запись для `"UTC"` или это пустая строка потому что отсутствует значение в карте? 1225 | Для того чтобы отличить - Вы можете использовать множественное присвоение. 1226 | 1227 | ```golang 1228 | var seconds int 1229 | var ok bool 1230 | seconds, ok = timeZone[tz] 1231 | ``` 1232 | 1233 | Очевидная причина называть данную идиому "запятая ок". 1234 | В данном примере, если `tz` существует, то `seconds` будет иметь необходимое значение и `ok` будет `true`, но если не существует, то `seconds` будет иметь нулевое значение а `ok` будет `false`. 1235 | В следующем примере, представлена функция с хорошим описанием ошибки: 1236 | 1237 | ```golang 1238 | func offset(tz string) int { 1239 | if seconds, ok := timeZone[tz]; ok { 1240 | return seconds 1241 | } 1242 | log.Println("unknown time zone:", tz) 1243 | return 0 1244 | } 1245 | ``` 1246 | 1247 | В случаи, если нас не интересует само значение, а лишь его наличие, то можно использовать **пустой идентификатор `_`**, расположенный вместо значения. 1248 | 1249 | ```golang 1250 | _ , present := timeZone[tz] 1251 | ``` 1252 | 1253 | Для удаления записи из карты, необходимо использовать встроенную функцию `delete`, где в качестве аргументов задаётся карта и ключ для удаления. 1254 | Данная операция безопасна, даже если данного ключа уже нет в карте. 1255 | 1256 | ```golang 1257 | delete(timeZone, "PDT") // Now on Standard Time 1258 | ``` 1259 | 1260 | [^](#Оглавление) 1261 | 1262 | ### Печать(Printing) 1263 | 1264 | [^](#Оглавление) 1265 | 1266 | Форматированная печать в Go подобна стилю в языке C `printf`, но более богаче и более обобщенное. Необходимые функции расположены в пакете `fmt` и имеют названия с большой буквы: `fmt.Printf`, `fmt.Fprintf`, `fmt.Sprintf` и так далее. Функции (`Sprintf` и другие) возвращают строку, а не заполняют предоставленный буфер. 1267 | 1268 | Вам нет необходимости в создании форматировании строк, так как для каждой `Printf`, `Fprintf` and `Sprintf` есть пара функций к примеру `Print` и `Println`. 1269 | 1270 | Данные функции не берут формат строки, а вместо этого устанавливают форматирование по умолчанию для каждого аргумента. Функция `Println` также добавляет пробел между аргументами и добавляет разрыв строки в конце строки. Функция `Print` добавляет пробел только той же строке. 1271 | В примере каждая строка производит одинаковый результат. 1272 | 1273 | ```golang 1274 | fmt.Printf("Hello %d\n", 23) 1275 | fmt.Fprint(os.Stdout, "Hello ", 23, "\n") 1276 | fmt.Println("Hello", 23) 1277 | fmt.Println(fmt.Sprint("Hello ", 23)) 1278 | ``` 1279 | 1280 | Для форматированной печати функцией `fmt.Fprint` и его друзьями, принимают в качестве первого аргумента объект реализующий интерфейс `io.Writer`. 1281 | Значения `os.Stdout` и `os.Stderr` знакомы. 1282 | 1283 | 1284 | Следующее расходится с реализацией на языке С. Первое, числовые форматы `%d` не имеют флагов знаковости или размера; Вместо этого, функции печати используют тип аргумента для задания свойств. 1285 | 1286 | ```golang 1287 | var x uint64 = 1<<64 - 1 1288 | fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x)) 1289 | ``` 1290 | 1291 | печатает 1292 | 1293 | ``` 1294 | 18446744073709551615 ffffffffffffffff; -1 -1 1295 | ``` 1296 | 1297 | Если вы используете соглашение по умолчанию, то для целых чисел можно использовать обобщенный формат `%v` (для "значений"); и результат будет одинаков как для `Print` так и для `Println`. 1298 | 1299 | Более того, данный формат может напечатать *любое* значение, даже срез, структуру или карту. 1300 | Печать карты временной зоны из предыдущего раздела. 1301 | 1302 | ```golang 1303 | fmt.Printf("%v\n", timeZone) // or just fmt.Println(timeZone) 1304 | ``` 1305 | 1306 | который печатает следующий результат 1307 | 1308 | ``` 1309 | map[CST:-21600 PST:-28800 EST:-18000 UTC:0 MST:-25200] 1310 | ``` 1311 | 1312 | Ключи карт могут быть напечатаны в любом порядке. 1313 | При печати структуры, с аннотацией `%+v` производиться печать полей структуры с их именами и для каждого значения с форматом `%#v` печатается значение с полным синтаксисом Go. 1314 | 1315 | ```golang 1316 | type T struct { 1317 | a int 1318 | b float64 1319 | c string 1320 | } 1321 | t := &T{ 7, -2.35, "abc\tdef" } 1322 | fmt.Printf("%v\n", t) 1323 | fmt.Printf("%+v\n", t) 1324 | fmt.Printf("%#v\n", t) 1325 | fmt.Printf("%#v\n", timeZone) 1326 | ``` 1327 | 1328 | печатает 1329 | 1330 | ``` 1331 | &{7 -2.35 abc def} 1332 | &{a:7 b:-2.35 c:abc def} 1333 | &main.T{a:7, b:-2.35, c:"abc\tdef"} 1334 | map[string] int{"CST":-21600, "PST":-28800, "EST":-18000, "UTC":0, "MST":-25200} 1335 | ``` 1336 | 1337 | (На заметку: обратите внимание на амперсанды) 1338 | 1339 | Для ссылок на строки подходит `%q`, который принимает значение на `string` или `[]byte`. 1340 | Альтернативный формат `%#q` будет использовать обратные кавычки, если это возможно. 1341 | (Формат `%q` также допустим для целых чисел и рун, создавая односсылочные константы рун.) 1342 | Также, `%x` работает со строками, массивом байт и срезом байт также как с целыми числами, создаёт шестнадцатеричные целые строки, а с пробелом в формате (`% x`) добавляет пробелы между байтами. 1343 | 1344 | 1345 | Другой удобный формат `%T`, который печатает *тип* значения. 1346 | 1347 | ```golang 1348 | fmt.Printf("%T\n", timeZone) 1349 | ``` 1350 | 1351 | печатает 1352 | 1353 | ``` 1354 | map[string] int 1355 | ``` 1356 | 1357 | Если Вы хотите свой собственный формат типа, то для этого достаточно метод с сигнатурой `String() string` для Вашего типа. 1358 | Для нашего простого примера, тип `T`, выглядит следующим образом. 1359 | 1360 | ```golang 1361 | func (t * T) String() string { 1362 | return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c) 1363 | } 1364 | fmt.Printf("%v\n", t) 1365 | ``` 1366 | 1367 | Печатает в следующем формате 1368 | 1369 | ``` 1370 | 7/-2.35/"abc\tdef" 1371 | ``` 1372 | 1373 | (Если Вам необходимо напечатать *значение* типа `T` как указателя на тип `T`, то метод `String` должен иметь значение типа; этот пример использует указатель, т.к. они более эффективны и идиоматичны типу структуры.) 1374 | 1375 | 1376 | Наша функция `String` может вызывать `Sprintf`, потому что функция печати возвращаемая и поэтому можно её обернуть. Это важно для понимания данного подхода. 1377 | Однако, не создавайте функцию `String` вызывающую метод `Sprintf`, в случаи если далее будет рекурсивно вызвана `String`. 1378 | Это может произойти если `Sprintf` вызывает на печать строку получателя, который вызовет функцию снова. Эту ошибку можно легко создать и она показана на следующем примере. 1379 | 1380 | ```golang 1381 | type MyString string 1382 | 1383 | func (m MyString) String() string { 1384 | return fmt.Sprintf("MyString=%s", m) // Error: will recur forever. 1385 | } 1386 | ``` 1387 | 1388 | Для того чтобы решить эту проблему, необходимо изменить аргумент на базовый тип, который не имеет функции. 1389 | 1390 | ```golang 1391 | type MyString string 1392 | func (m MyString) String() string { 1393 | return fmt.Sprintf("MyString=%s", string(m)) // OK: note conversion. 1394 | } 1395 | ``` 1396 | 1397 | Другой способ печати это допустить печать функции аргументов напрямую в другую функцию. 1398 | Сигнатура `Printf` используется для типов `...interface{}`, что допускает произвольное число аргументов, которые добавляются после формата *format*. 1399 | 1400 | ```golang 1401 | func Printf(format string, v ...interface{}) (n int, err error) { 1402 | ``` 1403 | 1404 | **TODO** 1405 | Within the function `Printf`, `v` acts like a variable of type `[]interface{}` but if it is passed to another variadic function, it acts like a regular list of arguments. 1406 | Here is the implementation of the function `log.Println` we used above. It passes its arguments directly to `fmt.Sprintln` for the actual formatting. 1407 | **-** 1408 | 1409 | ```golang 1410 | // Println prints to the standard logger in the manner of fmt.Println. 1411 | func Println(v ...interface{}) { 1412 | std.Output(2, fmt.Sprintln(v...)) // Output takes parameters (int, string) 1413 | } 1414 | ``` 1415 | 1416 | Запись `...` после `v` при вызове функции `Sprintln` объявляет компилятору о том что `v` является списком аргументов; с другой стороны `v` воспринимается как простой срез аргументов. 1417 | 1418 | Если Вам необходимо большее количество информации, то смотрите документацию `godoc` в пакете `fmt`. 1419 | 1420 | Кстати параметр `...` может иметь тип, для примера`...int` для функции определения минимума используется список целых чисел: 1421 | 1422 | ```golang 1423 | func Min(a ...int) int { 1424 | min := int(^uint(0) >> 1) // largest int 1425 | for _ , i := range a { 1426 | if i < min { 1427 | min = i 1428 | } 1429 | } 1430 | return min 1431 | } 1432 | ``` 1433 | 1434 | [^](#Оглавление) 1435 | 1436 | ### Присоединение(Append) 1437 | 1438 | [^](#Оглавление) 1439 | 1440 | В настоящий момент? пришел момент для разъяснения конструкции встроенной функции `append`. Сигнатура функции `append` отличается от ранее описанной функции `Append`. 1441 | Схематично, выглядит следующим образом: 1442 | 1443 | ```golang 1444 | func append(slice []*T*, elements ...*T*) []*T* 1445 | ``` 1446 | 1447 | где *T* любой тип. Вы не можете написать в языке Go функцию в которой `T` определена вызывающим. Поэтому необходима поддержка компилятора для функции `append`. 1448 | 1449 | Данная функция `append` добавляет элемент в конец среза и возвращает результат. 1450 | Причина возврата результата, в том что как и в рукописной функции `Append` массив может измениться. 1451 | Простой пример: 1452 | 1453 | ```golang 1454 | x := []int{1,2,3} 1455 | x = append(x, 4, 5, 6) 1456 | fmt.Println(x) 1457 | ``` 1458 | 1459 | печатает `[1 2 3 4 5 6]`. Итак, `append` работает в принципе как `Printf` с произвольным количеством аргументов. 1460 | 1461 | 1462 | Но что если необходимо добавить срез в срез, как в нашей реализации `Append`? Все просто: используем `...` который мы использовали в `Output`. Вот пример кода для получение того же результата. 1463 | 1464 | ```golang 1465 | x := []int{1,2,3} 1466 | y := []int{4,5,6} 1467 | x = append(x, y...) 1468 | fmt.Println(x) 1469 | ``` 1470 | 1471 | Обращаю внимание, что без `...` компилятор напишет ошибку, так как `y` не имеет тип `int`. 1472 | 1473 | 1474 | [^](#Оглавление) 1475 | 1476 | ## Инициализация(Initialization) 1477 | 1478 | [^](#Оглавление) 1479 | 1480 | Инициализация в языке Go более мощный инструмент нежели в языках С или С++. 1481 | Даже сложные структуры можно инициализировать. Упорядочивание между инициализируемыми объектами разных пакетов, обрабатывается корректно. 1482 | 1483 | [^](#Оглавление) 1484 | 1485 | ### Константы(Constants) 1486 | 1487 | [^](#Оглавление) 1488 | 1489 | 1490 | Константы в Go это просто константы. 1491 | Они создаются во время компиляции даже если она определена в локальной функции и могут быть цифры, символы(руны), строки или булевый тип. 1492 | Из-за ограничения времени компиляции, компилятор должен определять какие выражения могут быть константами. К примеру, выражение `1<<3` это константное выражение, в то время как выражение `math.Sin(math.Pi/4)` не является константой, так как вызывает функцию `math.Sin` требующую выполнения по время выполнения. 1493 | 1494 | 1495 | В языке Go, перечисление констант производиться с помощью перечислителя **`iota`**. Так как `iota` может быть неявно повторяемой для выражения или выражений, то легко можно строить сложные наборы значений. 1496 | 1497 | 1498 | ```golang 1499 | //{{code "/doc/progs/eff_bytesize.go" `/^type ByteSize/` `/^\)/`}} 1500 | // Copyright 2009 The Go Authors. All rights reserved. 1501 | // Use of this source code is governed by a BSD-style 1502 | // license that can be found in the LICENSE file. 1503 | 1504 | package main 1505 | 1506 | import "fmt" 1507 | 1508 | type ByteSize float64 1509 | 1510 | const ( 1511 | _ = iota // ignore first value by assigning to blank identifier 1512 | KB ByteSize = 1 << (10 * iota) 1513 | MB 1514 | GB 1515 | TB 1516 | PB 1517 | EB 1518 | ZB 1519 | YB 1520 | ) 1521 | ``` 1522 | 1523 | Использование функции `String` к пользовательским типам производить печать необходимым образом. 1524 | **TODO** 1525 | Although you'll see it most often applied to structs, this technique is also useful for scalar types such as floating-point types like `ByteSize`. 1526 | **-** 1527 | 1528 | ```golang 1529 | //See code "/doc/progs/eff_bytesize.go" 1530 | 1531 | func (b ByteSize) String() string { 1532 | switch { 1533 | case b >= YB: 1534 | return fmt.Sprintf("%.2fYB", b/YB) 1535 | case b >= ZB: 1536 | return fmt.Sprintf("%.2fZB", b/ZB) 1537 | case b >= EB: 1538 | return fmt.Sprintf("%.2fEB", b/EB) 1539 | case b >= PB: 1540 | return fmt.Sprintf("%.2fPB", b/PB) 1541 | case b >= TB: 1542 | return fmt.Sprintf("%.2fTB", b/TB) 1543 | case b >= GB: 1544 | return fmt.Sprintf("%.2fGB", b/GB) 1545 | case b >= MB: 1546 | return fmt.Sprintf("%.2fMB", b/MB) 1547 | case b >= KB: 1548 | return fmt.Sprintf("%.2fKB", b/KB) 1549 | } 1550 | return fmt.Sprintf("%.2fB", b) 1551 | } 1552 | ``` 1553 | 1554 | Выражение `YB` печатается как `1.00YB`, когда `ByteSize(1e13)` печатает как `9.09TB`. 1555 | 1556 | 1557 | Используемый здесь `Sprintf` в функции `String` типа `ByteSize` безопасна(не вызывается рекурсивно), не потому что происходит конвертирование, а потому что вызывается функция `Sprintf` с `%f`, который не строковый формат:`Sprintf` будет вызывать функцию `String`, функцию которой необходима строка и `%f` число с плавающей точкой. 1558 | 1559 | 1560 | [^](#Оглавление) 1561 | 1562 | ### Переменные(Variables) 1563 | 1564 | [^](#Оглавление) 1565 | 1566 | Переменные могут инициализироваться как константы, но инициализация производиться во время работы. 1567 | 1568 | ```golang 1569 | var ( 1570 | home = os.Getenv("HOME") 1571 | user = os.Getenv("USER") 1572 | gopath = os.Getenv("GOPATH") 1573 | ) 1574 | ``` 1575 | 1576 | [^](#Оглавление) 1577 | 1578 | ### Функция init 1579 | 1580 | [^](#Оглавление) 1581 | 1582 | Каждый исходный код может определить свою первичную функцию `init` для обязательных настройки. (На самом деле файл может иметь несколько функций `init`.) 1583 | Функция `init` вызывается после всех объявлений переменных и после всех объявлений переменных всех пакетов. 1584 | 1585 | Общее применение функции `init` в проверки или починки состояния программы до начала реального исполнения. 1586 | 1587 | ```golang 1588 | func init() { 1589 | if user == "" { 1590 | log.Fatal("$USER not set") 1591 | } 1592 | if home == "" { 1593 | home = "/home/" + user 1594 | } 1595 | if gopath == "" { 1596 | gopath = home + "/go" 1597 | } 1598 | // gopath may be overridden by --gopath flag on command line. 1599 | flag.StringVar(&gopath, "gopath", gopath, "override default GOPATH") 1600 | } 1601 | ``` 1602 | 1603 | [^](#Оглавление) 1604 | 1605 | ## Методы(Methods) 1606 | 1607 | [^](#Оглавление) 1608 | 1609 | ### Указатели или Значения 1610 | 1611 | [^](#Оглавление) 1612 | 1613 | Как мы видели в примеры с `ByteSize`, функции может иметь имя типа (кроме указателей или интерфейсов) и приемник не обязательно должен иметь структуры. 1614 | 1615 | Как обсуждалось ранее в срезах, мы написали функцию `Append`. 1616 | Мы можем определить функции вместе со срезом. Для этого, мы объявим именованный тип, который мы можем связать с функцией и там самым создать получателя данной функции для значений этого типа. 1617 | 1618 | ```golang 1619 | type ByteSlice []byte 1620 | 1621 | func (slice ByteSlice) Append(data []byte) []byte { 1622 | // Body exactly the same as the Append function defined above. 1623 | } 1624 | ``` 1625 | 1626 | Данный метод все также возвращает обновленный срез. Для решения этой неуклюжести можно воспользоваться *указателем* на `ByteSize` в получатель, итак можно переписать следующим образом: 1627 | 1628 | ```golang 1629 | func (p *ByteSlice) Append(data []byte) { 1630 | slice := *p 1631 | // Body as above, without the return. 1632 | *p = slice 1633 | } 1634 | ``` 1635 | 1636 | На самом деле, мы можем сделать это ещё лучше. Если мы изменим функцию, то она будет выглядеть как стандартная функция `Write`, то есть вот так, 1637 | 1638 | ```golang 1639 | func (p *ByteSlice) Write(data []byte) (n int, err error) { 1640 | slice := *p 1641 | // Again as above. 1642 | *p = slice 1643 | return len(data), nil 1644 | } 1645 | ``` 1646 | 1647 | тип `*ByteSlice` удовлетворяет стандартному интерфейсу `io.Writer`, что удобно. Например, мы можем напечатать один из них: 1648 | 1649 | ```golang 1650 | var b ByteSlice 1651 | fmt.Fprintf(&b, "This hour has %d days\n", 7) 1652 | ``` 1653 | 1654 | Мы передаем адрес `ByteSlice`, поскольку только `*ByteSlice` удовлетворяет интерфейсу `io.Writer`. 1655 | Правило получателя *о указателях или значениях*, в том что функции значения могут использоваться для указателей и значений, а функция указателя может только использовать указатель. 1656 | 1657 | 1658 | Это правило возникло потому что функции указателя могут изменять получателя. 1659 | Вызывая значение в функции значений получаешь копию значения, поэтому никаких модификаций не произойдет. 1660 | Поэтому язык запрещает эту ошибку. 1661 | Когда адресуется значение, то язык заботится о подставлении символа адресации автоматически. 1662 | 1663 | К примеру, переменная `b` адресованная, поэтому мы можем вызвать функцию `Write` просто вызвав `b.Write`. 1664 | Компилятор сам допишет `(&b).Write` за нас. 1665 | 1666 | 1667 | Кстати, идея использования `Write` на срезах байт наиважнейшая для реализации `bytes.Buffer`. 1668 | 1669 | 1670 | [^](#Оглавление) 1671 | 1672 | ## Интерфейсы и другие типы 1673 | 1674 | [^](#Оглавление) 1675 | 1676 | ### Интерфейсы 1677 | 1678 | [^](#Оглавление) 1679 | 1680 | Интерфейсы в Go позволяют создать особое поведения для объектов: *Если нечто может делать* **это** *, то это можно использовать* **здесь**. Мы уже это встречали в простых примерах, когда реализовывали функцию `String` для печати, в то время как `Fprintf` может выдавать на печать другое с методом `Write`. 1681 | Интерфейсы с одним или двумя функциями свойственны в языке Go, как `io.Writer` реализующий `Write`. 1682 | 1683 | Любой тип может реализовывать множество интерфейсов. 1684 | К примеру, коллекции могут быть отсортированы с помощью функций из пакета `sort`, если она реализует `sort.Interface`, который состоит из `Len()`, `Less(i, j int) bool`, и `Swap(i, j int)` и это может задать собственный формат. 1685 | Рассмотрим пример `Sequence` 1686 | 1687 | ```golang 1688 | //{{code "/doc/progs/eff_sequence.go" `/^type/` "$"}} 1689 | // Copyright 2009 The Go Authors. All rights reserved. 1690 | // Use of this source code is governed by a BSD-style 1691 | // license that can be found in the LICENSE file. 1692 | 1693 | package main 1694 | 1695 | import ( 1696 | "fmt" 1697 | "sort" 1698 | ) 1699 | 1700 | func main() { 1701 | seq := Sequence{6, 2, -1, 44, 16} 1702 | sort.Sort(seq) 1703 | fmt.Println(seq) 1704 | } 1705 | 1706 | type Sequence []int 1707 | 1708 | // Methods required by sort.Interface. 1709 | func (s Sequence) Len() int { 1710 | return len(s) 1711 | } 1712 | func (s Sequence) Less(i, j int) bool { 1713 | return s[i] < s[j] 1714 | } 1715 | func (s Sequence) Swap(i, j int) { 1716 | s[i], s[j] = s[j], s[i] 1717 | } 1718 | 1719 | // Method for printing - sorts the elements before printing. 1720 | func (s Sequence) String() string { 1721 | sort.Sort(s) 1722 | str := "[" 1723 | for i, elem := range s { 1724 | if i > 0 { 1725 | str += " " 1726 | } 1727 | str += fmt.Sprint(elem) 1728 | } 1729 | return str + "]" 1730 | } 1731 | ``` 1732 | 1733 | [^](#Оглавление) 1734 | 1735 | ### Преобразование (Conversions) 1736 | 1737 | [^](#Оглавление) 1738 | 1739 | Функция `String` работает с `Sequence` и `Sprint` уже работает со срезами. Мы может распространить данный эффект, если конвертируем `Sequence` на `[]int` до вызова `Sprint`. 1740 | 1741 | ```golang 1742 | func (s Sequence) String() string { 1743 | sort.Sort(s) 1744 | return fmt.Sprint([]int(s)) 1745 | } 1746 | ``` 1747 | 1748 | Это функция другой пример техники конвертирования для вызова `Sprintf` безопасно для функции `String`. 1749 | Так как два типа (`Sequence` и `[]int`) одинаковы, то мы можем игнорировать имя типа, это допустимое конвертирование между ними. 1750 | При конвертации не происходит создание нового значения, это временная замена существующего значения на новый тип. 1751 | (При других допустимых конвертациях, к примеру из целого числа в число с плавающей точкой, происходит создание нового значения.) 1752 | 1753 | 1754 | Это идиоматично в программе Go - конвертация типа позволяет получить доступ к другим функциям. К примеру, мы можем использовать существующий тип `sort.IntSlice`: 1755 | 1756 | ```golang 1757 | type Sequence []int 1758 | 1759 | // Method for printing - sorts the elements before printing 1760 | func (s Sequence) String() string { 1761 | sort.IntSlice(s).Sort() 1762 | return fmt.Sprint([]int(s)) 1763 | } 1764 | ``` 1765 | 1766 | Теперь, наш `Sequence` реализует множество интерфейсов (сортировка и печать), мы можем использовать множество типов (`Sequence`, `sort.IntSlice` и `[]int`), которые выполняют определенную часть работ. 1767 | Это не типично в использовании, но эффективно. 1768 | 1769 | [^](#Оглавление) 1770 | 1771 | ### Конвертация интерфейсов и привязка типов 1772 | 1773 | [^](#Оглавление) 1774 | 1775 | Переключатель типов(Type switches) является одной из форм конвертации: на основе интерфейса и переключателя для каждого элемента, в некотором смысле преобразует тип в элемент переключателя. 1776 | Это простой вариант как в коде `fmt.Printf` конвертирует значение в строку, используя переключатель типа. 1777 | И если это уже строка, мы хотим чтобы фактическое значение происходило по его интерфейсу, но в случаи если она имеет функцию `String`, то хотим чтобы в результате вызывалась именно она. 1778 | 1779 | ```golang 1780 | type Stringer interface { 1781 | String() string 1782 | } 1783 | 1784 | var value interface{} // Value provided by caller. 1785 | switch str := value.(type) { 1786 | case string: 1787 | return str 1788 | case Stringer: 1789 | return str.String() 1790 | } 1791 | ``` 1792 | 1793 | В первом случае ищется конкретное значение, во втором случаи происходит преобразование интерфейса в другой интерфейс. 1794 | Это хороший подход в преобразовании типов. 1795 | 1796 | Что если, мы будем беспокоиться лишь об одном типе? Если мы знаем что значение имеет тип `string` и мы хотим вытащить только его? 1797 | Можно сделать переключатель только с одним типом, но это будет *type assertion*. 1798 | И *type assertion* берет значение интерфейса и переводит из его значения в его тип. 1799 | Заимствование типа из открытия *type switch*, но переводит тип с помощью ключевого слова `type`: 1800 | 1801 | ```golang 1802 | value.(typeName) 1803 | ``` 1804 | 1805 | и в результате у нас значение со статическим типом `typeName`. 1806 | Этот тип должен быть конкретным типом имеющим интерфейс, или второй тип интерфейса - это тип в который может быть конвертирован. 1807 | Если мы знаем что это строка в значении, то мы можем записать: 1808 | 1809 | 1810 | ```golang 1811 | str := value.(string) 1812 | ``` 1813 | 1814 | Но если выясниться, что значение хранит не строку, то программа будет обрушена во время работы в *run-time error*. 1815 | Для защиты от этого используется идиома *запятая, ок* *"comma, ok"* для безопасности и проверка является ли значение строкой: 1816 | 1817 | 1818 | ```golang 1819 | str, ok := value.(string) 1820 | if ok { 1821 | fmt.Printf("string value is: %q\n", str) 1822 | } else { 1823 | fmt.Printf("value is not a string\n") 1824 | } 1825 | ``` 1826 | 1827 | В случаи неудачи, `str` будет всё ещё существовать и будет типом строка, но будет иметь нулевое значение - пустую строку. 1828 | 1829 | 1830 | Для иллюстрации, используем условие `if`-`else` как эквивалент переключателя типов *type switch* в начале этого раздела. 1831 | 1832 | ```golang 1833 | if str, ok := value.(string); ok { 1834 | return str 1835 | } else if str, ok := value.(Stringer); ok { 1836 | return str.String() 1837 | } 1838 | ``` 1839 | 1840 | [^](#Оглавление) 1841 | 1842 | ### Общее(Generality) 1843 | 1844 | [^](#Оглавление) 1845 | 1846 | Если тип существует только для реализации интерфейса и никогда не будет экспортироваться за пределы интерфейса, то нет необходимости экспортировать сам тип. 1847 | Экспортирование только интерфейса делает более понятным, что значение имеет не так интересно как поведение интерфейса. 1848 | Также это позволяет избегать повторения документации для каждого экземпляра общего метода. 1849 | 1850 | 1851 | В таких случаях, конструктор может возвращать значение интерфейса, что лучше чем реализованный тип. 1852 | Для примера, в библиотеках хэш *hash* оба конструктора `crc32.NewIEEE` и `adler32.New` возвращают тип интерфейса `hash.Hash32`. 1853 | Для подстановки алгоритма CRC-32 для Adler-32 в программе Go требуется только изменить вызов конструктора, а остальная часть кода не зависит от алгоритма. 1854 | 1855 | 1856 | Подобный подход позволяет создать поток шифровальных алгоритмов помимо имеющихся в пакете `crypto`, устанавливаются в цепочку отдельно от блока шифрования. 1857 | Интерфейс `Block` в пакете `crypto/cipher` имеющий поведение - шифрование, который обеспечивает шифрование одного блока данных. 1858 | Это по аналогии с пакетом `bufio`, пакет шифрования реализует этот интерфейс и может использовать конструктор потока шифрования, представляя интерфейс `Stream` без известных деталей о шифровании. 1859 | 1860 | Интерфейсы `crypto/cipher` выглядят следующим образом: 1861 | 1862 | ```golang 1863 | type Block interface { 1864 | BlockSize() int 1865 | Encrypt(src, dst []byte) 1866 | Decrypt(src, dst []byte) 1867 | } 1868 | 1869 | type Stream interface { 1870 | XORKeyStream(dst, src []byte) 1871 | } 1872 | ``` 1873 | 1874 | Определение режима счётчика потока *counter mode (CTR) stream*, который превращает блоки шифрования в поток шифрования, обратите внимание, что шифрование блоков абстрагировано: 1875 | 1876 | ```golang 1877 | // NewCTR returns a Stream that encrypts/decrypts using the given Block in 1878 | // counter mode. The length of iv must be the same as the Block's block size. 1879 | func NewCTR(block Block, iv []byte) Stream 1880 | ``` 1881 | 1882 | Принятое `NewCTR` не только для одного конкретного алгоритма шифрования и исходных данных, но для любой реализации интерфейса `Block` и любой `Stream`. 1883 | Так как он возвращает тип интерфейса, замена шифрование CTR с другими режимами шифрования это локальное изменение. Вызов конструктора должен быть отредактирован, и при этом окружающий код не заметит разницы , так как в результате `Stream`. 1884 | 1885 | [^](#Оглавление) 1886 | 1887 | 1888 | ### Интерфейсы и методы(функции) 1889 | 1890 | [^](#Оглавление) 1891 | 1892 | Так как метод может иметь почти всё, поэтому все можно удовлетворить интерфейсами. 1893 | Один из примеров из пакета `http`, который имеет интерфейс `Handler`. Любой объект реализующий `Handler` может служить для HTTP запросов. 1894 | 1895 | ```golang 1896 | type Handler interface { 1897 | ServeHTTP(ResponseWriter, *Request) 1898 | } 1899 | ``` 1900 | 1901 | Сам интерфейс `ResponseWriter` обеспечивает функции для возврата ответа клиенту. 1902 | Эти функции включают метод `Write`, то `http.ResponseWriter` можно использовать везде как где можно использовать `io.Writer`. `Request(Запрос)` это структура хранящая информацию о запросе от клиента. 1903 | 1904 | Для упрощения, давайте игнорировать POSTs и предположим что HTTP запросы всегда используют GETs; Это упрощение не влияет на способ настройки обработчика *handlers*. 1905 | К примеру следующий код показывает полный обработчик для подсчета количества раз показа данной страницы. 1906 | 1907 | ```golang 1908 | // Simple counter server. 1909 | type Counter struct { 1910 | n int 1911 | } 1912 | 1913 | func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) { 1914 | ctr.n++ 1915 | fmt.Fprintf(w, "counter = %d\n", ctr.n) 1916 | } 1917 | ``` 1918 | 1919 | (Обратите внимание, на то как `Fprintf` печатает в `http.ResponseWriter`.) 1920 | Для справки, следующий код показывает как присоединить сервер к узлу в *URL tree*. 1921 | 1922 | ```golang 1923 | import "net/http" 1924 | ... 1925 | ctr := new(Counter) 1926 | http.Handle("/counter", ctr) 1927 | ``` 1928 | 1929 | Но зачем использовать структуру для `Counter`? Все что нам необходимо - это целое число. 1930 | (Для получателя *receiver* необходим указатель, тогда инкремент будет виден для вызывающего) 1931 | 1932 | ```golang 1933 | // Simpler counter server. 1934 | type Counter int 1935 | 1936 | func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) { 1937 | *ctr++ 1938 | fmt.Fprintf(w, "counter = %d\n", *ctr) 1939 | } 1940 | ``` 1941 | 1942 | Что делать если Ваша программа имеет некое внутреннее состояние и необходимо уведомить что страница была посещена? Необходимо связать веб страницы каналом. 1943 | 1944 | ```golang 1945 | // A channel that sends a notification on each visit. 1946 | // (Probably want the channel to be buffered.) 1947 | type Chan chan *http.Request 1948 | 1949 | func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) { 1950 | ch <- req 1951 | fmt.Fprint(w, "notification sent") 1952 | } 1953 | ``` 1954 | 1955 | Если нам требуется представить на `/args` аргументы использованные для запуска приложения сервера. 1956 | Просто необходимо написать функцию для печати аргументов. 1957 | 1958 | ```golang 1959 | func ArgServer() { 1960 | fmt.Println(os.Args) 1961 | } 1962 | ``` 1963 | 1964 | Как превратить это в HTTP сервер? Мы могли бы сделать метод `ArgServer` некоторого типа значение которого мы игнорируем, но есть более простой путь. 1965 | Так как мы можем определить метод для любого типа, кроме указателя и интерфейса, то мы можем записать метод для функции. 1966 | В пакете `http` есть следующий код: 1967 | 1968 | ```golang 1969 | // The HandlerFunc type is an adapter to allow the use of 1970 | // ordinary functions as HTTP handlers. If f is a function 1971 | // with the appropriate signature, HandlerFunc(f) is a 1972 | // Handler object that calls f. 1973 | type HandlerFunc func(ResponseWriter, *Request) 1974 | 1975 | // ServeHTTP calls f(c, req). 1976 | func (f HandlerFunc) ServeHTTP(w ResponseWriter, req *Request) { 1977 | f(w, req) 1978 | } 1979 | ``` 1980 | 1981 | Это тип `HandlerFunc` с методом `ServeHTTP`, поэтому значения данного типа может служит для запросов HTTP. Посмотрим на реализацию метода: *receiver* это функция, `f`, и метод называется `f`. Это может показаться странным, но это ничем не отличается от работы с каналами и метод бы отсылал на канал. 1982 | 1983 | Для создания `ArgServer` как HTTP сервера, вначале мы изменим корректную сигнатуру. 1984 | 1985 | ```golang 1986 | // Argument server. 1987 | func ArgServer(w http.ResponseWriter, req *http.Request) { 1988 | fmt.Fprintln(w, os.Args) 1989 | } 1990 | ``` 1991 | 1992 | Сейчас, `ArgServer` имеет ту же сигнатуру как `HandlerFunc`, поэтому его можно конвертировать в этот тип для доступа к его методам, просто как сконвертировать `Sequence` в `IntSlice` для доступа к `IntSlice.Sort`. 1993 | Код для настройки лаконичен: 1994 | 1995 | ```golang 1996 | http.Handle("/args", http.HandlerFunc(ArgServer)) 1997 | ``` 1998 | 1999 | Когда кто-то посещает страницу `/args`, обработчик **handler** устанавливает страницу со значением `ArgServer` и типом `HandlerFunc`. 2000 | Сервер HTTP будет вызывать метод `ServeHTTP` данного типа с получателем `ArgServer`, который будет вызывать `ArgServer` через вызов `f(c, req)` внутри `HandlerFunc.ServeHTTP`. 2001 | Вследствие этого аргументы будут отображены. 2002 | 2003 | В этом разделе мы сделали сервер HTTP из структуры, целого числа, канала, и функции, все потому что интерфейсы имеют только набор методов, которые могут быть определены для (почти) любого типа. 2004 | 2005 | [^](#Оглавление) 2006 | 2007 | ## Пустой идентификатор (The blank identifier **_**) 2008 | 2009 | [^](#Оглавление) 2010 | 2011 | Мы уже упоминали пустой идентификатор пару раз, в разделах о циклах `for` `range` и картах `maps`. 2012 | Пустой идентификатор может быть назначен или объявлен для любого типа, значение при этом отбрасывается. 2013 | Это чем то похоже на запись в Unix файл в `/dev/null`: Это значение только на запись, где переменная необходима, но значение не важно. 2014 | Есть дополнительные способы использования. 2015 | 2016 | [^](#Оглавление) 2017 | 2018 | ### Пустой идентификатор в множественном присваивании (**_**) 2019 | 2020 | [^](#Оглавление) 2021 | 2022 | Использование пустого идентификатора в цикле `for` `range` является лишь одним случаем применения в общей картине множественного присваивания. 2023 | 2024 | 2025 | Если требуется множество значений на левой стороне при присваивании, но одно из значений не будет использоваться программой, то используется пустой идентификатор на левой стороне присвоения для того чтобы избежать необходимости в ненужных переменных и создании понимания что значение отброшенное. 2026 | Например, когда вызывается функция возвращающая значение и ошибку, но при этом только ошибка важна, то пустой идентификатор используется для того чтобы отбросить ненужное значение. 2027 | 2028 | ```golang 2029 | if _, err := os.Stat(path); os.IsNotExist(err) { 2030 | fmt.Printf("%s does not exist\n", path) 2031 | } 2032 | ``` 2033 | 2034 | Иногда Вы увидите код в котором отбрасывается ошибка, это ужасная практика. Всегда проверяйте возвращенную ошибку, так как они предоставляются по некой причине. 2035 | 2036 | 2037 | ```golang 2038 | // Bad! This code will crash if path does not exist. 2039 | fi, _ := os.Stat(path) 2040 | if fi.IsDir() { 2041 | fmt.Printf("%s is a directory\n", path) 2042 | } 2043 | ``` 2044 | 2045 | [^](#Оглавление) 2046 | 2047 | ### Неиспользуемое импортирование и значения 2048 | 2049 | [^](#Оглавление) 2050 | 2051 | Ошибкой является неиспользование пакета или объявление переменной без использования. 2052 | Неиспользованный импорт увеличивает программу и делает компиляцию медленнее, в то время как переменная инициализированная но не используется, по крайней мере приводит к пустому вычислению или может является индикатором об ошибке. 2053 | Однако неиспользуемые импорты и переменные возникают, когда программа на стадии активной разработки и удаление их может раздражать, только лишь для того чтобы прошла компиляция и если они снова понадобятся позже. 2054 | Пустые идентификаторы позволяют создать обход(**workaround**). 2055 | 2056 | Это полунаписанная программа имеет два неиспользуемых импорта (`fmt` и `io`) и не используемую переменную (`fd`), и она не проходит компиляцию, но было бы хорошо если бы можно было увидеть, что код корректен. 2057 | 2058 | ```golang 2059 | ///{{code "/doc/progs/eff_unused1.go" `/package/` `$`}} 2060 | package main 2061 | 2062 | import ( 2063 | "fmt" 2064 | "io" 2065 | "log" 2066 | "os" 2067 | ) 2068 | 2069 | func main() { 2070 | fd, err := os.Open("test.go") 2071 | if err != nil { 2072 | log.Fatal(err) 2073 | } 2074 | // TODO: use fd. 2075 | } 2076 | ``` 2077 | 2078 | Для того чтобы избежать жалоб о неиспользуемых импортах, необходимо использовать символ пустого идентификатора для обозначения импортирования пакета. 2079 | Аналогично, можно поступать с неиспользуемой переменной `fd` при использовании пустого идентификатора, что приведёт к избеганию ошибки о неиспользованной переменной. 2080 | Следующая версия программы будет компилироваться. 2081 | 2082 | ```golang 2083 | //{{code "/doc/progs/eff_unused2.go" `/package/` `$`}} 2084 | package main 2085 | 2086 | import ( 2087 | "fmt" 2088 | "io" 2089 | "log" 2090 | "os" 2091 | ) 2092 | 2093 | var _ = fmt.Printf // For debugging; delete when done. 2094 | var _ io.Reader // For debugging; delete when done. 2095 | 2096 | func main() { 2097 | fd, err := os.Open("test.go") 2098 | if err != nil { 2099 | log.Fatal(err) 2100 | } 2101 | // TODO: use fd. 2102 | _ = fd 2103 | } 2104 | ``` 2105 | 2106 | В соответствии с соглашением, глобальное объявление для замалчивания ошибки импорта должно идти сразу за импортированием и должно быть откомментировано, это сделано для того чтобы легко можно было найти и помнить об отчистки позже. 2107 | 2108 | [^](#Оглавление) 2109 | 2110 | ### Импортирование для побочного эффекта (Import for side effect) 2111 | 2112 | [^](#Оглавление) 2113 | 2114 | Неиспользуемые импорты, как например `fmt` и `io` в предыдущем примере, в конечном счете должны быть удалены: пустое задание должно определять что код в процессе разработки. 2115 | Но иногда, используется импортирование пакета только для создания побочного влияния, без какого либо явного использования. 2116 | К примеру, для функции `init` в пакете [net/http/pprof](https://golang.org/pkg/net/http/pprof/) регистрирует HTTP обработчики для обеспечения отладочной информацией. 2117 | Он имеет экспортированный API, но большинству клиентов необходима только регистрация обработчиков и получение доступа к данным через веб-страницу. 2118 | Только для импортирования пакета с этим побочным эффектом, переименовывают пакет в пустой идентификатор: 2119 | 2120 | ```golang 2121 | import _ "net/http/pprof" 2122 | ``` 2123 | 2124 | Эта форма импортирования означает, что данный пакет импортируется для данного побочного эффекта, потому что нет другой возможности использовать пакет: в этот файл, не имеет имени. (Если же он имеет и мы не используем это имя, то компилятор отменит программу.) 2125 | 2126 | [^](#Оглавление) 2127 | 2128 | 2129 | ### Проверка интерфейса (Interface checks) 2130 | 2131 | [^](#Оглавление) 2132 | 2133 | Как мы видели ранее в разделе об интерфейсах, нет необходимости в объявлении что тип реализует определенный интерфейс. 2134 | Вместо этого, тип реализует интерфейс только путем реализации методов интерфейса. 2135 | На практике, большинство преобразований интерфейсов статично и поэтому проверяется во время компиляции. 2136 | 2137 | 2138 | К примеру, передавая `*os.File` в функцию ожидающая `io.Reader` не будет скомпилировано, так как `*os.File` не реализует интерфейс `io.Reader`. 2139 | 2140 | 2141 | Хотя все же некоторые проверки интерфейсов происходят во время выполнения. 2142 | Один из примеров в пакете [encoding/json](https://golang.org/pkg/encoding/json/), который определяет интерфейс [Marshaler](https://golang.org/pkg/encoding/json/#Marshaler). Когда **JSON encoder** принимает значение, которое реализует этот интерфейс, *encoder* вызывает функцию упаковщик значений для преобразования в JSON, в отличии от стандартного преобразования. 2143 | 2144 | *Encoder* проверяет эти свойства во время работы: 2145 | 2146 | ```golang 2147 | m, ok := val.(json.Marshaler) 2148 | ``` 2149 | 2150 | Если необходимо только запросить тип реализуемого интерфейса без использования самого интерфейса, то это часть проверки ошибок, используйте пустой идентификатор для игнорирования защиты типов: 2151 | 2152 | ```golang 2153 | if _, ok := val.(json.Marshaler); ok { 2154 | fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val) 2155 | } 2156 | ``` 2157 | 2158 | Одна из ситуаций применения это когда необходимо гарантировать в рамках пакета что данный тип реализует интерфейс. 2159 | Если взглянуть на пример [json.RawMessage](https://golang.org/pkg/encoding/json/#RawMessage), где необходима пользовательское представление в формате JSON, он должен реализовывать `json.Marshaler`, но отсутствует статическое преобразование для автоматической проверки компилятором. 2160 | Если определенный тип не будет реализовывать интерфейс, то *JSON encoder* будет все же работать, но без пользовательской реализации. 2161 | Для гарантирования корректной реализации, в пакете можете использовать пустой идентификатор для глобальной декларации: 2162 | 2163 | ```golang 2164 | var _ json.Marshaler = (*RawMessage)(nil) 2165 | ``` 2166 | 2167 | в этой деклорации, присвоение с конвертацией `*RawMessage` к `Marshaler` требует чтобы, `*RawMessage` реализовывал `Marshaler` и данная проверка будет производиться во время компиляции. 2168 | В случаи если интерфейс `json.Marshaler`, этот пакет не будет компилироваться и мы будем знать об обновлении. 2169 | 2170 | Использование пустого идентификатора в данном случае является индикатором о проверки типов, и при этом не создается переменной. 2171 | Не используйте этот подход для проверки каждого типа. 2172 | В соответствии с соглашением, такое объявление используется только когда отсутствует статическая конвертация уже существующая в коде, и является редким событием. 2173 | 2174 | 2175 | 2176 | [^](#Оглавление) 2177 | 2178 | ## Вложение (Embedding) 2179 | 2180 | [^](#Оглавление) 2181 | 2182 | Язык Go не поддерживает типичное управление типов подклассов, но он имеет возможность "заимствовать" части реализации с помощью типа *вложения* структуры или интерфейса. 2183 | 2184 | Вложение интерфейса необычно простое. 2185 | Мы уже упоминали об интерфейсах `io.Reader` and `io.Writer` ранее, вот их определение. 2186 | 2187 | ```golang 2188 | type Reader interface { 2189 | Read(p []byte) (n int, err error) 2190 | } 2191 | 2192 | type Writer interface { 2193 | Write(p []byte) (n int, err error) 2194 | } 2195 | ``` 2196 | 2197 | Пакет `io` также экспортирует несколько других интерфейсов, которые определяют объекты, которые могут реализовывать несколько таких методов. 2198 | К примеру, `io.ReadWriter` содержит оба интерфейса `Read` и `Write`. 2199 | Мы может указать `io.ReadWriter` перечислением двух методов в явном виде, но проще и более запомяющим будет встраивание двух интерфейсов в одну новую форму, вот так: 2200 | 2201 | ```golang 2202 | // ReadWriter is the interface that combines the Reader and Writer interfaces. 2203 | type ReadWriter interface { 2204 | Reader 2205 | Writer 2206 | } 2207 | ``` 2208 | 2209 | Это выглядит следующим образом: `ReadWriter` может делать все что делает `Reader` **и** что делает `Writer`. Это объединение встраивания интерфейсов (которые не имеют пересечений в методах). 2210 | Только интерфейсы могут встраивать интерфейсы. 2211 | 2212 | Аналогичная идея используется для структур, но с большим количеством последствий. Пакет `bufio` имеет две структуры типов - `bufio.Reader` и `bufio.Writer`, каждая из которых реализует аналогичные интерфейсы как в пакете `io`. И `bufio` также реализует буферизованное чтение/запись, которое объединяет чтение и запись в одну структуру с использованием вложения: этот список типов структур, но не давая имена полям. 2213 | 2214 | ```golang 2215 | // ReadWriter stores pointers to a Reader and a Writer. 2216 | // It implements io.ReadWriter. 2217 | type ReadWriter struct { 2218 | *Reader // *bufio.Reader 2219 | *Writer // *bufio.Writer 2220 | } 2221 | ``` 2222 | 2223 | Вложение указателей элементов в структуры и конечно должно быть инициализировано необходимой структурой до его использования. 2224 | Структура `ReadWriter` может быть записана так: 2225 | 2226 | ```golang 2227 | type ReadWriter struct { 2228 | reader *Reader 2229 | writer *Writer 2230 | } 2231 | ``` 2232 | 2233 | **TODO** 2234 | but then to promote the methods of the fields and to 2235 | satisfy the `io` interfaces, we would also need 2236 | to provide forwarding methods, like this: 2237 | **-** 2238 | 2239 | 2240 | ```golang 2241 | func (rw *ReadWriter) Read(p []byte) (n int, err error) { 2242 | return rw.reader.Read(p) 2243 | } 2244 | ``` 2245 | 2246 | Для непосредственного вложения структур, мы должны избегать эту бухгалтерию. 2247 | Метод вложенного типа приходит свободно, что означает что `bufio.ReadWriter` имеет не только его методы `bufio.Reader` и `bufio.Writer`, а также удовлетворяет всем трем интерфейсам: 2248 | * `io.Reader`, 2249 | * `io.Writer`, и 2250 | * `io.ReadWriter`. 2251 | 2252 | 2253 | Это важное отличие вложения от подклассов. Когда мы вкладываем тип, методы этого типа становятся методами внешнего типа, но для получателя они вызываются как встроенные типы, а не внешние. 2254 | В нашем примере, когда метод `Read` из `bufio.ReadWriter` вызывается, он и вызываются также как описано выше; получатель поля `reader` из `ReadWriter`, является самим `ReadWriter`. 2255 | 2256 | Вложение может быть простым и удобным. 2257 | Этот пример показывает вложение поля рядом с именованным полем. 2258 | 2259 | ```golang 2260 | type Job struct { 2261 | Command string 2262 | *log.Logger 2263 | } 2264 | ``` 2265 | 2266 | Тип `Job` сейчас имеет `Log`, `Logf` и другие методы `*log.Logger`. 2267 | Мы могли бы дать имя для `Logger`, конечно же, но в этом нет необходимости. И сейчас, мы можем логировать `Job`: 2268 | 2269 | ```golang 2270 | job.Log("starting now...") 2271 | ``` 2272 | 2273 | Регулярное поле `Logger` в структуре `Job`, поэтому мы можем инициализировать его как обычно внутри конструктора `Job`, вот так: 2274 | 2275 | ```golang 2276 | func NewJob(command string, logger *log.Logger) *Job { 2277 | return &Job{command, logger} 2278 | } 2279 | ``` 2280 | 2281 | или с помощью составных литералов: 2282 | 2283 | ```golang 2284 | job := &Job{command, log.New(os.Stderr, "Job: ", log.Ldate)} 2285 | ``` 2286 | 2287 | Если нам необходимо обратиться непосредственно к вложенному полю, имени типа поля, игнорируя пакетный классификатор, как к имени поля, как это сделано в методе `Read` в нашей структуре `ReaderWriter`. 2288 | При этом нам необходим доступ к ` * log.Logger` в `Job` переменной `job`, мы можем написать `job.Logger`, что полезно если мы хотим уточнить методы `Logger`. 2289 | 2290 | ```golang 2291 | func (job *Job) Logf(format string, args ...interface{}) { 2292 | job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args...)) 2293 | } 2294 | ``` 2295 | 2296 | Вложение типов создает проблему конфликта имен, но правила для их решения просты. 2297 | 2298 | 2299 | 2300 | Первое, поля или метод `X` скрывает любой иной элемент `X` в более глубокой части вложенного типа. 2301 | Если `log.Logger` содержит поле или метод под названием `Command`, то поле `Command` в `Job` будет преобладать над ним. 2302 | 2303 | 2304 | Во-вторых, если есть одинаковые имена на том же уровне вложенности, это как правило ошибка и было бы ошибочно вставлять `log.Logger`, если структура `Job` имеет другое вложенное поле или метод с названием `Logger`. 2305 | Однако, если дублированные имена никогда не встречается в программе вне определённого типа, то это нормально. 2306 | Это защищает от изменения типов вложенности за его пределами; и это не проблема, если добавлено поле вступающее в конфликт с другим полем в другом подтипе, если ни одно из полей не используется. 2307 | 2308 | [^](#Оглавление) 2309 | 2310 | ## Согласованность, параллельная обработка, параллельное выполнение (Concurrency) 2311 | 2312 | [^](#Оглавление) 2313 | 2314 | ### Распределение памяти по сообщениям (Share by communicating) 2315 | 2316 | [^](#Оглавление) 2317 | 2318 | Параллельное программирование является большой темой и здесь будет рассматриваться только специфичное для языка Go. 2319 | 2320 | 2321 | Параллельное программирование во многих средах затруднено для корректной реализации доступа к общим переменным. 2322 | 2323 | В языке Go поддерживается другой подход, в котором общие переменные *shared values* передаются через каналы, по сути, никогда активно не распределяется по исполняемым потокам. 2324 | Только одна го-рутина(**goroutine**) имеет доступ к переменной в любой момент. 2325 | Перенос данных не происходит по конструкции языка. 2326 | Для того чтобы способствовать данному стилю мышления используется лозунг: 2327 | 2328 | 2329 | > Do not communicate by sharing memory; instead, share memory by communicating. 2330 | 2331 | > Не общайтесь с распределением памяти; Вместо того чтобы распределять память по коммуникациям. 2332 | 2333 | 2334 | Это дальновидный подход. К примеру, наилучшим образом подсчет ссылок можно производить установкой мютексов(**mutex**) вокруг целого переменной. 2335 | Но это высокоуровневый подход, использование каналов для контроля доступа является более простым и корректным для программ. 2336 | 2337 | 2338 | Один из способов думать об этой модели как для типичных однопоточных программ запущенных на одном процессоре CPU. И нет необходимости в синхронизации примитивов. 2339 | Для запуска следующего экземпляра, нет необходимости в синхронизации. Сейчас рассмотрим два способа коммуникации; Если коммуникация синхронна, то все также не требуется дополнительной синхронизации. К примеру, *Unix pipelines* великолепно используют эту модель. Хотя подход языка Go для организации параллельных процессов берет начало в **Hoare's Communicating Sequential Processes (CSP)**, он также может рассматриваться как обобщение безопасности типов Unix pipes. 2340 | 2341 | 2342 | [^](#Оглавление) 2343 | 2344 | ### Го-рутины (Goroutines) 2345 | 2346 | [^](#Оглавление) 2347 | 2348 | 2349 | Они называются Го-рутины, потому что существующие термины потоки, корутины, процессы и так далее передают неточную коннотацию. 2350 | **Го-рутины** имеют простую модель: это функция выполняющаяся параллельно с другими го-рутинами в одном адресном пространстве. Они легковесны стоящие чуть больше чем выделение пространства в стэке. Они дешевы, и растут по мере необходимости путем выделения или освобождения в куче. 2351 | 2352 | Горутины распределяются на несколько потоков OS, и если один заблокируются, например из-за ожидания I/O, другие продолжат работу. Их дизайн скрывает много сложностей по создание потоков и их управлению. 2353 | 2354 | 2355 | Префикс `go` у функции или метода запускает новую горутину. 2356 | Когда вызов закончен, горутина выходит, молча. (Этот эффект похож на команду Unix с нотацией `&` означающая запуск команды в фоновом режиме.) 2357 | 2358 | ```golang 2359 | go list.Sort() // run list.Sort concurrently; don't wait for it. 2360 | ``` 2361 | 2362 | Встроенные функции могут быть удобны для вызова горутин. 2363 | 2364 | ```golang 2365 | func Announce(message string, delay time.Duration) { 2366 | go func() { 2367 | time.Sleep(delay) 2368 | fmt.Println(message) 2369 | }() // Note the parentheses - must call the function. 2370 | } 2371 | ``` 2372 | 2373 | В языке Go, встроенные функции закрываемые и их реализация гарантирует что ссылаемые переменные будут жить до тех пор пока функция активна. 2374 | 2375 | Эти примеры не очень практичны, так как функции не имеют сигнализировать о своем завершении. Для этого у нас есть каналы. 2376 | 2377 | [^](#Оглавление) 2378 | 2379 | ### Каналы (Channels) 2380 | 2381 | [^](#Оглавление) 2382 | 2383 | Каналы, как и карты(map) выделяются в памяти с помощью `make` и полученное значение является ссылкой на изначальную структуру данных. 2384 | Если задан необязательный целый параметр, то он указывает на размер буфера в канале. 2385 | По умолчанию, значение нулевое, как для небуферезованного или синхронного канала. 2386 | 2387 | ```golang 2388 | ci := make(chan int) // unbuffered channel of integers 2389 | cj := make(chan int, 0) // unbuffered channel of integers 2390 | cs := make(chan *os.File, 100) // buffered channel of pointers to Files 2391 | ``` 2392 | 2393 | Небуферезованные каналы гарантируют, что обмен значениями будет синхронным между двумя горутинами в известном состоянии. 2394 | 2395 | 2396 | Есть много хороших идиом использования каналов. Вот один с которого мы начнем. 2397 | В предыдущем разделе мы запускали сортировку в фоне. Канал может помочь отследить завершение горутины с сортировкой. 2398 | 2399 | ```golang 2400 | c := make(chan int) // Allocate a channel. 2401 | // Start the sort in a goroutine; when it completes, signal on the channel. 2402 | go func() { 2403 | list.Sort() 2404 | c <- 1 // Send a signal; value does not matter. 2405 | }() 2406 | doSomethingForAWhile() 2407 | <-c // Wait for sort to finish; discard sent value. 2408 | ``` 2409 | 2410 | Получатель всегда блокируется до тех пор пока данные не получит получатель. 2411 | Если канал не буферизованный, отсылающий блокируется до тех пор пока получатель не получит данные. 2412 | Если канал буферизованный, то отсылающий блокируется только тогда когда значение копируется в буфер; если буфер полон, то будет ожидать до тех пор пока получатель не получит значение. 2413 | 2414 | **TODO** 2415 | A buffered channel can be used like a semaphore, for instance to 2416 | limit throughput. In this example, incoming requests are passed 2417 | to `handle`, which sends a value into the channel, processes 2418 | the request, and then receives a value from the channel 2419 | to ready the "semaphore" for the next consumer. 2420 | The capacity of the channel buffer limits the number of 2421 | simultaneous calls to `process`. 2422 | **-** 2423 | 2424 | ```golang 2425 | var sem = make(chan int, MaxOutstanding) 2426 | 2427 | func handle(r *Request) { 2428 | sem <- 1 // Wait for active queue to drain. 2429 | process(r) // May take a long time. 2430 | <-sem // Done; enable next request to run. 2431 | } 2432 | 2433 | func Serve(queue chan *Request) { 2434 | for { 2435 | req := <-queue 2436 | go handle(req) // Don't wait for handle to finish. 2437 | } 2438 | } 2439 | ``` 2440 | 2441 | **TODO** 2442 | Once `MaxOutstanding` handlers are executing `process`, 2443 | any more will block trying to send into the filled channel buffer, 2444 | until one of the existing handlers finishes and receives from the buffer. 2445 | **-** 2446 | 2447 | 2448 | Данный дизайн имеет проблемы: `Serve` создает новую горутину для каждого входящего запроса, при этом будет запущено не более `MaxOutstanding` в один момент. 2449 | Если количество запросов увеличивается слишком быстро, то как результат, программа может потребовать бесконечное количество ресурсов. 2450 | Мы можем решить это изменением `Serve` используя изменения количества порождаемых горутин. 2451 | Вот очевидное решение, но будьте осторожны, так как оно имеет ошибку, которую позже исправим: 2452 | 2453 | ```golang 2454 | func Serve(queue chan *Request) { 2455 | for req := range queue { 2456 | sem <- 1 2457 | go func() { 2458 | process(req) // Buggy; see explanation below. 2459 | <-sem 2460 | }() 2461 | } 2462 | } 2463 | ``` 2464 | 2465 | Ошибка в том, что в языке Go цикл `for`, цикл переменной повторно используется для каждой итерации, так что переменные `req` разделяется по всем горутинам. 2466 | Это не то что мы хотим. 2467 | Нам нужно убедиться, что `req` является уникальной для каждой горутиной. 2468 | Вот один из способов, передавать значение `req` как в качестве аргумента для закрытии горутины: 2469 | 2470 | ```golang 2471 | func Serve(queue chan *Request) { 2472 | for req := range queue { 2473 | sem <- 1 2474 | go func(req *Request) { 2475 | process(req) 2476 | <-sem 2477 | }(req) 2478 | } 2479 | } 2480 | ``` 2481 | 2482 | Сравнивая эту версию с предыдущей можно увидеть разницу в том как объявляется запуск и закрытие. 2483 | Другое решение заключается в том что создается новая переменная с тем же именем, как в примере: 2484 | 2485 | ```golang 2486 | func Serve(queue chan *Request) { 2487 | for req := range queue { 2488 | req := req // Create new instance of req for the goroutine. 2489 | sem <- 1 2490 | go func() { 2491 | process(req) 2492 | <-sem 2493 | }() 2494 | } 2495 | } 2496 | ``` 2497 | 2498 | Может кажется странным, писать: 2499 | 2500 | ```golang 2501 | req := req 2502 | ``` 2503 | 2504 | Но это допустимо и идиоматично делать это. 2505 | Вы получаете новую переменную с тем же именем, намеренно затеняя переменную цикла локально, но уникальный для каждой горутины. 2506 | 2507 | Возвращаясь к общей проблеме написания сервера, иной подход для управления ресурсами начинается с фиксации числа обработчиков `handle` горутин читающих из канала запросов. 2508 | Ограничение количества горутин количеством одновременных вызовов к `process`. 2509 | 2510 | Функция `Serve` также принимает канал, на который посылается об окончании; после запуска горутины блокируют получающих в этот канал. 2511 | 2512 | 2513 | ```golang 2514 | func handle(queue chan *Request) { 2515 | for r := range queue { 2516 | process(r) 2517 | } 2518 | } 2519 | 2520 | func Serve(clientRequests chan *Request, quit chan bool) { 2521 | // Start handlers 2522 | for i := 0; i < MaxOutstanding; i++ { 2523 | go handle(clientRequests) 2524 | } 2525 | <-quit // Wait to be told to exit. 2526 | } 2527 | ``` 2528 | 2529 | [^](#Оглавление) 2530 | 2531 | ### Канал каналов (Channels of channels) 2532 | 2533 | [^](#Оглавление) 2534 | 2535 | Одно из важных свойств Go в том что каналы это переменная, а значит аллоцированы и могут передаваться как любой другой элемент. Одно из использований данной свойства в реализации безопасного и **параллельного демультиплексирования**. 2536 | 2537 | В примере из предыдущего раздела, `handle` был идеальным обработчиком для запросов, но он не определял тип обработки. Если тип включен в канал, на который отвечать, то каждый клиент может предоставить собственный путь для ответа. Вот схематичное определение типа `Request`. 2538 | 2539 | ```golang 2540 | type Request struct { 2541 | args []int 2542 | f func([]int) int 2543 | resultChan chan int 2544 | } 2545 | ``` 2546 | 2547 | Клиент предоставляет функцию и ее аргументы, а также канал внутри объекта запроса, не который будет получен ответ. 2548 | 2549 | ```golang 2550 | func sum(a []int) (s int) { 2551 | for _, v := range a { 2552 | s += v 2553 | } 2554 | return 2555 | } 2556 | 2557 | request := &Request{[]int{3, 4, 5}, sum, make(chan int)} 2558 | // Send request 2559 | clientRequests <- request 2560 | // Wait for response. 2561 | fmt.Printf("answer: %d\n", <-request.resultChan) 2562 | ``` 2563 | 2564 | На стороне сервера, функция обработчик это единственное что меняется. 2565 | 2566 | ```golang 2567 | func handle(queue chan *Request) { 2568 | for req := range queue { 2569 | req.resultChan <- req.f(req.args) 2570 | } 2571 | } 2572 | ``` 2573 | 2574 | Этот пример является примером основой для ограничения скорости, параллелизма, неблокирующей RPC системы и без использования мютекса. 2575 | 2576 | 2577 | [^](#Оглавление) 2578 | 2579 | ### Параллелизм (Parallelization) 2580 | 2581 | [^](#Оглавление) 2582 | 2583 | Другой пример использования этих идей в расчёте на нескольких ядрах CPU. Если расчет можно разбить на кусочки выполняющиеся независимо, то это можно распараллелить с каналами сигнализирующие, когда отдельный кусочек закончил свою работу. 2584 | 2585 | К примеру, у нас есть дорогая операция выполнения на векторе элементов и эти операции могут выполнять независимо, то вот идеализированный пример. 2586 | 2587 | ```golang 2588 | type Vector []float64 2589 | 2590 | // Apply the operation to v[i], v[i+1] ... up to v[n-1]. 2591 | func (v Vector) DoSome(i, n int, u Vector, c chan int) { 2592 | for ; i < n; i++ { 2593 | v[i] += u.Op(v[i]) 2594 | } 2595 | c <- 1 // signal that this piece is done 2596 | } 2597 | ``` 2598 | 2599 | Вы выполняем кусочки независимо в цикле, по одному CPU на кусочек. 2600 | Они могут закончить в любом порядке, но это не важно; мы только считаем количество сигналов окончания по каналу после запуска всех горутин. 2601 | 2602 | ```golang 2603 | const numCPU = 4 // number of CPU cores 2604 | 2605 | func (v Vector) DoAll(u Vector) { 2606 | c := make(chan int, numCPU) // Buffering optional but sensible. 2607 | for i := 0; i < numCPU; i++ { 2608 | go v.DoSome(i*len(v)/numCPU, (i+1)*len(v)/numCPU, u, c) 2609 | } 2610 | // Drain the channel. 2611 | for i := 0; i < numCPU; i++ { 2612 | <-c // wait for one task to complete 2613 | } 2614 | // All done. 2615 | } 2616 | ``` 2617 | 2618 | Вместо того, чтобы создать постоянное значение для numCPU, мы можем задать во время выполнения необходимое значение. 2619 | Функция [runtime.NumCPU](https://golang.org/pkg/runtime/#NumCPU) возвращает количество ядер CPU в машине, тогда мы должны записать: 2620 | 2621 | ```golang 2622 | var numCPU = runtime.NumCPU() 2623 | ``` 2624 | 2625 | Есть также такая функция [runtime.GOMAXPROCS](https://golang.org/pkg/runtime/#GOMAXPROCS), которая возвращает заданное пользователем количество ядер, которая программа Go может использовать. 2626 | По умолчанию значение `runtime.NumCPU`, но может быть переопределен путем установки в среде с тем же именем или вызовом функции с положительным числом. 2627 | Вызов с нулевым значением запрашивает значение. 2628 | Поэтому если мы хотим выполнить запрос ресурсов пользователя, мы должны написать 2629 | 2630 | ```golang 2631 | var numCPU = runtime.GOMAXPROCS(0) 2632 | ``` 2633 | 2634 | Будьте уверены, чтобы не путать идеи параллельно-структурированной(**concurrency—structuring**) программы как независимо исполняемых компонентов и параллельно-выполняемые вычисления(**parallelism—executing**) для эффективности на нескольких процессорах. 2635 | Хотя особенности *concurrency* в языке Go могут решить некоторые проблемы легко с использованием структур параллельного вычисления, Go является *concurrent* языком, не параллельным и не все проблемы параллелизма подходят модели Go. 2636 | Для обсуждения различий, смотрите [следующий блог](https://blog.golang.org/concurrency-is-not-parallelism). 2637 | 2638 | 2639 | [^](#Оглавление) 2640 | 2641 | ### Текущий буфер (A leaky buffer) 2642 | 2643 | [^](#Оглавление) 2644 | 2645 | Инструменты конкарентси программирования позволяют для неконкаренси идей быть нагляднее. Вот пример из пакета RPC. Цикл клиента горутины принимает данные из нескольких источников, возможно из сети. Для того чтобы избежать выделения и освобождения буферов, он пустой список и использует буферизованный канал для его представления. Если канал пуст, то выделяется новый буфер. После того, как буфер готов, он высылает на сервер на `serverChan`. 2646 | 2647 | ```golang 2648 | var freeList = make(chan *Buffer, 100) 2649 | var serverChan = make(chan *Buffer) 2650 | 2651 | func client() { 2652 | for { 2653 | var b *Buffer 2654 | // Grab a buffer if available; allocate if not. 2655 | select { 2656 | case b = <-freeList: 2657 | // Got one; nothing more to do. 2658 | default: 2659 | // None free, so allocate a new one. 2660 | b = new(Buffer) 2661 | } 2662 | load(b) // Read next message from the net. 2663 | serverChan <- b // Send to server. 2664 | } 2665 | } 2666 | ``` 2667 | 2668 | Цикл сервера принимает каждое сообщение из клиента, обрабатывает его и возвращает буфер на пустое список. 2669 | 2670 | ```golang 2671 | func server() { 2672 | for { 2673 | b := <-serverChan // Wait for work. 2674 | process(b) 2675 | // Reuse buffer if there's room. 2676 | select { 2677 | case freeList <- b: 2678 | // Buffer on free list; nothing more to do. 2679 | default: 2680 | // Free list full, just carry on. 2681 | } 2682 | } 2683 | } 2684 | ``` 2685 | 2686 | Клиент пытается получить буфер из `freeList`; если ни один не доступен, он выделяется новые. 2687 | Посылка от сервера в `freeList` подставляется назад `b` в свободный список, если список не полон, и в этом случаи буфер сбрасывается, чтобы утилизироваться сборщиком мусора. 2688 | 2689 | (Положение `default` в `select` выполняется когда другие условия не готовы, это означает что `selects` никогда не блокируется.) 2690 | Эта реализация устроена как утекающее ведро со свободным списком всего в несколько строк, опираясь на буферизованный канал и сборщик мусора. 2691 | 2692 | 2693 | [^](#Оглавление) 2694 | 2695 | ## Ошибки (Errors) 2696 | 2697 | [^](#Оглавление) 2698 | 2699 | Библиотеки подпрограмм часто должны возвращать какой-то признак ошибки для вызывающего. 2700 | Как уже упоминалось ранее, множественные значения в Go могут легко возвращать подробное описание ошибки вместе с нормальным возвращением значения. 2701 | Использование данной особенности Go для возвращения детального описания ошибки является хорошим стилем. 2702 | Например, как вы увидите `os.Open` при неудаче не просто возвращает указатель на `nil`, он также возвращает значение ошибки, описывающей что пошло не так. 2703 | 2704 | В соответствии с соглашением, ошибки имеют тип `error`, простой встроенный интерфейс. 2705 | 2706 | ```golang 2707 | type error interface { 2708 | Error() string 2709 | } 2710 | ``` 2711 | 2712 | Библиотека записи может реализовать данный интерфейс с богатой моделью покрытия, что позволяет не только увидеть ошибку, но и также обеспечить некий контекст. 2713 | Как уже отмечалось, наряду с обычным `*os.File` возвращением значения, `os.Open` также возвращает значение ошибки. 2714 | Если файл будет успешно открыт то значение ошибки будет `nil`, но когда есть проблема, то будет передана `os.PathError`: 2715 | 2716 | ```golang 2717 | // PathError records an error and the operation and 2718 | // file path that caused it. 2719 | type PathError struct { 2720 | Op string // "open", "unlink", etc. 2721 | Path string // The associated file. 2722 | Err error // Returned by the system call. 2723 | } 2724 | 2725 | func (e * PathError) Error() string { 2726 | return e.Op + " " + e.Path + ": " + e.Err.Error() 2727 | } 2728 | ``` 2729 | 2730 | Ошибка `Error` в `PathError` сгенерирует строку как эта: 2731 | 2732 | ```command 2733 | open /etc/passwx: no such file or directory 2734 | ``` 2735 | 2736 | Такая ошибка, которая включает имя проблемного файла, операции, ошибка операционной системы и т.д., полезная, даже если напечатать далеко от вызова; это гораздо полезнее, что просто запись "файл или папка не найдены". 2737 | 2738 | 2739 | Если это возможно, то строка ошибки должна определять происхождение, например, при наличии префикса имен операции или пакета, который вызвал ошибку. 2740 | Например, в пакете `image` при ошибки декодирования представлена от неизвестного формата: "image: unknown format". 2741 | 2742 | 2743 | Вызывающие, которые заботятся о точности ошибки, могут использовать переключатель типов *type switch* или *type assertion* для того специфицирования ошибок и получения большего количества деталей. Для `PathErrors` это означает включения изучения внутренних полей `Err` для восстановления причины отказа. 2744 | 2745 | 2746 | ```golang 2747 | for try := 0; try < 2; try++ { 2748 | file, err = os.Create(filename) 2749 | if err == nil { 2750 | return 2751 | } 2752 | if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC { 2753 | deleteTempFiles() // Recover some space. 2754 | continue 2755 | } 2756 | return 2757 | } 2758 | ``` 2759 | 2760 | Здесь вторая проверка `if` это ещё другой тип *type assertion*. 2761 | Если это не удается, то `ok` будет *false* и значение `e` будет `nil`. 2762 | Если это удается, то `ok` будет *true*, который означает, что имеет тип `*os.PathError`, и затем когда `e`, который мы можем рассматривать для более подробной информации об ошибке. 2763 | 2764 | 2765 | [^](#Оглавление) 2766 | 2767 | ### Паника (Panic) 2768 | 2769 | [^](#Оглавление) 2770 | 2771 | 2772 | Обычный способ сообщить об ошибке к абоненту, это вернуть `error`, в качестве дополнительного возвращаемого значения. Канонический метод `Read` является хорошим примером, который возвращает количество байт и `error`. Но что если ошибка невосстановимая? Иногда программа просто не может продолжать работать. 2773 | 2774 | Для этого есть встроенная функция `panic`, которая создаёт ошибку во время выполнения программы, которая остановит программу (но смотрите следующий раздел). 2775 | Функция принимает один аргумент произвольного типа, часто используется строка для вывода на печать, так как программа умирает. Это также путь указать, что произошло что-то невозможное, как например выход из бесконечного цикла. 2776 | 2777 | 2778 | ```golang 2779 | // A toy implementation of cube root using Newton's method. 2780 | func CubeRoot(x float64) float64 { 2781 | z := x/3 // Arbitrary initial value 2782 | for i := 0; i < 1e6; i++ { 2783 | prevz := z 2784 | z -= (z*z*z-x) / (3*z*z) 2785 | if veryClose(z, prevz) { 2786 | return z 2787 | } 2788 | } 2789 | // A million iterations has not converged; something is wrong. 2790 | panic(fmt.Sprintf("CubeRoot(%g) did not converge", x)) 2791 | } 2792 | ``` 2793 | 2794 | 2795 | Это всего лишь пример и в реальных библиотечных функциях следует избегать `panic`. Если проблема может быть замаскирована или работать по другому алгоритму, то это всегда лучше, чтобы программа продолжала работать, а не выключать её. Один из возможных примеров: если библиотека действительно не может это сделать, то это причина паниковать. 2796 | 2797 | ```golang 2798 | var user = os.Getenv("USER") 2799 | 2800 | func init() { 2801 | if user == "" { 2802 | panic("no value for $USER") 2803 | } 2804 | } 2805 | ``` 2806 | 2807 | [^](#Оглавление) 2808 | 2809 | ### Восстановление (Recover) 2810 | 2811 | [^](#Оглавление) 2812 | 2813 | Когда вызывается `panic`, в том числе не явно при наличии ошибок во время выполнения программы, к примеру когда происходит обращение к срезу за его пределами или при некорректной работы с типами, происходит немедленное прекращение работы функции и начинается раскручивание стека горутин, запуск всех отсроченных функций *defer*. 2814 | Если раскручивание достигает вершины стека, то программа умирает. Тем не менее, можно использовать встроенную функцию `recover`, чтобы восстановить контроль над горутинами и возобновить нормальное выполнение. 2815 | 2816 | Вызов `recover` останавливает раскручивание и возвращает аргументы в `panic`. Поскольку только код, который работает во время раскручивания внутри отложенных функций, `recover` полезно устанавливать внутри отложенных функций. 2817 | 2818 | Одно `recover` приложение выключает недопустимые горутины изнутри, то сервер без выключения других запущенных горутин. 2819 | 2820 | ```golang 2821 | func server(workChan <-chan *Work) { 2822 | for work := range workChan { 2823 | go safelyDo(work) 2824 | } 2825 | } 2826 | 2827 | func safelyDo(work *Work) { 2828 | defer func() { 2829 | if err := recover(); err != nil { 2830 | log.Println("work failed:", err) 2831 | } 2832 | }() 2833 | do(work) 2834 | } 2835 | ``` 2836 | 2837 | В этом примере, если будет вызвана паника в `do(work)`, то результат будет залогирован и горутина закончит работу без препятствия выполнения для других. Там нет необходимости делать что то дополнительно при отсроченном выполнении; вызывание `recover` обрабатывает состояние полностью. 2838 | 2839 | 2840 | Так как `recover` всегда возвращает `nil`, если вызывалась из отложенной функции, отложенный код может вызывать библиотеку функций, которые сами используют `panic` и `recover` без сбоя. 2841 | К примеру, отложенная функция в `safelyDo` может вызвать функцию логирования до вызова `recover`, и этот код логирования будет работать не зависимо от состоянии паники. 2842 | 2843 | 2844 | С помощью данного шаблона восстановления , функция `do` (и все что он вызывает) может выйти из любой ситуации вызовом `panic`. 2845 | Мы можем использовать данную идею для простой обработки ошибок в сложной программе. Давайте взглянем на идеализированную версию пакета `regexp`, которая сообщает об ошибке с помощью `panic` с типом локальной ошибки. Это определение `Error`, в методе `error` и функции `Compile`. 2846 | 2847 | ```golang 2848 | // Error is the type of a parse error; it satisfies the error interface. 2849 | type Error string 2850 | func (e Error) Error() string { 2851 | return string(e) 2852 | } 2853 | 2854 | // error is a method of *Regexp that reports parsing errors by 2855 | // panicking with an Error. 2856 | func (regexp *Regexp) error(err string) { 2857 | panic(Error(err)) 2858 | } 2859 | 2860 | // Compile returns a parsed representation of the regular expression. 2861 | func Compile(str string) (regexp *Regexp, err error) { 2862 | regexp = new(Regexp) 2863 | // doParse will panic if there is a parse error. 2864 | defer func() { 2865 | if e := recover(); e != nil { 2866 | regexp = nil // Clear return value. 2867 | err = e.(Error) // Will re-panic if not a parse error. 2868 | } 2869 | }() 2870 | return regexp.doParse(str), nil 2871 | } 2872 | ``` 2873 | 2874 | Если происходит паника в `doParse`, то блок восстановления будет устанавливать значение `nil` отложенная функция может модифицировать имя возвращаемых значений. 2875 | Затем он проверяет, значение `err`, синтаксическая ошибка имеет локальный тип `Error`. Если этого не произойдет, то это приведет к ошибке во время выполнения и будет раскручивать стек. 2876 | Эта проверка означает что если происходит что-то неожиданное, как выход за пределы индексирования, код будет прерван даже при использовании `panic` и `recover` для обработки ошибок. 2877 | 2878 | 2879 | При наличии обработчика ошибок, метод `error` (потому его метод связан с типом, это хорошо, так как он имеет то же имя что встроенный тип `error`) позволяет легко сообщить о наличии синтаксической ошибки, не беспокоясь о разматывания стек вручную: 2880 | 2881 | ```golang 2882 | if pos == 0 { 2883 | re.error("'*' illegal at start of expression") 2884 | } 2885 | ``` 2886 | 2887 | Данный шаблон полезный в рамках только одного пакета. Превращение `Parse` внутреннего вызова `panic` в значение `error`, что позволяет на выставлять `panics` для клиента. Это хорошее правило, чтобы ему следовать. 2888 | 2889 | 2890 | Данный подход, меняет идиому паник на значение паники если произошла ошибка. 2891 | Тем не менее, как оригинальная, так и новые сбои будут представлены в отчёте сбоев, поэтому основная причина этой проблемы не будет видна. 2892 | Если Вы хотите увидеть только оригинальные значения, Вам необходимо немного больше кода для фильтрации неожиданных проблем и повторно паниковать с оригинальной ошибкой. 2893 | 2894 | 2895 | [^](#Оглавление) 2896 | 2897 | ## Веб-сервер 2898 | 2899 | [^](#Оглавление) 2900 | 2901 | Давайте закончим разработкой веб-сервера на Go. 2902 | Google предоставлен сервис по адресу [http://chart.apis.google.com](http://chart.apis.google.com) с автоматическим форматированием данных графиков и диаграмм. 2903 | Это трудно использовать в интерактивном режиме, но Вам необходимо добавить URL в качестве запроса. 2904 | Здесь программа использует приятный простой интерфейс с одной формой для данных: для небольшого кусочка текста, который вызывает сервер диаграмм для создания QR кода, кодируя текст в матрицу пиксел. 2905 | Эта картинка можно быть сфотографирована с помощью камеры телефона и интерпретирована, к примеру, как URL, экономя тем самым его набор на маленькой клавиатуре телефона. 2906 | 2907 | Вот программа полностью с последующими пояснениями. 2908 | 2909 | ```golang 2910 | //{{code "/doc/progs/eff_qr.go" `/package/` `$`}} 2911 | // Copyright 2009 The Go Authors. All rights reserved. 2912 | // Use of this source code is governed by a BSD-style 2913 | // license that can be found in the LICENSE file. 2914 | 2915 | package main 2916 | 2917 | import ( 2918 | "flag" 2919 | "html/template" 2920 | "log" 2921 | "net/http" 2922 | ) 2923 | 2924 | var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18 2925 | 2926 | var templ = template.Must(template.New("qr").Parse(templateStr)) 2927 | 2928 | func main() { 2929 | flag.Parse() 2930 | http.Handle("/", http.HandlerFunc(QR)) 2931 | err := http.ListenAndServe(*addr, nil) 2932 | if err != nil { 2933 | log.Fatal("ListenAndServe:", err) 2934 | } 2935 | } 2936 | 2937 | func QR(w http.ResponseWriter, req *http.Request) { 2938 | templ.Execute(w, req.FormValue("s")) 2939 | } 2940 | 2941 | const templateStr = ` 2942 | 2943 | 2944 | QR Link Generator 2945 | 2946 | 2947 | {{if .}} 2948 | 2949 |
2950 | {{.}} 2951 |
2952 |
2953 | {{end}} 2954 |
2957 |
2958 | 2959 | ` 2960 | ``` 2961 | 2962 | Легко понять, что происходит в `main`. 2963 | Один флаг устанавливает HTTP сервер по умолчания для нашего сервера. 2964 | В значении шаблона `templ`, происходит самое интересное. Он конструирует шаблон HTML, который будет выполнен сервером для показа страницы. Давайте опишем, что происходит в этот момент. 2965 | 2966 | Функция `main` разбирает флаги и использует механизм о котором мы говорили выше, связывает функцию `QR` для корневого пути для сервера. 2967 | Когда вызывается `http.ListenAndServe` для старта сервера, он блокируется пока сервер запущен. 2968 | 2969 | Функция `QR` только получает запрос, который содержит дынные формы, и выполняет шаблон на данных в форме с именем переменной `s`. 2970 | 2971 | 2972 | Пакет шаблонов `html/template` мощный; данная программа лишь слегка затрагивает его возможности. 2973 | По сути, он переписывает часть текста HTML на лету, заменяя элементы на элементы данных, передаваемые в `templ.Execute`, в данном случаи переменной формы. 2974 | В тексте шаблона (`templateStr`), имеются *двойные скобки разделители* обозначающие действия шаблона. 2975 | Участок от `{{html "{{if .}}"}}` до `{{html "{{end}}"}}` выполняются только если значения текущей элемента данных, вызывают `.` (точка) не пустая. То есть, если строка пуста, то данный участок шаблона игнорируется. 2976 | 2977 | 2978 | Два примере кода `{{html "{{.}}"}}` предназначены для показа существующих данных в запросе шаблона на веб странице. 2979 | Пакет шаблонов HTML автоматически обеспечивает соответствие, поэтому текст является безопасным для отображения. 2980 | 2981 | 2982 | Остальные строки шаблона, просто строки HTML , которые показываются при загрузки страницы. 2983 | Если это слишком быстрое объяснение, то смотрите [документацию](https://golang.org/pkg/html/template/) о пакете шаблонов для большего понимания. 2984 | 2985 | В результате у Вас есть: полезный пример веб сервера из нескольких строк кода с управлением данных текста HTML. 2986 | Язык Go достаточно мощный для создание много чего интересного за несколько строк. 2987 | 2988 | [^](#Оглавление) 2989 | 2990 | ------ 2991 | 2992 | **Список дополнительных материалов:** 2993 | * [Руководство сотрудничества](https://github.com/Konstantin8105/Contribution_Guide_RU) 2994 | * [Эффективный Go](https://github.com/Konstantin8105/Effective_Go_RU) 2995 | 2996 | ------ 2997 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-modernist --------------------------------------------------------------------------------