Как преобразовать слайс байт в строку в Go
Что проверяют
Синтаксис string(b) знают все, поэтому вопрос на самом деле про устройство строк в Go: что при конверсии происходит с памятью и что будет с байтами, которые не являются корректным UTF-8. Следом часто просят преобразование без копирования.
Как отвечать
Базовый ответ: s := string(b). Дальше объясните, что за ним стоит:
- копирование: строка в Go неизменяема, а слайс нет. Поэтому конверсия выделяет новую память и копирует байты, иначе изменение слайса поменяло бы строку. Обратное
[]byte(s)тоже копирует; - оптимизации компилятора: в ряде случаев копии нет. Поиск в мапе
m[string(b)], сравнениеstring(b) == "ok", циклfor range string(b)не выделяют память, компилятор видит, что строка не переживёт выражение; - UTF-8: конверсия не проверяет содержимое и никогда не падает, строка хранит те же байты. Замена на
U+FFFDпоявляется позже, приfor rangeпо строке или при декодировании рун. Проверить заранее можно черезutf8.Valid(b), почистить черезstrings.ToValidUTF8; - без копирования: с Go 1.20 есть
unsafe.String(unsafe.SliceData(b), len(b)). Это законно, только если слайс больше никто не меняет, иначе нарушается неизменяемость строки и появляются трудноуловимые баги. Так устроенString()уstrings.Builder, и там инвариант держит сам тип.
Если строку собирают по кусочкам, берите сразу strings.Builder: он отдаёт результат без лишней копии, в отличие от bytes.Buffer с его String().
Где спотыкаются
Говорят, что при некорректных байтах конверсия вернёт ошибку или испортит данные. Байты сохранятся как есть. И помните, что len(s) считает байты, а не символы; для числа символов нужен utf8.RuneCountInString.
Лезут в unsafe ради микрооптимизации без профиля. Аллокация на конверсии редко бывает узким местом, а риск сломать строку после изменения исходного буфера вполне реальный.
Путают string(b) с конверсией целого числа. string(65) даёт "A", а не "65", и go vet предупреждает о таких конверсиях. Число в строку переводят через strconv.Itoa.