Строка в Go это неизменяемый срез байт. Поэтому подстрока берётся так же, как срез, но с одной оговоркой. Индексы считаются в байтах, а не в символах. На ASCII разницы не видно, на кириллице или эмодзи всё ломается.
Срез по байтам
s := "hello world"
fmt.Println(s[0:5]) // hello
fmt.Println(s[6:]) // world
Такой срез не копирует данные, он ссылается на тот же массив байт. Работает за O(1).
Теперь то же самое с кириллицей:
s := "привет"
fmt.Println(s[0:3]) // "пр"? нет, это "п" с хвостом
Каждая кириллическая буква в UTF-8 занимает два байта, так что
s[0:3] разрежет символ пополам и вы получите битые данные.Срез по символам
Если нужны именно символы, переводим строку в срез рун:
r := []rune("привет")
fmt.Println(string(r[0:3])) // приЗдесь уже есть аллокация и проход по всей строке, то есть O(n). Для длинных строк в горячем цикле это заметно.
🐸 Библиотека Go для собеса