🐹 Go
Полный справочник: модули, типы, срезы и map, функции и ошибки, структуры и интерфейсы, дженерики, горутины и каналы, context, net/http, JSON, тесты и инструменты.
Шпаргалки · Программирование · #go #golang #backend #concurrency
Инструменты
go version go mod init example.com/app go mod tidy go get github.com/x/y@latest
go run . go build -o app ./cmd/app go install ./... go vet ./...
go test ./... go test -race -cover ./... go test -bench . -benchmem
gofmt -w . go fmt ./... golangci-lint run
GOOS=linux GOARCH=amd64 go build # кросс-компиляция
IDE: GoLand, VS Code (расширение Go + gopls). Документация: go doc fmt.Println, pkg.go.dev. Структура: cmd/ (точки входа), internal/ (закрытый код), pkg/ (публичный).
Минимальная программа
package main
import "fmt"
func main() {
fmt.Println("Привет")
}
Файлы начинаются с package. Экспортируется то, что с заглавной буквы (Name), остальное закрыто в пакете.
Переменные и типы
var n int = 5 var s string // нулевые значения: 0, "", false, nil
x := 10 // короткое объявление (только внутри функций)
const Pi = 3.14 const ( A = iota; B; C ) // iota: 0, 1, 2
var a, b = 1, "x"
| Группа | Типы |
|---|---|
| Целые | int, int8…int64, uint, uint8…uint64, uintptr; byte = uint8, rune = int32 |
| Дробные | float32, float64, complex64/128 |
| Прочие | bool, string, error, any (interface{}) |
| Составные | массив [3]int, срез []int, map[K]V, struct, *T, func, chan T |
float64(n); int(f); strconv.Itoa(5); strconv.Atoi("5"); strconv.ParseFloat("1.5", 64); fmt.Sprint(n)
type ID int64 type Celsius float64 // именованные типы
Приведение явное: Go не расширяет типы неявно.
Строки
s := "Привет"
len(s) // байты (UTF-8), не символы
utf8.RuneCountInString(s) // символы
for i, r := range s {} // r — rune
[]rune(s); []byte(s); string(runes)
strings.Contains(s, "р"); strings.Split(s, ","); strings.Join(parts, ","); strings.TrimSpace(s); strings.ToUpper(s)
strings.Fields(s); strings.Replace(s, "a", "b", -1); strings.HasPrefix(s, "П"); strings.Builder
fmt.Sprintf("%s: %d (%.2f) %v %+v %T %q %x", name, 5, 3.14159, v, v, v, s, 255)
%v значение, %+v с именами полей, %#v синтаксис Go, %T тип, %q в кавычках, %x hex. Строки неизменяемы; для сборки strings.Builder.
Управление
if x > 0 { } else if x == 0 { } else { }
if v, ok := m["key"]; ok { } // инициализация в if
switch day { case "sat", "sun": ...; case "mon": ...; default: ... }
switch { case x < 0: ...; case x == 0: ...; default: ... } // без выражения; нет «проваливания»
switch v := i.(type) { case int: ...; case string: ...; } // switch по типу
for i := 0; i < 3; i++ { } for cond { } for { } // единственный цикл
for i, v := range slice { } for k, v := range m { } for i := range 10 { } // range по числу (1.22)
for range ch { } break; continue; goto; label: for ...
defer f.Close() // выполнится при выходе из функции (в обратном порядке)
Массивы, срезы, map
arr := [3]int{1, 2, 3} // массив фиксированной длины, копируется по значению
sl := []int{1, 2, 3} sl = append(sl, 4, 5) sl2 := sl[1:3] // срез ссылается на тот же массив
make([]int, 0, 100) len(sl); cap(sl); copy(dst, src); slices.Contains(sl, 2); slices.Sort(sl); slices.Index(sl, 3)
sl = slices.Delete(sl, 1, 2); sl = append(sl[:i], sl[i+1:]...)
m := map[string]int{"a": 1} m["b"] = 2 v, ok := m["x"] delete(m, "a") len(m)
make(map[string][]string) maps.Keys(m); maps.Clone(m) // map не потокобезопасен
Подвох: срезы делят память, append может переиспользовать массив. Чтобы изолировать: slices.Clone(sl). Порядок обхода map случайный. Запись в nil-map паникует, чтение нет.
Функции и ошибки
func add(a, b int) int { return a + b }
func div(a, b float64) (float64, error) {
if b == 0 { return 0, errors.New("деление на ноль") }
return a / b, nil
}
func sum(nums ...int) int { } sum(sl...)
f := func(x int) int { return x * x } // замыкание
func named() (n int, err error) { n = 1; return }
res, err := div(1, 0)
if err != nil { return fmt.Errorf("div: %w", err) } // обёртка ошибки
errors.Is(err, ErrNotFound); errors.As(err, &target); errors.Join(e1, e2)
var ErrNotFound = errors.New("не найдено")
type MyErr struct{ Code int }; func (e *MyErr) Error() string { return fmt.Sprint(e.Code) }
panic("критично") defer func() { if r := recover(); r != nil { } }() // для невосстановимых ситуаций
Идиома: ошибка последнее возвращаемое значение, обрабатывайте её сразу.
Структуры, методы, интерфейсы
type User struct {
ID int `json:"id" db:"id"`
Name string `json:"name,omitempty"`
email string // с маленькой — не экспортируется
}
u := User{ID: 1, Name: "Аня"} p := &u p.Name = "Б" // доступ через указатель без разыменования
func (u User) Label() string { return "@" + u.Name } // метод со значением
func (u *User) Rename(n string) { u.Name = n } // метод с указателем: изменяет
type Reader interface { Read(p []byte) (int, error) } // интерфейс: неявная реализация
type Shape interface { Area() float64 }
var s Shape = Circle{R: 1}
type Admin struct { User; Level int } // встраивание (композиция): Admin получает методы User
Принимайте интерфейсы, возвращайте структуры. Мелкие интерфейсы (io.Reader, fmt.Stringer). Пустой интерфейс any теряет типизацию.
Дженерики (1.18+)
func Map[T, U any](xs []T, f func(T) U) []U { out := make([]U, 0, len(xs)); for _, x := range xs { out = append(out, f(x)) }; return out }
type Number interface { ~int | ~int64 | ~float64 }
func Sum[T Number](xs []T) (s T) { for _, x := range xs { s += x }; return }
type Stack[T any] struct{ items []T }
func (s *Stack[T]) Push(x T) { s.items = append(s.items, x) }
Ограничения: any, comparable, cmp.Ordered.
Горутины и каналы
go work() // запуск лёгкой горутины
ch := make(chan int) buf := make(chan int, 10) ch <- 1 v := <-ch close(ch) v, ok := <-ch
for v := range ch { } // до закрытия канала
var wg sync.WaitGroup
for i := 0; i < 5; i++ { wg.Add(1); go func() { defer wg.Done(); work(i) }() }
wg.Wait()
var mu sync.Mutex; mu.Lock(); defer mu.Unlock() // sync.RWMutex, sync.Once, sync/atomic
select {
case v := <-ch: fmt.Println(v)
case <-time.After(time.Second): fmt.Println("таймаут")
case <-ctx.Done(): return ctx.Err()
default: }
Правила: канал закрывает отправитель; не запускайте горутины без способа остановки (утечки); при общей памяти используйте -race. Пул воркеров: канал задач + WaitGroup. Для группы с ошибками: golang.org/x/sync/errgroup.
context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
select { case <-ctx.Done(): return ctx.Err() }
ctx = context.WithValue(ctx, key{}, "val") // для данных запроса, не для опций
Передавайте ctx первым параметром во все функции, делающие ввод-вывод.
Работа с HTTP и JSON
// сервер
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) { // шаблоны с методом и параметром (1.22)
id := r.PathValue("id")
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"id": id})
})
srv := &http.Server{Addr: ":8080", Handler: mux, ReadHeaderTimeout: 5 * time.Second}
log.Fatal(srv.ListenAndServe())
// клиент
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(url); if err != nil { return err }; defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var u User; json.Unmarshal(data, &u); json.Marshal(u); json.NewDecoder(r.Body).Decode(&u)
Не используйте http.DefaultClient без таймаута.
Файлы и ввод-вывод
data, err := os.ReadFile("a.txt"); os.WriteFile("b.txt", data, 0o644); os.MkdirAll(dir, 0o755); os.Remove(p)
f, _ := os.Open("a.txt"); defer f.Close(); sc := bufio.NewScanner(f); for sc.Scan() { sc.Text() }
filepath.Join("a", "b"); filepath.Walk(root, fn); os.Getenv("HOME"); os.Args; flag.String("port", "8080", "порт"); flag.Parse()
Логи
log/slog (структурные): slog.Info("start", "port", 8080), slog.New(slog.NewJSONHandler(os.Stdout, nil)).
Тесты
func TestAdd(t *testing.T) { if got := add(1, 2); got != 3 { t.Errorf("got %d, want 3", got) } }
func TestTable(t *testing.T) {
tests := []struct{ name string; a, b, want int }{{"plus", 1, 2, 3}, {"zero", 0, 0, 0}}
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if add(tt.a, tt.b) != tt.want { t.Fatal("fail") } }) }
}
func BenchmarkAdd(b *testing.B) { for i := 0; i < b.N; i++ { add(1, 2) } }
func FuzzParse(f *testing.F) { f.Fuzz(func(t *testing.T, s string) { parse(s) }) }
Файлы *_test.go. Библиотеки: testify, gomock, httptest (встроено), testcontainers-go.
Подводные камни
- Горутины в цикле захватывали переменную до 1.22; сейчас каждая итерация своя, но следите за версией
goвgo.mod. nil-интерфейс против интерфейса сnil-указателем.- Утечки горутин и незакрытое тело ответа.
- Гонки данных (
go test -race). - Копия
sync.Mutexпо значению. - Срезы делят память.
Библиотеки по задачам
| Задача | Что взять |
|---|---|
| Веб | net/http, chi, Gin, Echo, Fiber |
| БД | database/sql + pgx, sqlx, GORM, sqlc, ent |
| Миграции | goose, golang-migrate, atlas |
| CLI | cobra, urfave/cli, pflag |
| Конфиги | viper, envconfig, koanf |
| gRPC | google.golang.org/grpc, connect-go |
| Очереди | sarama, franz-go (Kafka), amqp091-go |
| Логи | slog, zap, zerolog |
| Валидация | go-playground/validator |
| Метрики | prometheus/client_golang, OpenTelemetry |