← Назад к вопросам

Что вернет len в строке?

1.0 Junior🔥 171 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Отличный вопрос! Он затрагивает важнейшую концепцию работы со строками в Go.

Короткий ответ: len() для строки в Go возвращает количество байт (bytes), которые занимает эта строка в памяти, а не количество символов (runes), как можно было бы ожидать.

Подробное объяснение

Это поведение напрямую вытекает из внутреннего представления строк в Go. Строка (string) — это неизменяемая (immutable) последовательность байт ([]byte), которая может содержать текст в произвольной кодировке, но по умолчанию и чаще всего используется UTF-8.

Функция len(s) является встроенной и для строк работает с их низкоуровневым представлением:

package main

import "fmt"

func main() {
    s1 := "Hello"
    fmt.Println(len(s1)) // Вывод: 5. Каждый символ ASCII занимает 1 байт.

    s2 := "Привет"
    fmt.Println(len(s2)) // Вывод: 12, а не 6!
}

Во втором случае строка "Привет" состоит из 6 символов (рун), но в кодировке UTF-8 каждый кириллический символ кодируется 2 байтами. Следовательно, 6 символов * 2 байта = 12 байт. Именно это число и возвращает len().

Почему это важно и как правильно работать с символами?

Использование len() для определения длины строки в символах — классическая ошибка новичков в Go. Это может сломать логику при обрезке строк, валидации длины ввода пользователя и т.д.

Для корректной работы с юникодными символами (рунами) необходимо преобразовать строку в срез рун ([]rune) и использовать len() для этого среза, либо воспользоваться пакетом utf8.

Способ 1: Преобразование в []rune

Этот метод создает в памяти новый срез, содержащий все декодированные руны строки. Подходит для относительно коротких строк или когда требуется многократный доступ к символам по индексу.

package main

import "fmt"

func main() {
    s := "Привет, мир!"
    runeSlice := []rune(s)

    fmt.Println("Длина в байтах (len(string)):", len(s))       // Вывод: 20
    fmt.Println("Длина в символах (len([]rune)):", len(runeSlice)) // Вывод: 12
    fmt.Println("Первый символ:", string(runeSlice[0]))       // Вывод: "П"
}

Способ 2: Использование utf8.RuneCountInString()

Функция из стандартного пакета unicode/utf8. Это предпочтительный и наиболее эффективный способ, так как она не выделяет новую память под срез, а просто проходит по строке, подсчитывая количество UTF-8 последовательностей (рун).

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "Привет, мир!"
    charCount := utf8.RuneCountInString(s)

    fmt.Println("Длина в байтах (len):", len(s))                // 20
    fmt.Println("Длина в символах (RuneCountInString):", charCount) // 12
}

Практические примеры различий

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    // Эмодзи и сложные символы часто занимают больше байт.
    s := "Go🔥"
    fmt.Printf("Строка: '%s'\n", s)
    fmt.Printf("len(): %d (байт)\n", len(s))               // Вывод: 5 (G(1) + o(1) + 🔥(3 байта в UTF-8))
    fmt.Printf("Рун: %d\n", utf8.RuneCountInString(s))    // Вывод: 3 (G, o, 🔥)

    // Неправильная обрезка по байтам может испортить строку.
    badSubstring := s[:4] // Берем первые 4 байта из 5.
    fmt.Println("Неправильная обрезка (s[:4]):", badSubstring) // Может вывести "Go" - битый символ.

    // Правильная обрезка требует итерации по рунам.
    runes := []rune(s)
    correctSubstring := string(runes[:2]) // Берем первые 2 руны.
    fmt.Println("Правильная обрезка (первые 2 руны):", correctSubstring) // Вывод: "Go"
}

Итог и ключевые выводы

  • len(string) — это операция O(1), которая возвращает длину базового среза байт. Она мгновенна и не зависит от содержимого строки.
  • utf8.RuneCountInString(string) или len([]rune(string)) — это операции O(N), которые требуют прохода по всей строке для декодирования UTF-8 и подсчета символов.
  • Всегда четко определяйте, что вам нужно: количество байт или количество символов. Для проверки лимита размера данных (например, для отправки по сети) подойдет len(). Для проверки длины логина пользователя, обрезки текста в UI или любой другой семантической работы с текстом обязательно используйте подсчет рун.
  • Строки в Go — это контейнеры для байт в кодировке UTF-8. Это design-решение делает язык эффективным для низкоуровневых операций, но возлагает на разработчика ответственность за корректную обработку юникода на более высоком уровне.

Понимание этого различия — один из краеугольных камней для написания корректного и интернационализированного кода на Go.