← назад к разделу

Go спроектирован так, чтобы код читался одинаково у всех. В языке мало конструкций, почти нет «синтаксического сахара», а компилятор ругается на лишнее — например, на неиспользуемую переменную или импорт. Эта статья — первое знакомство с тем, как выглядит программа на Go: из чего она собрана, как объявляются переменные и типы, и как работают базовые управляющие конструкции.

Простота как принцип

Главная идея Go — читаемость важнее краткости. Авторы языка сознательно отказались от многих привычных по другим языкам возможностей: нет наследования классов, нет исключений в привычном виде, нет тернарного оператора, есть только один цикл — for. Меньше способов написать одно и то же — меньше споров и проще читать чужой код.

Из этого следует строгость компилятора. Объявил переменную и не использовал — ошибка компиляции, а не предупреждение. Импортировал пакет «на будущее» — тоже ошибка. Поначалу это раздражает, но быстро приучает не оставлять мусора.

Пакеты и func main

Любой файл Go начинается с объявления пакета. Пакет — это единица организации кода: набор файлов в одной директории, которые видят внутренние имена друг друга.

Программа, которую можно запустить, живёт в пакете main и содержит функцию main — точку входа:

package main

import "fmt" // пакет стандартной библиотеки для ввода-вывода

func main() {
    fmt.Println("Привет, Go") // печать строки в консоль
}

Несколько важных деталей сразу:

  • package main + func main() — это то, что делает программу исполняемой. Библиотеки используют другое имя пакета и функции main не имеют.
  • import подключает другие пакеты. fmt — часть стандартной библиотеки.
  • Видимость определяется регистром буквы. Имя с заглавной буквы (Println) экспортируется — доступно из других пакетов. Имя со строчной буквы видно только внутри своего пакета. Отдельных ключевых слов вроде public/private нет.

Объявление переменных: var и :=

Переменную можно объявить двумя способами. Полная форма — через var:

var age int = 30      // тип указан явно
var name = "Анна"     // тип выведен компилятором из значения (string)
var active bool       // без значения — переменная получит нулевое значение

Короткая форма — оператор :=. Он одновременно объявляет переменную и присваивает значение, тип выводится автоматически:

age := 30        // int
name := "Анна"   // string
active := true   // bool

Короткая формула: := работает только внутри функций; var можно использовать и на уровне пакета. На практике внутри функций почти всегда пишут :=, а var оставляют для случаев, когда нужно явно задать тип или объявить переменную без начального значения.

Тип в Go указывается после имени — age int, а не int age. Это непривычно, но читается слева направо: «переменная age типа int».

Базовые типы

Набор встроенных типов небольшой и предсказуемый:

var i int        // целое, размер зависит от платформы (обычно 64 бита)
var i32 int32    // целое фиксированного размера; есть int8/int16/int64
var u uint       // беззнаковое целое
var f float64    // число с плавающей точкой (по умолчанию для дробей)
var b bool       // true / false
var s string     // строка, неизменяемая последовательность байт (UTF-8)
var r rune       // один символ Unicode (псевдоним int32)

Несколько правил, которые экономят время:

  • По умолчанию для целых берут int, для дробных — float64. Конкретные размеры (int32, int64) — когда это важно для формата данных или экономии памяти.
  • Go не делает неявных преобразований между числовыми типами. Сложить int и int64 напрямую нельзя — нужно явно преобразовать: int64(x) + y. Это убирает целый класс скрытых ошибок.
  • string неизменяема: нельзя поменять символ по индексу. Чтобы собрать строку из частей, используют конкатенацию или специальные средства из стандартной библиотеки.

Нулевые значения (zero values)

В Go нет понятия «неинициализированная переменная». Если объявить переменную без значения, она получит нулевое значение своего типа:

var n int      // 0
var f float64  // 0.0
var b bool     // false
var s string   // "" (пустая строка)
var p *int     // nil (для указателей, срезов, map, каналов, функций, интерфейсов)

Это сознательное решение: переменная всегда в осмысленном состоянии, «мусора» из памяти не бывает. Нулевые значения часто делают типы готовыми к работе сразу — например, нулевое значение многих структур из стандартной библиотеки уже можно использовать без дополнительной настройки.

Короткая формула: объявил — уже инициализировано. Думай о нулевом значении как о части дизайна типа, а не как о «пустышке».

Константы

Константы объявляются через const и вычисляются на этапе компиляции. Менять их нельзя:

const Pi = 3.14159
const greeting = "привет"

const (
    StatusOK    = 200
    StatusError = 500
)

Числовые константы в Go нетипизированы, пока их не присвоят переменной. Благодаря этому const Pi = 3.14159 спокойно используется и там, где ждут float64, и там, где ждут float32 — без явного преобразования. Для перечислений в Go используют конструкцию iota, но это тема для более продвинутого разбора.

Управляющие конструкции

if

Скобки вокруг условия не нужны, фигурные скобки обязательны:

if age >= 18 {
    fmt.Println("совершеннолетний")
} else {
    fmt.Println("нет")
}

Удобная особенность — короткий оператор перед условием. Можно объявить переменную прямо в if, и она будет видна только внутри ветвей:

if n := len(name); n > 0 {
    fmt.Println("длина имени:", n)
}
// здесь n уже недоступна

for — единственный цикл

В Go только один цикл — for, но он покрывает все случаи. Классическая форма со счётчиком:

for i := 0; i < 3; i++ {
    fmt.Println(i)
}

Форма «пока условие истинно» (аналог while из других языков):

i := 0
for i < 3 {
    i++
}

Бесконечный цикл — просто for { }. А для перебора коллекций есть for ... range:

names := []string{"Анна", "Борис"}
for index, value := range names {
    fmt.Println(index, value)
}

switch

switch в Go мощнее, чем привычный по другим языкам. По умолчанию нет «проваливания» в следующую ветку — break писать не нужно:

switch day {
case "сб", "вс":
    fmt.Println("выходной")
case "пт":
    fmt.Println("почти выходной")
default:
    fmt.Println("рабочий день")
}

switch можно писать вообще без выражения — тогда ветки case содержат условия. Это чистая замена длинной цепочки if/else:

switch {
case age < 18:
    fmt.Println("несовершеннолетний")
case age < 65:
    fmt.Println("взрослый")
default:
    fmt.Println("пенсионер")
}

Почему в Go нет тернарного оператора

В большинстве языков есть запись вида condition ? a : b. В Go её намеренно нет. Авторы посчитали, что вложенные тернарники быстро становятся нечитаемыми, а одна явная конструкция лучше двух способов делать выбор.

Вместо тернарника пишут обычный if:

label := "нет"
if age >= 18 {
    label = "да"
}

Чуть длиннее — зато читается однозначно. Это хорошая иллюстрация всей философии языка: меньше способов, больше предсказуемости.

Коротко

  • Программа на Go начинается с package; исполняемая программа — это package main и функция func main().
  • Видимость имени определяется регистром первой буквы: заглавная — экспортируется, строчная — видна только в пакете.
  • Переменные объявляют через var (с явным типом) или := (короткая форма, только внутри функций); тип пишется после имени.
  • Каждый тип имеет нулевое значение — переменная без явного значения всегда инициализирована (0, "", false, nil).
  • Неявных преобразований между числовыми типами нет — преобразовывай явно.
  • Цикл один — for, он же заменяет while; switch не «проваливается» и работает без выражения; тернарного оператора нет — используется обычный if.

Что почитать дальше

  • Структуры и методы — как из базовых типов собирать собственные.
  • Срезы и map — основные коллекции языка.
  • Инструменты Go — go run, go build, форматирование и тесты.