Строки, руны и пакеты strings/strconv

Строки, байты и руны: модель UTF-8 в Go

Содержание курса

В предыдущих уроках вы работали со срезами []T и картами map[K]V. Строки в Go выглядят привычно, но устроены иначе — и это несоответствие между внешней простотой и внутренней моделью легко упустить. Разберём, что на самом деле хранит тип string.

string как последовательность байт: len, s[i] и отсутствие гарантии UTF-8

Тип string в Go — это неизменяемая последовательность байт. Не символов, не рун, не кодовых точек Unicode — именно байт, от 0 до 255 каждый.

Посмотрим, что происходит с кириллической строкой:

package main

import "fmt"

func main() {
    s := "Привет"
    fmt.Println(len(s))  // 12
    fmt.Println(s[0])    // 208  (первый байт буквы 'П' в UTF-8)
    fmt.Printf("%T\n", s[0]) // uint8
}

len(s) вернул 12, хотя в слове «Привет» шесть букв. Причина: каждая из этих кириллических букв кодируется двумя байтами в UTF-8, который использует исходный файл Go. Шесть букв × два байта = двенадцать. len об этом ничего не знает — он просто считает байты.

s[0] вернул 208 — это первый байт буквы «П» (0xD0). Тип результата — uint8, он же byte. Это сырой байт, не символ.

Присваивание s[0] = 'p' не скомпилируется:

cannot assign to s[0] (value of type byte)

Строка неизменяема. Индекс даёт доступ на чтение, но не на запись.

Важный нюанс: кодировка не гарантирована. Исходный файл с кириллическим литералом компилятор кодирует в UTF-8, но можно создать строку с произвольными байтами через escape-последовательности:

s2 := "\xFF\xFE\x00"  // три байта, не является корректным UTF-8
fmt.Println(len(s2))  // 3

Go примет её без ошибок. Тип string не знает ни о какой кодировке — он просто держит байты. Это принципиальное решение языка: строки пригодны для хранения любых двоичных данных, не только текста.