Числовые типы и специфика работы со строками (UTF-8 vs Bytes)

В TypeScript тип number — это универсальный «черный ящик». В Go вы сами управляете памятью. Тип данных здесь определяет физический размер контейнера. Если попытаться сложить числа разных типов, компилятор выдаст ошибку. Это реализация принципа «Явное лучше неявного».

Числа: выбираем размер контейнера

На бэкенде важно контролировать потребление ресурсов, особенно в высоконагруженных системах.

Целые числа (Integers)

Они делятся на знаковые (со знаком +/-) и беззнаковые (только положительные).

  1. Фиксированный размер:
    • int8, int16, int32, int64: Число означает количество бит. int64 (8 байт) незаменим для ID пользователей или хранения денежных сумм в минимальных единицах (копейках, центах).
    • uint8, uint16, uint32, uint64: Беззнаковые типы. uint8 часто используют для работы с «сырыми» байтами данных.
  2. Архитектурные типы:
    • 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-й байт попадет на середину кириллической буквы, строка «сломается» и фронтенд получит битый символ.
  • Безопасно:
    1. Преобразовать string в срез рун []rune.
    2. Обрезать срез рун.
    3. Вернуть результат в string.

Этот метод гарантирует, что вы не «разрежете» символ пополам.

Мы разобрались, как выделять память и работать с текстом. Но что будет, если создать переменную и не присвоить ей значение? В TypeScript мы бы получили undefined и риск упасть в рантайме. В Go всё иначе — в следующей теме мы изучим Zero values и узнаем, почему в этом языке не бывает «неопределенных» состояний.