Числовые типы и специфика работы со строками (UTF-8 vs Bytes)
В TypeScript тип number — это универсальный «черный ящик». В Go вы сами управляете памятью. Тип данных здесь определяет физический размер контейнера. Если попытаться сложить числа разных типов, компилятор выдаст ошибку. Это реализация принципа «Явное лучше неявного».
Числа: выбираем размер контейнера
На бэкенде важно контролировать потребление ресурсов, особенно в высоконагруженных системах.
Целые числа (Integers)
Они делятся на знаковые (со знаком +/-) и беззнаковые (только положительные).
- Фиксированный размер:
int8,int16,int32, int64: Число означает количество бит. int64 (8 байт) незаменим для ID пользователей или хранения денежных сумм в минимальных единицах (копейках, центах).- uint8,
uint16,uint32,uint64: Беззнаковые типы. uint8 часто используют для работы с «сырыми» байтами данных.
- Архитектурные типы:
intиuint: Размер зависит от процессора (на 64-битных серверах — 64 бита). Это стандарт для счетчиков и индексов массивов.
Дробные числа (Floating Point)
Для работы с дробями используйте float32 или float64. В серверной разработке стандартом считается float64 из-за высокой точности.
💳 Золотое правило: никогда не используйте
floatдля финансов. Из-за специфики хранения данных в памяти0.1 + 0.2не всегда равно0.3. Для денег используйте целые числа, например int64, сохраняя баланс в копейках.
Строгая типизация и Type conversion
Go запрещает автоматическое приведение типов. Вы не сможете прибавить int8 к int32 без явного указания. Это называется Type conversion.
Сравните подход Go с гибкостью TypeScript:
var small int8 = 10
var big int32 = 100
// result := small + big // Ошибка: разные типы
result := int32(small) + big // Верно: приводим к общему типу
Явное приведение исключает случайные переполнения и делает логику прозрачной. Как показано на Схеме 1, такой подход служит защитным барьером для ваших данных.
Строки, байты и кодировка
Во фронтенде строка — это массив символов. В Go string — это неизменяемый срез байт.
Go использует UTF-8 encoding по умолчанию. В этой кодировке символы имеют разную длину: латиница занимает 1 байт, а кириллица — 2 байта и более.
str := "Go"
fmt.Println(len(str)) // 2
ruStr := "Го"
fmt.Println(len(ruStr)) // 4!
Функция len() считает байты, а не буквы. Обратившись к ruStr[0], вы получите не букву «Г», а лишь первый байт её кода.
Руны (Rune): работаем с текстом правильно
Для корректной обработки Unicode существует тип Rune.
Rune — это псевдоним для int32. Она хранит один полноценный символ, независимо от его веса в байтах. Чтобы правильно перебрать строку с кириллицей, используйте цикл range. Он автоматически декодирует UTF-8 encoding.
word := "Привет"
// Итерация по рунам
for _, r := range word {
fmt.Printf("%c ", r) // П р и в е т
}
// Получение реальной длины через срез рун
runes := []rune(word)
fmt.Println(len(runes)) // 6
Практика: безопасная обрезка текста
Представьте задачу: обрезать заголовок статьи до 10 символов.
- Опасно: обрезать строку по байтам
title[:10]. Если 10-й байт попадет на середину кириллической буквы, строка «сломается» и фронтенд получит битый символ. - Безопасно:
- Преобразовать
stringв срез рун[]rune. - Обрезать срез рун.
- Вернуть результат в
string.
- Преобразовать
Этот метод гарантирует, что вы не «разрежете» символ пополам.
Мы разобрались, как выделять память и работать с текстом. Но что будет, если создать переменную и не присвоить ей значение? В TypeScript мы бы получили undefined и риск упасть в рантайме. В Go всё иначе — в следующей теме мы изучим Zero values и узнаем, почему в этом языке не бывает «неопределенных» состояний.