Что означает неизменяемость (immutability) строк в Go и какие у этого преимущества?

Ответ

Неизменяемость (immutability) строк в Go означает, что после создания строки ее значение — последовательность байт — не может быть изменено.

Любая операция, которая выглядит как модификация строки (например, конкатенация или получение подстроки), на самом деле создает в памяти новую строку.

// Попытка изменить байт строки напрямую вызовет ошибку компиляции
s := "hello"
s[0] = 'H' // Error: cannot assign to s[0]

// Конкатенация создает новую строку
s2 := s + " world" // s осталась "hello", s2 стала "hello world"

Преимущества неизменяемости

  1. Потокобезопасность (Thread Safety)

    • Поскольку строки нельзя изменить, их можно безопасно передавать между горутинами без использования мьютексов или других примитивов синхронизации. Данные гарантированно не будут повреждены.
  2. Эффективность и экономия памяти

    • При создании подстроки (слайса строки), например sub := s[1:4], Go не копирует данные. Новая строка sub будет указывать на тот же самый базовый массив байт в памяти, что и исходная строка s. Это очень быстро и экономно.
  3. Надежность и предсказуемость

    • Неизменяемые строки можно безопасно использовать в качестве ключей в map. Если бы строки были изменяемыми, изменение значения ключа после его добавления в мапу нарушило бы ее внутреннюю структуру.

Как работать с изменяемыми строками?

Когда требуется многократно изменять строку (например, собирать ее в цикле), создавать каждый раз новую строку неэффективно. Для этого существуют специальные инструменты:

  • strings.Builder: Самый эффективный и предпочтительный способ для построения строк. Он использует внутренний байтовый буфер, который растет по мере необходимости, избегая лишних аллокаций.
    var builder strings.Builder
    for i := 0; i < 10; i++ {
        builder.WriteString("a") // Эффективно
    }
    result := builder.String()
  • Слайс байт ([]byte): Если нужно изменять отдельные байты, строку можно преобразовать в []byte, модифицировать и затем преобразовать обратно.
    s := "hello"
    b := []byte(s)
    b[0] = 'H'
    s = string(b) // s теперь "Hello"
  • Слайс рун ([]rune): Для корректной работы с многобайтными Unicode-символами (например, кириллицей) следует использовать []rune.