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

ОСНОВЫ ЯЗЫКА: GO Синтаксис и типы

Обязательно

Синтаксис и типы Go: первое знакомство

Go

Пакеты, func main, var и :=, базовые типы, нулевые значения, константы, if/for/switch и почему в Go нет тернарного оператора.

1. Программа собирается из пакета и функции main

1 / 5

Файл начинается с package. Исполняемая программа использует package main и функцию main, библиотеки называются иначе.

  • Файлы одной директории образуют пакет.
  • import подключает стандартную или внешнюю библиотеку.
  • Заглавная буква экспортирует имя, строчная оставляет его внутри пакета.
package main func main()точка входа Println - видно helper - внутри Регистр первой буквы = видимость

2. var и := объявляют переменные по-разному

2 / 5

Тип пишется после имени: age int. Полная форма var задаёт тип явно или оставляет нулевое значение, короткая := выводит его из значения.

  • var age int = 30 - явный тип.
  • name := "Анна" - вывод string.
  • var работает и на уровне пакета, := только внутри функций.
var age int = 30тип явно age := 30тип из значения age - int Неиспользованная переменная = ошибка компиляции

3. Базовые типы не преобразуются скрыто

3 / 5

Для целых обычно берут int, для дробных float64, для текста string, для Unicode-символа rune. Размеры int32 и int64 выбирают осознанно.

  • bool хранит только true или false.
  • string неизменяема, поэтому собирают новую строку.
  • Сложить int и int64 напрямую нельзя: сначала int64(x).
int float64 string bool x - int int64(x) + y Преобразование всегда явное

4. Нулевое значение инициализирует всё

4 / 5

В Go нет неинициализированной переменной. Без явного значения она получает осмысленное нулевое: 0, "", false или nil.

  • Числа и дроби начинаются с нуля, строка - пустая, bool - false.
  • Указатели, срезы, map, каналы, функции и интерфейсы получают nil.
  • Константы вычисляются при сборке, перечисления делают через iota.
int = 0 string = "" bool = false *T = nil iota = 0 = 1 = 2 Объявил - уже инициализировано

5. if, for и switch без лишнего синтаксиса

5 / 5

Условия пишутся без круглых скобок, фигурные обязательны. Вместо тернарного оператора используют обычный if - читаемость важнее краткости.

  • for единственный цикл: счётчик, условие, бесконечный и range.
  • switch не проваливается вниз и работает без выражения.
  • Строка - байты: len считает байты, range идёт по рунам.
if label = "да" for switch range: 0,2,4 Тернарного оператора и break не нужны

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)
}

С Go 1.22 range умеет считать сам: for i := range 3 даёт 0, 1, 2, а если счётчик не нужен — просто for range 3.

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 = "да"
}

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

Дополнительно: при первом чтении можно пропустить

Глубже: перечисления через iotaрасширенное

В Go нет enum. Вместо него — свой тип на основе int и блок констант, где iota выдаёт 0, 1, 2… по порядку строк:

type Weekday int

const (
    Sunday Weekday = iota // 0
    Monday                // 1
    Tuesday               // 2
)

Тип нужен не для красоты: функция, которая принимает Weekday, не примет случайное число 7 без явного преобразования, а switch по такому типу читается как по перечислению. Строкового имени у константы нет — его печатают методом String() на этом типе; сгенерировать такой метод умеет утилита stringer.

Глубже: строки, байты и рунырасширенное

Строка в Go — это байты, а не символы. Для латиницы это незаметно, для кириллицы всплывает сразу: len считает байты, а буква в UTF-8 занимает два.

живой пример

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "привет"
    fmt.Println(len(s))                    // 12: байтов
    fmt.Println(utf8.RuneCountInString(s)) // 6: символов
    for i, r := range s {
        fmt.Println(i, string(r)) // индекс байта и символ
    }
}
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Символ здесь называется руной (rune, тот же int32), а range по строке идёт именно по рунам и отдаёт индекс первого байта каждой. Индексация s[0] возвращает байт (byte, он же uint8), а не букву; резать строку по байтовым индексам посреди символа нельзя. Собирать длинную строку в цикле через + дорого — каждое сложение создаёт новую строку; для этого есть strings.Builder.

Глубже: вариативные функции, init и затенениерасширенное

Функция может принимать сколько угодно аргументов одного типа, если последний параметр объявлен с многоточием: func sum(nums ...int) int. Внутри nums это обычный срез, пустой при вызове без аргументов, а готовый срез передают с многоточием после имени: sum(s...). Так устроены fmt.Println и append.

Порядок запуска пакета фиксирован: сначала инициализируются пакеты, которые он импортирует, потом его переменные уровня пакета в порядке зависимостей, потом функции init, и только после всех пакетов начинается main. Функций init в пакете может быть несколько, звать их руками нельзя. На этом держится анонимный импорт import _ "net/http/pprof": пакет подключают ради его init, который регистрирует обработчики, а имён из него не используют.

Затенение (shadowing) это объявление переменной с тем же именем во вложенном блоке. Чаще всего ловят err - переменную с ошибкой (что это за тип, подробно в статье Ошибки; здесь важно само затенение):

живой пример

package main

import (
	"errors"
	"fmt"
)

func load() (int, error) { return 0, errors.New("нет данных") }

func main() {
	var err error
	if v, err := load(); err == nil {
		fmt.Println("значение", v)
	}
	fmt.Println(err) // <nil>: внутри if была другая err
}
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Оператор := в заголовке if создал новую err, живущую только внутри блока, и внешняя осталась nil. Компилятор это разрешает, go vet по умолчанию молчит, ловит отдельный анализатор shadow и внимательность на ревью: во вложенном блоке с := стоит проверять, не объявлена ли переменная с таким именем снаружи.

Коротко

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

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