Коротко: Go — компилируемый язык со статической типизацией, горутинами и одним бинарником; синтаксически проще PHP, но заставляет иначе думать об ошибках и параллелизме.

Если вы пишете на PHP и присматриваетесь к Go, короткий ответ такой: Go — компилируемый язык со статической типизацией, встроенной конкурентностью (горутины) и одним самодостаточным бинарником на выходе. Синтаксис у него проще, чем у PHP, но он заставляет иначе думать об ошибках, типах и параллелизме. Ниже разберём главные отличия, покажем код и дадим практический путь старта.

Чем Go отличается от PHP

Самое важное, что стоит принять на старте, — это не «другой синтаксис», а другая модель выполнения и мышления:

  • Компиляция в бинарник. Нет интерпретатора и PHP-FPM: go build даёт один исполняемый файл, который просто запускается на сервере. Деплой — это «положить бинарник».
  • Статическая строгая типизация. Типы проверяются на этапе компиляции, и часть ошибок ловится ещё до запуска, а не в проде.
  • Конкурентность «из коробки». Горутины и каналы вместо отдельных процессов или внешних очередей для параллельных задач.
  • Нет классов и наследования. Вместо них — структуры, методы и интерфейсы. В основе лежит композиция, а не иерархии.
  • Ошибки — это значения. По умолчанию никаких исключений: функция возвращает error, и вы обрабатываете его явно.
  • Минимализм и единообразие. Один официальный форматтер (gofmt), маленькая спецификация языка, мало «магии» и неявного поведения.

Первая программа и модули

Go-проект начинается с модуля — это аналог composer.json. Инициализация и «hello world»:

go mod init example.com/hello

// main.go
package main

import "fmt"

func main() {
    fmt.Println("Привет, Go!")
}

Запуск — go run ., сборка — go build. Зависимости подтягиваются автоматически в go.mod и go.sum (аналоги composer.json и composer.lock). Команда go mod tidy наводит порядок: добавляет недостающие и убирает лишние зависимости.

Базовые типы, слайсы и мапы

Вместо привычного PHP-массива «на все случаи жизни» в Go есть отдельные структуры данных. Чаще всего вы будете работать со слайсами (динамические массивы) и мапами (ассоциативные массивы):

nums := []int{1, 2, 3}      // слайс
nums = append(nums, 4)       // добавление элемента

ages := map[string]int{      // мапа (ключ -> значение)
    "Лео": 36,
}
ages["Иван"] = 28

for key, value := range ages {
    fmt.Println(key, value)
}

Важное отличие от PHP: типы элементов фиксированы. В []int нельзя случайно положить строку — компилятор не даст.

Структуры, методы и интерфейсы вместо классов

В Go нет классов. Данные описываются структурами, поведение — методами, а контракты — интерфейсами:

type User struct {
    Name string
    Age  int
}

func (u User) Greeting() string {
    return "Привет, " + u.Name
}

type Greeter interface {
    Greeting() string
}

Главная особенность — интерфейсы в Go неявные: тип реализует интерфейс автоматически, если у него есть нужные методы. Никакого implements не нужно. Это располагает к маленьким интерфейсам и композиции вместо громоздких иерархий наследования, к которым привыкаешь в PHP.

Горутины и каналы: главная фишка

То, ради чего часто и приходят в Go. Горутина — это очень лёгкий поток, запускается ключевым словом go. Каналы (chan) — безопасный способ обмена данными между горутинами:

func main() {
    ch := make(chan string)

    go func() {
        ch <- "готово" // пишем в канал
    }()

    fmt.Println(<-ch)  // читаем из канала (блокируется, пока не появится значение)
}

Когда горутин много и нужно дождаться их всех, помогает sync.WaitGroup:

var wg sync.WaitGroup
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Println("задача", n)
    }(i)
}
wg.Wait() // ждём завершения всех

Там, где в PHP вы бы вынесли задачу в очередь (RabbitMQ или Kafka), в Go часто достаточно горутин и каналов внутри одного процесса. Это не отменяет очереди для распределённых систем, но для параллелизма в рамках сервиса инструмент встроенный.

Обработка ошибок без исключений

Go возвращает ошибку как обычное значение, и вы проверяете её явно сразу после вызова:

data, err := os.ReadFile("config.json")
if err != nil {
    return fmt.Errorf("не смог прочитать конфиг: %w", err)
}
// здесь data гарантированно валидна

Глагол %w «оборачивает» исходную ошибку, сохраняя цепочку причин — потом её можно разобрать через errors.Is и errors.As. После try/catch это сначала кажется многословным, но делает поток ошибок видимым и предсказуемым. Механизмы panic и recover тоже есть, но это для действительно исключительных ситуаций, а не для бизнес-логики.

Тестирование из коробки

Тесты — часть языка, отдельный фреймворк не нужен. Идиоматичный стиль — table-driven тесты:

func TestSum(t *testing.T) {
    cases := []struct {
        a, b, want int
    }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }
    for _, c := range cases {
        if got := Sum(c.a, c.b); got != c.want {
            t.Errorf("Sum(%d,%d)=%d, хотели %d", c.a, c.b, got, c.want)
        }
    }
}

Запуск — go test ./.... Покрытие — go test -cover.

Сборка и деплой одним бинарником

Главный практический плюс для эксплуатации: на выходе один файл без зависимостей рантайма. Типовой multi-stage Dockerfile получается крошечным:

FROM golang:1.22 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/server

FROM alpine:3.20
COPY --from=build /app /app
ENTRYPOINT ["/app"]

Итоговый образ — единицы мегабайт, а не сотни, как с полноценным PHP-окружением.

Когда Go, а когда PHP

  • Go хорош, когда нужны высокая конкурентность, низкое потребление памяти, сетевые сервисы и демоны, CLI-утилиты, самодостаточные бинарники.
  • PHP остаётся сильнее там, где зрелая экосистема веб-фреймворков (Symfony, Laravel), быстрый CRUD, админки, контент-сайты и большой пул библиотек под типовые задачи.

На практике это часто гибрид: основной продукт на PHP, а «горячие» по нагрузке части — на Go.

С чего начать PHP-разработчику

  1. Пройдите интерактивный A Tour of Go — за вечер получите базу синтаксиса.
  2. Перепишите маленькую утилиту, которую обычно делаете на PHP CLI (парсер файла, скрипт-обработчик).
  3. Освойте горутины и каналы на простой задаче — например, параллельные HTTP-запросы с агрегацией результата.
  4. Напишите небольшой HTTP-сервис на стандартном net/http — сначала без фреймворка, чтобы понять основы.
  5. Закройте его table-driven тестами и соберите в Docker одним бинарником.

Go не заменяет PHP, а дополняет его там, где нужны конкурентность и лёгкие сервисы. Если думаете, где Go уместен именно в вашем проекте, а где лучше остаться на PHP — это как раз тема для консультации.