- Основные особенности языка
- Как установить Go?
- Управление версиями Go
- Что дальше?
- Подводные камни Go для начинающих
- Переменные
- Короткие объявления переменных можно использовать только внутри функций
- Переобъявления переменных с помощью коротких объявлений
- Нельзя использовать короткие объявления переменных для настройки значений полей
- Случайное сокрытие переменных
- Нельзя использовать nil для инициализации переменной без явного указания типа
- Операторы
- Константы
- Срезы и массивы
- Массивы (slice)
- Срезы (array)
- Обновление и привязка значений полей в slice, array
- Передача массивов в функции
- Неожиданные значения в выражениях range в слайсах и массивах
- Карты
- Порядок итерации карты случайный (на самом деле нет)
- Проверка наличия ключа карты
- Карта - это указатель
- Зачем используют значение пустой стурктуры вместо булева
- Емкость карты
- Значения карты не адресуются
- Гонки данных
- sync.Map
- Ёмкость хеш-таблиц
- Использование nil-слайсов (slice) и хеш-таблиц (map)
- Строки и байтовые массивы
- Основы строк в Go
- Строки не могут быть нулевыми (
nil
) - Строки неизменяемы (вроде)
- Строки против среза
[]byte
- UTF-8 и фсе такое
- Кодировка строк в Go
- Руны, что это
- Оператор строкового индекса или оператор
for
...range
- Циклы песни льда и пламени (нет)
- Итератор диапазона (range) возвращает два значения
- Переменные итератора цикла используются повторно
- Именованные
break
иcontinue
- Погружение в
switch
иselect
- Операторы
case
по умолчанию прерываются - Именованные
break
- Операторы
- Функции
- Отложенные (
defer
) - Горутины (goroutines), что это такое **
- Программа не ждет запущенных горутин
- Паникующая горутина приведет к сбою всего приложения, вот ***
- Отложенные (
- Интерфейсы
- Наследование (почти ООП. кек)
- Операторы равенства, проверка на равенство, равенство во всем мире, мы топим за ра.., стоп не туда
- Менеджмент памяти
- Логирование
- Дата и время
- Переменные
Go или GoLang — компилируемый многопоточный язык программирования. Язык был разработан Google для решения проблем корпорации, возникающих при разработке программного обеспечения.
- Ортогональность — в языке есть небольшое число средств, не повторяющих функциональность друг друга.
- Простая грамматика — минимум ключевых слов, легко разбираемая структура и читаемый код.
- Простая работа с типами — типизация обеспечивает безопасность, но не превращается в бюрократию.
- Отсутствие неявных преобразований.
- Сборка мусора.
- Встроенные простые и эффективные средства распараллеливания.
- Чёткое разделение интерфейса и реализации.
- Быстрая сборка за счёт эффективной системы пакетов с явным указанием зависимостей.
- Скачайте исходники:
$ wget https://dl.google.com/go/go$VERSION.$OS-$ARCH.tar.gz
Чтобы понять для какой архитектуры и какую версию Go скачивать посетите сайт: https://golang.org/dl/
- Распакуйте:
$ tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz
- Установите переменные окружения.
Для этого откройте ваш
.profile
файл и добавьте следующие 3 строки в конец файла. Вы можете добавить это в файл.zshrc
или.bashrc
в соответствии с вашей конфигурацией оболочки.
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
P.S. $ echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.profile
- Выполните, чтобы изменения вступили в силу:
source ~/.profile
или.bashrc
,.zshrc
- это позволит использовать команды go без перезапуска сеанса.
Используйте goenv, чтобы выбрать версию Go для своего приложения и гарантировать, что ваша среда разработки соответствует производственной.
- A Tour of Go
- Golang - изучаем зяык программирования GO
- How to Write Go Code
- Effective Go | Эффективный Go
- Маленькая книга о Go – Глава 1: Основы
- Awesome Go
- Идиомы Go | Idiomatic Go
- Основные комментарии, возникающие при ревью кода | Go Code Review Comments
- Организация программного кода | Organizing Go code
- Пословицы Go | Go Proverbs
- Структуризация тестов | Structuring Tests in Go
- Стандартный шаблон пакета | Standard Package Layout
- Анатомия проекта на Go | The anatomy of a Go project
- Руководства по стилю для пакетов Go | Style guideline for Go packages
- Обработка времени в go | time in go
- Как использовать интерфейсы в Go | How to use interfaces in Go
- Лучшие практики Go, шесть лет в деле
- Отличительные особенности хорошей библиотеки на языке Go
- Код-стайл в Go
- Шпаргалка по слайсам | Go Slice Tricks Cheat Sheet
- Concurrency in Go (book)
- Состояние гонки по данным и способы решения
- Использование контекстов для того, чтобы избежать утечек горутин
- Паттерны конкурентности в Go: пайплайны и отмены
- Туториал: Синхронизация состояния с мьютексами в Go
- Буферизованные каналы в Go: советы и рекомендации
- Видеоурок о gRPC
- Токен авторизация в микросервисах Go | Token Based Authentication in Go Microservices
- Написание и использование interveptors
- Modern Make
- What is REST
- Двенадцать факторов
- Semantic Versioning
- Шпаргалка по git-flow
- ведите changelog
- Работа с ssh-agent
В отличие от многих других языков, таких как, JS, PHP, Java и т.д., Go не имеет префиксных операторов увеличения (инкремент) или уменьшения (декремент):
var i int
++i // syntax error: unexpected ++, expecting }
--i // syntax error: unexpected --, expecting }
Хотя в Go и есть постфиксные версии этих операторов, но их нельзя использовать в выражениях:
slice := []int{1,2,3}
i := 1
slice[i++] = 0 // syntax error: unexpected ++, expecting :
Наверное одна из самых частых вещей, которые ищут разработчики перешедшие с других языков - это тернарный оператор. Но тут все просто - его нет. Разработчики языка Go решили, что этот оператор часто приводит к некрасивому коду, и лучше вообще его не использовать.
- Ключевое слово iota представляет последовательные целочисленные константы 0, 1, 2,…
- Оно обнуляется каждый раз, когда const появляется в исходном коде
- И увеличивается после каждой спецификации const.
const (
C0 = iota
C1 = iota
C2 = iota
)
fmt.Println(C0, C1, C2) // "0 1 2"
Можно упростить до:
const (
C0 = iota
C1
C2
)
Двойное использование йоты не сбрасывает нумерацию:
const (
C0 = iota // 0
C1 // 1
C2 = iota // 2
)
Чтобы начать список констант с 1 вместо 0, можно использовать iota в арифметическом выражении.
const (
C1 = iota + 1
C2
C3
)
fmt.Println(C1, C2, C3) // "1 2 3"
Можно использовать пустой идентификатор, чтобы пропустить значение в списке констант.
const (
C1 = iota + 1
_
C3
C4
)
fmt.Println(C1, C3, C4) // "1 3 4"
Полный тип enum со строками [best practice]. Вот идиоматический способ реализации перечисляемого типа:
- создаем новый целочисленный тип,
- перечисляем его значения с использованием iota,
- реализуем для типа функцию String.
type Direction int
const (
North Direction = iota
East
South
West
)
func (d Direction) String() string {
return [...]string{"North", "East", "South", "West"}[d]
}
// usage
var d Direction = North
fmt.Print(d)
switch d {
case North:
fmt.Println(" goes up.")
case South:
fmt.Println(" goes down.")
default:
fmt.Println(" stays put.")
}
// Output: North goes up.
По стандартному соглашению об именовании, необходимо использовать смешанный caps и для для констант. Например, экспортируемую константу будет правильным назвать NorthWest, а не NORTH_WEST.
Другое распространенное приложение для iota — реализация bitmask. Это небольшой набор булевых значений (их часто называют “флагами”), которые представлены битами в одном числе.
Посмотрите bitmasks и флаги для полного понимания.
В Go срезы и массивы служат аналогичной цели. Они декларируются примерно так же одинаково:
slice := []int{1, 2, 3}
array := [3]int{1, 2, 3}
// let the compiler work out array length
// this will be an equivalent of [3]int
array2 := [...]int{1, 2, 3}
fmt.Println(slice, array, array2)
Срезы являются более "продвинутыми" и удобными (по факту срез содержит указатель на массив, об этом чуть ниже), по этой причине массивы используют реже и в каких-то специфических случаях. В самых обычных случаях вы также будете использовать срезы.
Массив - это типизированный набор памяти фиксированного размера. Массивы разной длины считаются разными несовместимыми типами.
- В отличие от C, элементы массива инициализируются нулевыми значениями при создании массива, поэтому нет необходимости делать это явно.
- Также, в отличие от C в Go, массив - это тип значения. Это не указатель на первый элемент блока памяти. Если вы передадите массив в функцию, будет скопирован весь массив. Вы все равно можете передать указатель на массив, чтобы он не копировался.
Это очень полезная структура данных, но, возможно, немного необычная. Есть несколько способов выстрелить им себе в ногу, всех которых можно избежать, если вы знаете, как срез работает изнутри.
Вот так выглядит срез исходном коде Go:
type slice struct {
array unsafe.Pointer
len int
cap int
}
Сам срез является типом значения, но он ссылается на массив, который он использует, с помощью указателя. В отличие от массива, если вы передадите срез в функцию, вы получите копию указателя массива, свойств len
и cap
но данные самого массива не будут скопированы и обе копии (оригинал и в функции) среза будут указывать на один и тот же массив.
То же самое происходит, когда вы "разрезаете" срез. Нарезка создает новый срез, который по-прежнему указывает на тот же массив:
func f1(s []int) {
// slicing the slice creates a new slice
// but does not copy the array data
s = s[2:4]
// modifying the sub-slice
// changes the array of slice in main function as well
for i := range s {
s[i] += 10
}
fmt.Println("f1", s, len(s), cap(s))
}
func main() {
s := []int{1, 2, 3, 4, 5}
// passing a slice as an argument
// makes a copy of the slice properties (pointer, len and cap)
// but the copy shares the same array
f1(s)
fmt.Println("main", s, len(s), cap(s))
}
Output:
in function f1 [13 14] 2 3
in function main [1 2 13 14 5] 5 5
Мы передали срез в фнкцию, "разрезали" (под-срез) его и изменили значения последнего и, как видно, мы изменили оригинальный срез, который находится на самом верхнем уровне в main
.
Чтобы получить копию среза с данными, вам нужно немного поработать. Вы можете вручную скопировать элементы в новый фрагмент или использовать copy
или append
:
func f1(s []int) {
s = s[2:4]
s2 := make([]int, len(s))
copy(s2, s)
// or if you prefer less efficient, but more concise version:
// s2 := append([]int{}, s[2:4]...)
for i := range s2 {
s2[i] += 10
}
fmt.Println("f1", s2, len(s2), cap(s2))
}
func main() {
s := []int{1, 2, 3, 4, 5}
f1(s)
fmt.Println("main", s, len(s), cap(s))
}
Output:
in function f1 [13 14] 2 3
in function main [1 2 3 4 5] 5 5
Как видим, оригинальный срзе не изменился, т.к. мы скопировали и он больше не указывает на массив первого среза.
Самым полезным свойством среза является то, что он управляет ростом массива за вас. Когда срезу необходимо превысить емкость существующего массива, Go выделит совершенно новый массив с дополнительной емкостью и переместит данные в него (точнее это сделает функция append). Но это является еще одной ловушкой, например, если вы ожидаете что два среза будут указывать на один и тот же массив.
func main() {
// make a slice with length 3 and capacity 4
s := make([]int, 3, 4)
// initialize to 1,2,3
s[0] = 1
s[1] = 2
s[2] = 3
// capacity of the array is 4
// adding one more number fits in the initial array
s2 := append(s, 4)
// modify the elements of the array
// s and s2 still share the same array
for i := range s2 {
s2[i] += 10
}
fmt.Println(s, len(s), cap(s)) // [11 12 13] 3 4
fmt.Println(s2, len(s2), cap(s2)) // [11 12 13 14] 4 4
// this append grows the array past its capacity
// new array must be allocated for s3
s3 := append(s2, 5)
// modify the elements of the array to see the result
for i := range s3 {
s3[i] += 10
}
fmt.Println(s, len(s), cap(s)) // still the old array [11 12 13] 3 4
fmt.Println(s2, len(s2), cap(s2)) // the old array [11 12 13 14] 4 4
// array was copied on last append [21 22 23 24 15] 5 8
fmt.Println(s3, len(s3), cap(s3))
}
Еще одной приятной особенностью является то, что срезы не нужно проверять на нулевое значение и не всегда нужно инициализировать. Такие функции, как len
, cap
и append
, отлично работают с нулевым срезом:
func main() {
var s []int // nil slice
fmt.Println(s, len(s), cap(s)) // [] 0 0
s = append(s, 1)
fmt.Println(s, len(s), cap(s)) // [1] 1 1
}
Но, надо помнить, что пустой срез - это не то же самое, что нулевой срез:
func main() {
var s []int // this is a nil slice
s2 := []int{} // this is an empty slice
// looks like the same thing here:
fmt.Println(s, len(s), cap(s)) // [] 0 0
fmt.Println(s2, len(s2), cap(s2)) // [] 0 0
// but s2 is actually allocated somewhere
fmt.Printf("%p %p", s, s2) // 0x0 0x65ca90
}
Еще одна ловушка, когда вы инициализируете срез через make
:
s := make([]int, 3)
s = append(s, 1)
s = append(s, 2)
s = append(s, 3)
Как вы думаете, что будет? Вот так выглядит сигнатура make
для срезов:
func make([]T, len, cap) []T
Конечно, он инициализирует срез с длиной 3 и емкостью! Т.е. по факту мы сделали следующее:
s := make([]int, 3, 3)
...
И вывод кода выше будет следующим:
[0 0 0 1 2 3]
Это может привести к неожиданным ошибка, если не понимать работу срезов. Чтобы код выше вывел ожидаемые 1,2,3, нам необходимо изменить инициализацию через make
след. образомм:
s := make([]int, 0, 3)
...
// [1 2 3]
Иногда встречаются варианты с индексированным доступом, что тоже в принципе верно и вполне рабочее:
s := make([]int, 3, 3)
for i, v := range []int{1, 2, 3} {
s[i] = v
}
fmt.Println(s)
Ну и напоследок покажу многомерные срезы, собственно это же можно проделать и с массивами:
x := 2
y := 3
s := make([][]int, y)
for i := range s {
s[i] = make([]int, x)
}
fmt.Println(s) // [[0 0] [0 0] [0 0]]
time.LoadLocation читает из файла
Одна из моих любимых ловушек в Go. Для преобразования между часовыми поясами вам сначала необходимо загрузить информацию о местоположении. Оказывается, time.LoadLocation читает из файла каждый раз, когда он вызывается. Не самое лучшее решение, анпример, если нам нужно делать это при форматировании каждой строки массивного CSV-отчета:
package main
import (
"testing"
"time"
)
func BenchmarkLocation(b *testing.B) {
for n := 0; n < b.N; n++ {
loc, _ := time.LoadLocation("Asia/Kolkata")
time.Now().In(loc)
}
}
func BenchmarkLocation2(b *testing.B) {
loc, _ := time.LoadLocation("Asia/Kolkata")
for n := 0; n < b.N; n++ {
time.Now().In(loc)
}
}
// Output:
BenchmarkLocation-8 16810 76179 ns/op 58192 B/op 14 allocs/op
BenchmarkLocation2-8 188887110 6.97 ns/op 0 B/op 0 allocs/op