LEARN X · ЗА 15 МИН

Go

Go (Golang) за 15 минут: синтаксис, типы, срезы, map, структуры, интерфейсы, горутины и каналы — весь язык на одной странице в примерах кода.

Go (Golang) — компилируемый язык от Google: строгая типизация, простой синтаксис, встроенная конкурентность через горутины и каналы, быстрая компиляция в один бинарник. Весь язык — на одной странице, всё в комментариях кода.

Структура программы

Программа на Go — это набор пакетов. Запускаемая программа всегда содержит package main и функцию main.

// Каждый файл начинается с объявления пакета.
// Исполняемая программа должна быть в пакете main.
package main

// Импорт пакетов стандартной библиотеки.
import "fmt"

// Точка входа — функция main без параметров и возврата.
func main() {
    fmt.Println("Привет, Go!") // Println — вывод строки + перенос строки
    // => Привет, Go!

    fmt.Print("без переноса")        // Print — без переноса строки
    fmt.Printf("%s = %d\n", "x", 42) // Printf — форматированный вывод
    // => x = 42
}

// Запуск:  go run main.go
// Сборка:  go build main.go  ->  бинарник

Переменные и константы

Неиспользованные переменные и импорты — ошибка компиляции. Это особенность Go.

// Полная форма: var имя тип = значение
var age int = 30

// Тип можно опустить — выводится из значения.
var name = "Аня"

// Короткое объявление := (только внутри функций).
count := 10 // тип int выведен автоматически

// Несколько переменных сразу.
var x, y int = 1, 2
a, b := "да", true

// Без инициализации — нулевое значение типа.
var empty int    // 0
var s string     // "" (пустая строка)
var ok bool      // false

// Константы — неизменяемы, объявляются через const.
const Pi = 3.14159
const (
    Red   = 0
    Green = 1
    Blue  = 2
)

// iota — автонумерация констант (0, 1, 2, ...).
const (
    Mon = iota // 0
    Tue        // 1
    Wed        // 2
)

Базовые типы и строки

// Целые числа: int, int8/16/32/64, uint и т.д.
var i int = -7
var u uint = 7

// Числа с плавающей точкой.
var f float64 = 3.14
var f32 float32 = 1.5

// Булев тип.
var flag bool = true

// Строки — неизменяемая последовательность байтов (UTF-8).
var str string = "Привет"
fmt.Println(len(str)) // 12 — длина в БАЙТАХ (кириллица по 2 байта)

// Руна (rune) — один символ Unicode (псевдоним int32).
var r rune = 'Я'
fmt.Println(r)         // 1071 — код символа
fmt.Printf("%c\n", r)  // Я

// Перебор строки по рунам через range.
for index, ch := range "abЯ" {
    fmt.Printf("%d:%c ", index, ch) // 0:a 1:b 2:Я
}

// Конвертация типов — только явная.
var n int = 65
var c string = string(rune(n)) // "A"
var fl float64 = float64(n)     // 65.0

Операторы и условия

// Арифметика: + - * / %  и сравнения == != < > <= >=
// Логика: && || !
x := 7

// if без скобок вокруг условия, но с обязательными {}.
if x > 5 {
    fmt.Println("больше 5")
} else if x == 5 {
    fmt.Println("равно 5")
} else {
    fmt.Println("меньше 5")
}

// if с инициализатором: переменная видна только внутри if/else.
if y := x * 2; y > 10 {
    fmt.Println("y =", y) // y = 14
}

// switch — без break (нет проваливания по умолчанию).
switch x {
case 1, 2, 3:
    fmt.Println("малое")
case 7:
    fmt.Println("семь")
default:
    fmt.Println("другое")
}

// switch без выражения — замена длинным if/else.
switch {
case x < 0:
    fmt.Println("отрицательное")
case x == 0:
    fmt.Println("ноль")
default:
    fmt.Println("положительное")
}

// fallthrough — явно провалиться в следующий case.
switch x {
case 7:
    fmt.Println("семь")
    fallthrough
case 8:
    fmt.Println("и сюда тоже")
}

Циклы (только for)

// В Go ОДИН цикл — for. Он же заменяет while и do-while.

// 1) Классический счётчик.
for i := 0; i < 3; i++ {
    fmt.Print(i, " ") // 0 1 2
}

// 2) Как while — только условие.
n := 3
for n > 0 {
    fmt.Print(n, " ") // 3 2 1
    n--
}

// 3) Бесконечный цикл + break/continue.
i := 0
for {
    if i == 2 {
        i++
        continue // пропустить итерацию
    }
    if i >= 4 {
        break // выйти из цикла
    }
    fmt.Print(i, " ") // 0 1 3
    i++
}

// 4) range — перебор срезов, массивов, map, строк, каналов.
nums := []int{10, 20, 30}
for index, value := range nums {
    fmt.Printf("%d=%d ", index, value) // 0=10 1=20 2=30
}

