Go — язык, созданный для простоты
Go (Golang) — компилируемый язык, созданный в Google для надёжных и быстрых сервисов. Он сочетает простоту и производительность: горутины делают параллельность лёгкой, а сборщик мусора — безопасной. Идеальный выбор для серверов, кли и DevOps-инструментов.
1. Что такое Go
Go разработали Роберт Гризмер, Роб Пайк и Кен Томпсон в 2009 году. Цель — убрать боль разработки на C++ и Java: быстрая компиляция, простой синтаксис, встроенная параллельность и жёсткая статическая типизация.
Ключевые особенности Go:
- Компиляция — программа превращается в один бинарный файл, который выполняется без установки каких-либо зависимостей.
- Goroutines — лёгкие «потоки», которых можно запускать тысячами.
- Стандартная библиотека — HTTP, JSON, шифрование, работа с файлами уже в комплекте.
- Форматирование gofmt — единый стиль кода, споры о форматировании исчезают.
| Где применяется | Примеры продуктов |
|---|---|
| Инфраструктура и DevOps | Docker, Kubernetes, Terraform |
| Серверы и API | сервисы Google, Cloudflare |
| CLI-инструменты | GitHub CLI, Hugo |
| Сбор данных и сети | прокси, анализаторы трафика |
2. Установка и первая программа
Установи Go с официального сайта go.dev — там есть установщики для Windows, macOS и Linux. После установки проверь версию.
go version # go version go1.24.0 windows/amd64
# на Windows можно также поставить через scoop или winget
winget install GoLang.GoСоздай файл main.go с классической программой:
package main
import "fmt"
func main() {
fmt.Println("Привет, Go!")
}go run main.go # запуск
go build main.go # сборка бинарника main.exeКоманда go run компилирует и сразу выполняет код, а go build создаёт исполняемый файл, который можно переносить на другие компьютеры.
Структура Go-программы
| Элемент | Назначение |
|---|---|
| package main | исполняемая программа (не библиотека) |
| import "fmt" | подключение пакета ввода-вывода |
| func main() | точка входа программы |
| go run / go build | запуск и сборка |
3. Типы и переменные
Go строго типизирован: у каждой переменной есть тип, и неявные преобразования запрещены.
package main
import "fmt"
func main() {
var name string = "Аня" // явный тип
var age int = 25
var height float64 = 1.72
var active bool = true
city := "Москва" // короткая запись (тип выводится сам)
year := 2026
fmt.Println(name, age, height, active, city, year)
// несколько переменных сразу
var x, y int = 10, 20
a, b := "привет", 3.14
// константы
const pi = 3.14159
}Основные типы
| Тип | Размер | Пример |
|---|---|---|
| int | машинное слово | 42 |
| int8 / int16 / int32 / int64 | 1/2/4/8 байт | -5 |
| float32 / float64 | 4 / 8 байт | 3.14 |
| string | — | "текст" |
| bool | — | true / false |
| byte (=uint8) | 1 байт | 255 |
| rune (=int32) | 4 байта | 'Ж' |
// явное преобразование типов
var i int = 7
var f float64 = float64(i)
var s string = strconv.Itoa(i) // "7" — нужен импорт strconv4. Условия и циклы
В Go всего один цикл — for. Он заменяет while и do-while, а выбор строится на if и switch.
package main
import "fmt"
func main() {
score := 85
if score >= 90 {
fmt.Println("Отлично")
} else if score >= 60 {
fmt.Println("Хорошо")
} else {
fmt.Println("Подтянись")
}
// switch — альтернатива цепочке if
day := "saturday"
switch day {
case "saturday", "sunday":
fmt.Println("Выходной")
default:
fmt.Println("Рабочий день")
}
}Варианты цикла for
// классический for
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// while-стиль
n := 10
for n > 0 {
n--
}
// бесконечный цикл
for {
// выход — через break
break
}
// итерация по коллекции (range)
names := []string{"Аня", "Боря", "Вера"}
for idx, name := range names {
fmt.Println(idx, name)
}if x > 5, а не if (x > 5). Но фигурная скобка открывается на той же строке — иначе код не соберётся.if v := compute(); v > 0 { ... }. Это удобно и безопасно.5. Функции и замыкания
Функции объявляются ключевым словом func и могут возвращать несколько значений — это удобно для пар «результат + ошибка».
package main
import "fmt"
// функция с одним результатом
func square(x int) int {
return x * x
}
// несколько возвращаемых значений
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("деление на ноль")
}
return a / b, nil
}
// именованные результаты
func swap(x, y int) (a, b int) {
a, b = y, x
return // возвращает a и b
}
func main() {
fmt.Println(square(5)) // 25
res, err := divide(10, 2)
if err != nil {
fmt.Println("Ошибка:", err)
} else {
fmt.Println("Результат:", res)
}
a, b := swap(1, 2)
fmt.Println(a, b) // 2 1
}Функции — значения: их можно передавать в другие функции и возвращать. Замыкание «запоминает» переменные внешней функции.
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
next := counter()
fmt.Println(next()) // 1
fmt.Println(next()) // 2
fmt.Println(next()) // 3
fmt.Println(counter()()) // 1 — отдельный счётчик
}6. Указатели
Указатель хранит адрес переменной в памяти. Оператор & получает адрес, * разыменовывает — обращается к значению.
package main
import "fmt"
func addPrice(p *int, amount int) {
*p += amount // меняем значение по адресу
}
func main() {
price := 100
addPrice(&price, 50) // передаём адрес переменной
fmt.Println(price) // 150
// nil-указатели
var p *int
if p == nil {
fmt.Println("Указатель пустой")
}
}Значение против указателя
| Передача по значению | Передача по указателю |
|---|---|
| копия переменной | работа с оригиналом |
| изменения не сохраняются | изменения сохраняются |
| быстро для малых данных | не копирует большие структуры |
7. Структуры и методы
Структура (struct) объединяет поля в один тип. Методы — функции с «получателем» (receiver), они определяют поведение.
package main
import "fmt"
type User struct {
Name string
Age int
}
// метод со значением получателя — не меняет оригинал
func (u User) Greeting() string {
return "Привет, " + u.Name
}
// метод с указателем — может менять поля
func (u *User) Birthday() {
u.Age++
}
func main() {
u := User{Name: "Аня", Age: 25}
fmt.Println(u.Greeting()) // Привет, Аня
u.Birthday()
u.Birthday()
fmt.Println(u.Age) // 27
}// конструктор-функция (по соглашению NewXxx)
func NewUser(name string) *User {
return &User{Name: name}
}
type Product struct {
Name string
Price int
}
// встраивание структуры (embedding) — композиция
type CartItem struct {
Product
Quantity int
}
func main() {
item := CartItem{
Product: Product{Name: "Кроссовки", Price: 4990},
Quantity: 2,
}
fmt.Println(item.Name) // поле из встроенной структуры
fmt.Println(item.Price)
}8. Интерфейсы
Интерфейс описывает набор методов. Тип удовлетворяет интерфейсу автоматически, если у него есть все эти методы — явных объявлений не нужно.
package main
import "fmt"
type Shape interface {
Area() float64
}
type Rectangle struct{ W, H float64 }
type Circle struct{ R float64 }
func (r Rectangle) Area() float64 { return r.W * r.H }
func (c Circle) Area() float64 { return 3.14 * c.R * c.R }
func printArea(s Shape) {
fmt.Printf("Площадь: %.2f\n", s.Area())
}
func main() {
printArea(Rectangle{W: 3, H: 4}) // 12.00
printArea(Circle{R: 2}) // 12.56
var s Shape = Rectangle{W: 5, H: 2}
fmt.Println(s.Area())
}Пустой интерфейс interface{} (или any) принимает любой тип, но пользоваться полями нужно через утверждение типа.
func describe(v any) {
switch t := v.(type) {
case string:
fmt.Println("строка:", t)
case int:
fmt.Println("число:", t)
default:
fmt.Println("неизвестный тип")
}
}value, ok := x.(string). Второе значение ok — успешно ли прошло утверждение. Используй его, чтобы избежать паники.if err != nil { return ... }. Ранние return и короткие функции делают код читаемым.9. Goroutines и каналы
Goroutine — это легко весомая единица параллельного выполнения, запускается словом go. Каналы (chan) позволяют goroutines обмениваться данными и синхронизироваться.
package main
import (
"fmt"
"time"
)
func work(name string) {
for i := 1; i <= 3; i++ {
fmt.Println(name, i)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go work("горутина A") // запуск в отдельной горутине
go work("горутина B")
time.Sleep(500 * time.Millisecond) // ждём завершения
fmt.Println("Готово")
}Каналы
func sum(nums []int, result chan int) {
total := 0
for _, n := range nums {
total += n
}
result <- total // отправка в канал
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6}
result := make(chan int)
go sum(nums[:3], result)
go sum(nums[3:], result)
a, b := <-result, <-result // ждём оба результата
fmt.Println(a, b, a+b)
}// буферизованный канал и закрытие
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch) // закрываем: больше отправлять нельзя
for v := range ch {
fmt.Println(v) // читаем все значения
}select { case v := <-ch1: ...; case v := <-ch2: ... }. Это как switch, но для каналов.10. Пакеты, модули и HTTP-сервер
Go-программа собирается из пакетов. Модуль (go.mod) описывает проект и его зависимости. Всю экосистему версионирует команда go mod.
go mod init myapp # создаём модуль
go get github.com/gin-gonic/gin # добавляем внешний пакет
go mod tidy # подчистить зависимости
go run .// mathx/mathx.go — свой пакет
package mathx
func Max(a, b int) int {
if a > b {
return a
}
return b
}// main.go
package main
import (
"fmt"
"myapp/mathx"
)
func main() {
fmt.Println(mathx.Max(10, 3)) // 10
}Стандартная библиотека уже содержит HTTP-сервер. Напишем мини-API без сторонних фреймворков:
package main
import (
"encoding/json"
"log"
"net/http"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price int `json:"price"`
}
var products = []Product{
{ID: 1, Name: "Кроссовки", Price: 4990},
{ID: 2, Name: "Шапка", Price: 990},
}
func listProducts(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(products)
}
func main() {
http.HandleFunc("/products", listProducts)
log.Println("Сервер: http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}# проверка
curl http://localhost:8080/productsПопулярные фреймворки и пакеты
| Пакет | Назначение |
|---|---|
| net/http | HTTP-сервер из стандартной библиотеки |
| gin / chi / echo | web-фреймворки (маршруты, middleware) |
| database/sql | работа с базами данных (драйверы отдельно) |
| gorm | ORM для баз данных |
| testing | встроенное тестирование |
11. Обработка ошибок: error, panic, recover
В Go ошибки — обычные значения типа error. Их возвращают, проверяют и оборачивают, а не бросают исключения. Типовой паттерн:
func parseBudget(text string) (int, error) {
n, err := strconv.Atoi(text)
if err != nil {
return 0, fmt.Errorf("parse budget %q: %w", text, err)
}
return n, nil
}// %w позволяет распаковать цепочку errors.Is / errors.As
if errors.Is(err, strconv.ErrSyntax) {
log.Println("некорректный формат числа")
}Для поистине критических ситуаций существует panic, а defer + recover ловит его в горутине:
func safe() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
panic("недоступная БД")
}fmt.Errorf("...: %w") — главный инструмент оборачивания: он сохраняет исходную ошибку для errors.Is и errors.As.12. Работа с файлами и контекстом
Чтение файлов лежит в стандартной библиотеке os и io. Эффективный паттерн — построчная обработка:
f, err := os.Open("data/orders.txt")
if err != nil {
return err
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
process(sc.Text())
}
if err := sc.Err(); err != nil {
log.Println("чтение прервано:", err)
}defer f.Close() — идиома: закрытие файла обязательно выполнится при выходе из функции.
Для длительных операций используют context.Context — он передаёт дедлайн, отмену и значения между сервисами:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
data, err := db.Query(ctx, "SELECT * FROM events")ctx.13. Логирование и переменные окружения
Начни с log/slog — структурированного логгера из стандартной библиотеки (Go 1.21+):
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
slog.Info("сервис запущен",
"port", 8080,
"env", os.Getenv("APP_ENV"),
)
slog.Error("ошибка подключения", "db", "postgres")
}Настройки приложения выносят в переменные окружения — они не зависят от кода и легко меняются в деплое:
func appConfig() (addr string, debug bool) {
addr = os.Getenv("ADDR")
if addr == "" {
addr = ":8080"
}
debug = os.Getenv("DEBUG") == "1"
return addr, debug
}Debug — трассировка, Info — важные события, Error — с контекстом kernel. Не логируй пароли и токены.14. Тесты и бенчмарки
Тесты в Go — обычные функции TestXxx в файлах _test.go:
// parser_test.go
func TestParseBudget(t *testing.T) {
cases := []struct {
in string
want int
err bool
}{
{"100", 100, false},
{"ab", 0, true},
}
for _, c := range cases {
got, err := parseBudget(c.in)
if (err != nil) != c.err {
t.Fatalf("%q: err = %v, want %v", c.in, err, c.err)
}
if !c.err && got != c.want {
t.Errorf("%q: got %d, want %d", c.in, got, c.want)
}
}
}func BenchmarkParseBudget(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = parseBudget("12345")
}
}Запуск: go test ./..., для бенчмарков — go test -bench=. -benchmem. Команда сама находит все _test.go в пакете.
// табличные тесты — главный стиль Go
// покрытие: go test -cover15. Продвинутая конкурентность
После знакомства с горутинами и каналами переходим к надёжным паттернам. Три классических инструмента синхронизации:
sync.WaitGroup— дождаться завершения группы горутин;sync.Mutex— защитить общие данные от одновременной записи;golang.org/x/sync/errgroup— параллельные задачи, где первый err останавливает группу.
func main() {
var wg sync.WaitGroup
for _, job := range jobs {
wg.Add(1)
go func(j string) {
defer wg.Done()
process(j)
}(job)
}
wg.Wait()
}Гонки данных (data race) — главный источник хаоса в конкурентном коде. Запускай тесты с флагом -race: встроенный детектор ловит большинство гонок на этапе разработки.
Популярная модель — worker pool: фиксированное число горутин берёт задачи из канала, а результаты кладёт в другой:
jobs := make(chan int, 10)
for w := 0; w < 4; w++ {
go worker(jobs)
}
for i := 0; i < 10; i++ { jobs <- i }
close(jobs)go func(j string){...}(job). Иначе все горутины прочитают последнее значение итерации.16. JSON и практика REST
Сериализация в Go — стандартная encoding/json с тегами структур. Теги задают имена полей в JSON и правила пропуска:
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price int `json:"price"`
Discount *int `json:"discount,omitempty"`
CreatedAt string `json:"created_at"`
}
p := Product{ID: 1, Name: "Кроссовки", Price: 4990}
data, _ := json.Marshal(p) // компактный JSON
var q Product
json.Unmarshal(data, &q) // обратно в структуруomitempty— пропускать пустые значения; указатель на nil тоже пропускается;Decoderчитает по потоку — идеален для больших HTTP-ответов;- даты передавай в RFC3339 (
time.Timeсериализуется так по умолчанию); json.Numberсохраняет точность больших чисел безfloat64.
null против []. Если API обещает пустой массив, инициализируй слайс явно: data := []Product{}.UnmarshalTypeError и отправляй клиенту понятный текст, а не «400 Bad Request» без деталей.17. Профилирование и оптимизация
Go включает профилировщик pprof — получить картину «где время и память» можно за несколько минут:
import _ "net/http/pprof"
// в main добавить отдельный локальный порт:
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()# снять CPU-профиль 30с и открыть интерактив
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30
# или текст-топ для консоли
go tool pprof -top http://localhost:6060/debug/pprof/allocationsТипичные находки: избыточные аллокации в циклах, fmt.Sprintf вместо strconv, копии больших строк, неэффективные map. Порядок работы один: профиль → горячие точки → простые фиксы → повторный замер.
go test -bench=. -benchmem показывает аллокации на операцию — бенчмарки дополняют профиль и защищают от регрессий.18. Сборка и кросс-компиляция
Одна команда собирает бинарник под любую платформу — суперсила Go в деплое и CLI-инструментах:
GOOS=linux GOARCH=amd64 go build -o app-linux .
GOOS=windows GOARCH=amd64 go build -o app.exe .
GOOS=darwin GOARCH=arm64 go build -o app-mac .
# меньше размер + версия в бинарнике:
go build -ldflags="-s -w -X main.version=1.2.3" .Флаги -s -w убирают таблицу символов и уменьшают файл, а -X подставляет значение в переменную — стандарт версионирования CLI-утилит.
Контейнеризация — двухэтапный Dockerfile: стадия сборки и минимальный финальный образ:
FROM golang:1.24 AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o server .
FROM scratch
COPY --from=build /app/server /server
ENTRYPOINT ["/server"]CGO_ENABLED=0 отключает C-зависимости — бинарник статический и работает в любом образе. Для драйверов с C-кодом (SQLite) собирай на целевом окружении.go vet чистые, платформа целевая, версия через ldflags, образ минимален, секреты только в переменных окружения.Что дальше
Ты прошёл весь базовый курс Go: типы, функции, указатели, структуры, интерфейсы, горутины и HTTP. Дальше стоит углубиться в работу с базами через SQL, понять сеть в HTTP и DNS и познакомиться с деплоем в Хостинг и деплой. А для алгоритмического фундамента загляни в Алгоритмы и структуры данных.