1. Основы синтаксиса, переменные и базовые типы данных в Go
Основы синтаксиса, переменные и базовые типы данных в Go
В спецификации языка Go всего 25 ключевых слов. Для сравнения: в C++ их больше 90, в Java — более 50, в Rust — около 40. Эта аскетичность не является признаком слабости языка. Напротив, она отражает главную философию Go: код должен легко читаться, однозначно пониматься и не скрывать от разработчика стоимость выполняемых операций. Язык проектировался так, чтобы программист тратил время на архитектуру системы, а не на расшифровку многослойных синтаксических конструкций или неявных преобразований типов.
Go был создан в недрах Google инженерами, стоявшими у истоков операционной системы Unix и кодировки UTF-8.
!Кен Томпсон, один из создателей Go
Их задачей было решить проблемы масштабной разработки: долгое время компиляции, сложный контроль зависимостей и трудности при написании многопоточного кода. Результатом стал компилируемый, строго типизированный язык, который выглядит почти так же просто, как скриптовые языки, но работает со скоростью, близкой к C.
Анатомия программы на Go
Любая программа начинается с определения пакета и функции main. Это точка входа, с которой операционная система начинает выполнение скомпилированного бинарного файла.
Первая строка package main сообщает компилятору, что этот файл должен быть скомпилирован в исполняемый файл, а не в разделяемую библиотеку. Если бы мы написали package mathutils, компилятор создал бы пакет, который можно импортировать в другие программы, но запустить его напрямую было бы невозможно.
Блок import "fmt" подключает пакет из стандартной библиотеки Go. fmt (сокращение от format) отвечает за форматированный ввод и вывод. В Go действует жёсткое правило: если пакет импортирован, но не используется в коде, программа не скомпилируется. Это защищает кодовую базу от накопления «мёртвого» кода и раздувания бинарных файлов.
Объявление переменных и философия Zero Values
В Go существует строгая статическая типизация. Тип переменной определяется на этапе компиляции и не может быть изменён во время работы программы. Есть два основных способа объявить переменную.
Первый — явное объявление с использованием ключевого слова var:
Второй — краткое объявление с помощью оператора :=, который позволяет компилятору самостоятельно вывести тип на основе присваиваемого значения:
Краткое объявление := используется в 90% случаев при написании кода внутри функций. Однако у него есть ограничение: его нельзя использовать на уровне пакета (вне функций). Глобальные переменные всегда объявляются через var.
Одной из важнейших концепций безопасности памяти в Go является механизм «нулевых значений» (Zero Values). В таких языках, как C или C++, если объявить переменную и не присвоить ей значение, она будет содержать «мусор» — случайный набор битов, оставшийся в этой ячейке памяти от предыдущих операций. В Go неинициализированных переменных не существует.
Если вы объявляете переменную, но не задаёте ей значение, среда выполнения Go автоматически очищает выделенную память и присваивает переменной дефолтное значение для её типа:
0.false."".nil.Это исключает целый класс уязвимостей и багов, связанных с непредсказуемым поведением неинициализированной памяти.
Базовые типы данных: точность и контроль памяти
В отличие от языков с динамической типизацией, где есть просто «число», Go предоставляет детальный контроль над тем, сколько памяти занимает переменная.
Целочисленные типы
Go предлагает платформо-независимые типы с фиксированным размером:
int8, int16, int32, int64.uint8, uint16, uint32, uint64.Число в названии типа указывает на количество выделяемых бит. Например, uint8 может хранить значения от до (то есть до 255). Тип int8 использует один бит для знака, поэтому его диапазон от до .
Помимо них существуют платформо-зависимые типы: int и uint. Их размер зависит от архитектуры процессора, под которую компилируется программа. На 64-битной системе int будет занимать 64 бита (8 байт), на 32-битной — 32 бита (4 байта).
По умолчанию, когда вы пишете count := 42, компилятор использует тип int. Использовать типы с фиксированным размером (например, int16) имеет смысл только в трёх случаях:
Числа с плавающей точкой
Для работы с дробными числами предусмотрены типы float32 и float64, реализующие стандарт IEEE-754. По умолчанию при кратком объявлении pi := 3.14 выводится тип float64.
Важно понимать, что числа с плавающей точкой не могут абсолютно точно представлять некоторые десятичные дроби из-за особенностей двоичной арифметики. Операция в Go, как и в большинстве других языков, не будет равна ровно . Для финансовых вычислений, где недопустима потеря даже долей цента, базовые float типы не применяются — вместо них используют целые числа (храня сумму в копейках/центах) или специализированные пакеты для десятичной арифметики.
Строки, байты и руны
Строки в Go (string) обладают двумя критически важными свойствами: они неизменяемы (immutable) и под капотом представляют собой просто массив байтов.
Неизменяемость означает, что после создания строки вы не можете изменить её отдельный символ. Конструкция str[0] = 'A' вызовет ошибку компиляции. Чтобы изменить строку, необходимо создать новую. Это делает строки потокобезопасными: их можно без страха передавать между разными горутинами.
Второе свойство часто становится ловушкой для новичков. Встроенная функция len() возвращает не количество символов в строке, а количество байт. Поскольку Go нативно использует кодировку UTF-8, один символ может занимать от 1 до 4 байт.
Чтобы корректно работать с символами Unicode, в Go введён тип rune (руна). Технически rune — это просто псевдоним (alias) для типа int32. Он используется для представления одного Unicode-символа, независимо от того, сколько байт этот символ занимает в памяти.
!Внутреннее устройство строки в Go: байты против рун
Если нужно посчитать именно количество символов, строку необходимо преобразовать в срез рун или использовать пакет unicode/utf8:
Строгая типизация и явные преобразования
Go категорически отвергает неявные преобразования типов. Если у вас есть переменная типа int32 и переменная типа int64, вы не можете их сложить, даже если математически это имеет смысл.
Компилятор не будет пытаться угадать, к какому типу привести результат, чтобы не допустить скрытой потери данных (например, при неявном приведении 64-битного числа к 32-битному). Разработчик обязан явно указать преобразование с помощью синтаксиса Type(value):
Это правило касается абсолютно всех типов. Нельзя использовать int там, где ожидается float64, и нельзя использовать число 1 как логическое true. Такая педантичность компилятора на этапе написания кода экономит сотни часов отладки в продакшене.
Константы и магия нетипизированности
Константы в Go объявляются ключевым словом const и вычисляются исключительно на этапе компиляции. Вы не можете присвоить константе результат выполнения функции, так как функция выполняется в рантайме.
Здесь кроется одна из самых мощных и элегантных особенностей Go — нетипизированные константы (untyped constants). В примере выше daysInWeek не имеет жёстко заданного типа int. Она существует в идеальном математическом пространстве компилятора.
Благодаря этому, нетипизированную константу можно использовать в выражениях с любыми совместимыми типами без явного преобразования:
Более того, нетипизированные константы могут хранить числа, значительно превышающие лимиты стандартных типов. Например, выражение const huge = 1 << 100 (единица со сдвигом на 100 бит влево) успешно скомпилируется, хотя такое число не поместится даже в uint64. Ошибка возникнет только в том случае, если вы попытаетесь присвоить эту константу переменной, чей тип не способен вместить данное значение.
Область видимости и соглашения об именовании
В Go нет ключевых слов public, private или protected для управления видимостью переменных и функций. Вместо этого используется предельно простое правило, основанное на регистре первой буквы имени.
Если имя переменной, константы, функции или типа начинается с заглавной буквы (например, User, CalculateTotal, MaxConnections), этот идентификатор экспортируется. Он становится доступен (видим) для импорта и использования в других пакетах.
Если имя начинается со строчной буквы (например, user, calculateTotal, maxConnections), оно является неэкспортируемым (приватным). Доступ к нему возможен только внутри того пакета, где оно объявлено.
Внутри самих функций область видимости ограничивается фигурными скобками {}. Переменная, объявленная внутри блока if или цикла for, перестанет существовать сразу после выхода из этого блока.
В Go принято использовать camelCase для именования переменных. Использование snake_case (с подчёркиваниями) считается нарушением идиоматики языка и вызовет предупреждения у встроенных линтеров. Также в сообществе Go принято давать переменным короткие имена, если их область видимости невелика (например, i для индекса цикла, w для http.ResponseWriter), и более описательные имена, если переменная используется на протяжении большой функции или экспортируется из пакета.
Язык Go с самого начала проектировался так, чтобы минимизировать когнитивную нагрузку при чтении кода. Отсутствие неявных приведений типов заставляет разработчика быть точным в намерениях. Концепция нулевых значений защищает от случайного использования неинициализированной памяти. А чёткое разделение между байтами и рунами не позволяет игнорировать сложность работы с современными текстовыми кодировками. Эти базовые правила формируют фундамент, на котором строятся надёжные и предсказуемые системы.