// Только значения — индекс игнорируем через _.
for _, value := range nums {
    fmt.Print(value, " ") // 10 20 30
}

Массивы и срезы

// Массив — фиксированная длина, часть типа.
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{4, 5, 6} // длина выводится: [3]int
fmt.Println(len(arr))      // 3

// Срез (slice) — динамический «вид» на массив. Используется чаще массивов.
var s []int                // nil-срез, len 0
s = []int{1, 2, 3}
fmt.Println(s, len(s), cap(s)) // [1 2 3] 3 3

// make — создать срез заданной длины (и ёмкости).
buf := make([]int, 2, 5) // длина 2, ёмкость 5
fmt.Println(buf)          // [0 0]

// append — добавление элементов (может вернуть новый массив).
s = append(s, 4, 5)
fmt.Println(s) // [1 2 3 4 5]

// Слайсинг: s[low:high] — полуинтервал [low, high).
fmt.Println(s[1:3]) // [2 3]
fmt.Println(s[:2])  // [1 2]
fmt.Println(s[2:])  // [3 4 5]

// Копирование среза.
dst := make([]int, len(s))
copy(dst, s)

// Двумерный срез.
grid := [][]int{{1, 2}, {3, 4}}
fmt.Println(grid[1][0]) // 3

Maps (словари)

// map — хеш-таблица ключ->значение. Создаём через make или литерал.
ages := make(map[string]int)
ages["Аня"] = 30
ages["Боб"] = 25

// Литерал map.
colors := map[string]string{
    "red":   "красный",
    "green": "зелёный",
}

fmt.Println(ages["Аня"]) // 30
fmt.Println(len(ages))    // 2

// Запрос несуществующего ключа -> нулевое значение типа.
fmt.Println(ages["Нет"]) // 0

// Проверка наличия ключа: запятая-ok.
value, ok := ages["Боб"]
if ok {
    fmt.Println("найден:", value) // найден: 25
}

if _, exists := ages["Ева"]; !exists {
    fmt.Println("Евы нет в map")
}

// Удаление ключа.
delete(ages, "Боб")

// Перебор (порядок НЕ гарантирован!).
for key, val := range colors {
    fmt.Printf("%s=%s ", key, val)
}

Структуры и методы

Методы объявляются вне структуры. Получатель-указатель *T позволяет изменять оригинал.

// struct — составной тип из именованных полей.
type Point struct {
    X, Y int
}

type Person struct {
    Name string
    Age  int
}

// Создание разными способами.
p1 := Point{1, 2}            // по порядку полей
p2 := Point{X: 3, Y: 4}      // по именам
var p3 Point                 // нулевые значения: {0 0}
fmt.Println(p1.X, p2.Y)      // 1 4

// Метод с получателем-значением (копия структуры).
func (p Point) Dist() int {
    return p.X*p.X + p.Y*p.Y
}

// Метод с получателем-указателем — может ИЗМЕНЯТЬ структуру.
func (p *Point) Scale(k int) {
    p.X *= k
    p.Y *= k
}

// Вызов методов.
pt := Point{3, 4}
fmt.Println(pt.Dist()) // 25
pt.Scale(2)
fmt.Println(pt)        // {6 8}

// Встраивание (композиция вместо наследования).
type Employee struct {
    Person      // встроенная структура
    Salary int
}
e := Employee{Person{"Аня", 30}, 1000}
fmt.Println(e.Name) // Аня — поля встроенной доступны напрямую

Функции

// Обычная функция: func имя(параметры) типВозврата.
func add(a int, b int) int {
    return a + b
}

// Множественный возврат — частый приём в Go.
func divmod(a, b int) (int, int) {
    return a / b, a % b
}
q, r := divmod(17, 5) // 3, 2

// Именованные результаты + «голый» return.
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return // вернёт x и y
}

// Variadic — переменное число аргументов.
func total(nums ...int) int {
    s := 0
    for _, n := range nums {
        s += n
    }
    return s
}
fmt.Println(total(1, 2, 3)) // 6
xs := []int{4, 5, 6}
fmt.Println(total(xs...))    // 15 — распаковка среза

// Функция как значение и замыкание (closure).
func counter() func() int {
    c := 0
    return func() int { // захватывает c
        c++
        return c
    }
}
next := counter()
fmt.Println(next(), next()) // 1 2

// defer — отложить вызов до выхода из функции (LIFO порядок).
func demo() {
    defer fmt.Println("3-й (выполнится последним)")
    defer fmt.Println("2-й")
    fmt.Println("1-й")
}
// Вывод: 1-й / 2-й / 3-й

Указатели

Go передаёт аргументы по значению. Чтобы изменить переменную вызывающего, передавайте указатель.

// &amp; — взять адрес переменной. * — разыменовать (получить значение).
x := 10
p := &amp;x        // p — указатель на x, тип *int
fmt.Println(*p) // 10 — значение по адресу

*p = 20         // меняем x через указатель
fmt.Println(x)  // 20

// Нулевое значение указателя — nil.
var ptr *int
fmt.Println(ptr == nil) // true

// Указатели в функциях — изменение оригинала.
func increment(n *int) {
    *n++ // меняем значение по адресу
}
y := 5
increment(&amp;y)
fmt.Println(y) // 6

// new — выделить память, вернуть указатель на нулевое значение.
np := new(int) // *int, *np == 0
*np = 42
fmt.Println(*np) // 42

// В Go НЕТ арифметики указателей (в отличие от C) — это безопаснее.

Интерфейсы

Интерфейсы в Go удовлетворяются неявно — это «утиная типизация» на уровне компиляции.

// Интерфейс — набор сигнатур методов.
type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct{ R float64 }
type Rect struct{ W, H float64 }

// Реализация НЕЯВНАЯ: тип реализует интерфейс,
// просто имея нужные методы (без слова "implements").
func (c Circle) Area() float64      { return 3.14 * c.R * c.R }
func (c Circle) Perimeter() float64 { return 2 * 3.14 * c.R }
func (r Rect) Area() float64        { return r.W * r.H }
func (r Rect) Perimeter() float64   { return 2 * (r.W + r.H) }

// Функция принимает любой Shape.
func describe(s Shape) {
    fmt.Printf("площадь=%.1f\n", s.Area())
}
describe(Circle{R: 2}) // площадь=12.6
describe(Rect{W: 3, H: 4}) // площадь=12.0

// Пустой интерфейс interface{} (или any) — любое значение.
var anything interface{} = "строка"

// Type assertion — извлечь конкретный тип.
if str, ok := anything.(string); ok {
    fmt.Println("это строка:", str)
}

// Type switch — ветвление по динамическому типу.
switch v := anything.(type) {
case int:
    fmt.Println("int", v)
case string:
    fmt.Println("string", v)
default:
    fmt.Println("другой тип")
}

Обработка ошибок

Go не использует try/catch. Ошибки возвращаются явно; panic/recover — только для исключительных случаев.

// В Go ошибки — обычные значения типа error, а не исключения.
import "errors"

// Функция возвращает результат И ошибку.
func safeDiv(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("деление на ноль")
    }
    return a / b, nil
}

// Идиома: проверка if err != nil.
result, err := safeDiv(10, 0)
if err != nil {
    fmt.Println("ошибка:", err) // ошибка: деление на ноль
} else {
    fmt.Println("результат:", result)
}

// fmt.Errorf — ошибка с форматированием и обёрткой (%w).
func load(id int) error {
    return fmt.Errorf("не найден id=%d", id)
}

// panic — аварийное завершение (для невосстановимых ситуаций).
// recover — перехват паники внутри defer.
func mayPanic() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("восстановились после:", r)
        }
    }()
    panic("что-то сломалось")
}
mayPanic() // восстановились после: что-то сломалось
fmt.Println("программа продолжает работу")

Горутины и каналы

Горутины — дешёвые конкурентные функции. Каналы синхронизируют их по принципу «общение через передачу данных».

import (
    "fmt"
    "sync"
)

// Горутина — лёгкий поток. Запуск через ключевое слово go.
func say(s string) { fmt.Println(s) }

go say("асинхронно") // выполнится параллельно

// Канал (channel) — типизированная труба для обмена данными.
ch := make(chan int) // небуферизованный канал

// Отправка ch &lt;- value и приём value := &lt;-ch.
go func() {
    ch &lt;- 42 // отправить в канал
}()
v := &lt;-ch // принять из канала (блокирует до получения)
fmt.Println(v) // 42

// Буферизованный канал — не блокирует, пока есть место.
buf := make(chan int, 2)
buf &lt;- 1
buf &lt;- 2
close(buf) // закрыть канал
for n := range buf { // читаем до закрытия
    fmt.Print(n, " ") // 1 2
}

// select — ждать на нескольких каналах сразу.
c1, c2 := make(chan string), make(chan string)
go func() { c1 &lt;- "от c1" }()
select {
case msg := &lt;-c1:
    fmt.Println(msg)
case msg := &lt;-c2:
    fmt.Println(msg)
default:
    fmt.Println("нет данных")
}

// sync.WaitGroup — дождаться завершения группы горутин.
var wg sync.WaitGroup
for i := 0; i &lt; 3; i++ {
    wg.Add(1) // +1 к счётчику
    go func(id int) {
        defer wg.Done() // -1 по завершении
        fmt.Printf("горутина %d\n", id)
    }(i)
}
wg.Wait() // блокирует, пока счётчик не станет 0
fmt.Println("все горутины завершились")
Поддержать проект