Строка — неизменяемая последовательность байтов.

len()

len() возвращает кол-во байтов (не символов) в строке.

s := "hello, world"
fmt.Println(len(s)) // 12

Indexing and slicing

Операция индексирования s[i] возвращает iбайт строки s (где 0 <= i <= len(s)).
iбайт строки не обязательно является iсимволом строки, ибо кодирока UTF-8 (не ASCII) требует 2 или более байтов.

s := "hello, world"
fmt.Println(s[0], s[7]) // 104 119 ('h' 'w')
 
fmt.Println(s[0:5]) // "hello"
fmt.Println(s[:5])  // "hello"
fmt.Println(s[7:])  // "world"
fmt.Println(s[:])   // "hello, world"

Immutability

Т.к. строки неизменяемы, изменить данные строки не получится:

s[0] = 'L' // cannot assign to s[0] (neither addressable nor a map index expression)

Другие профиты иммутабельности

Иммутабельность также означает, что две копии строки могут безопасно храниться в одном и том же участке памяти, что делает копирование строки любой длины очень дешевой операцией.
Строка s и ее подстроки (s[7:], например) могут также безопасно разделять одни и те же данные в памяти, поэтому операция получения подстроки тоже очень дешевая.

Raw string literal

Неформатированный строковый литерал (raw string literal) записывается с помощью символов backtick (`).
Внутри управляющие последовательности не обрабатываются (кроме удалении символа возврата каретки (CR или \r)), оставаясь в сыром виде.

Это особенности дают возможность:

  • записывать RegEx-ы, в которых бывает много символов backslash (\)
  • шаблоны HTML
  • литералы JSON
  • usage messages
    const GoUsage = `Go is a tool for working with Go source code.
    Usage:
    go command [arguments].
    ...`

UTF-8

Каждому символу Unicode назначен стандартный номер — код символа Unicode (Unicode code point), или, руна (rune) в терминологии Go.

int32естественный тип данных для хранения отдельной руны, который и используется в Go с синонимом (alias) типа rune. И, по идее, можно последовательность рун представлять как последовательность значений int32. Это представление называется UTF-32 или UCS-4, где каждый символ имеет одинаковый размер в 32 бита. Но проблема в том, что это кодирование использует сильно больше необходимой памяти, ибо большинство текстов в компьютерах используют только символы ASCII, которой требуется только 8 битов (1 байт) на символ.

Решение — UTF-8, кодировка переменной длины символов Unicode в виде байтов. Создана была двумя из создателей Go, Ken Thompson и Rob Pike, и является на данный момент стандартом Unicode.
Она использует от 1 до 4 байтов для представления каждой руны, но:

  • только 1 байт для символов ASCII, что делает его идентичным и совместимым с ASCII
  • 2 или 3 байта для болшинства распространенных рун

Unicode control characters

Многие символы Unicode трудно набирать на клавиатуре, поэтому есть управляющие последовательности Unicode, которые позволяют записывать символы Unicode с помощью их числового кода:

  • \uhhhh для 16-разрядных значений
  • \Uhhhhhhhh для 36-разрядных (где каждое hшестнадцатеричная цифра)
    Необходимость в этой разновидности управляющей последовательности Unicode возникает очень редко.

Ниже преведены строковые литералы, представляющие одну и ту же 6-байтовую строку:

"世界"
"\xe4\xb8\x96\xe7\x95\x8c"
"\u4e16\u754c"
"\U00004e16\U0000754c"

Это просто альтернативные записи одного и того же значения, то есть, они равны:

fmt.Println(
	"世界" == "\xe4\xb8\x96\xe7\x95\x8c",
	"世界" == "\u4e16\u754c",
	"世界" == "\U00004e16\U0000754c",
)
// true true true

Тоже самое можно использовать для литералов рун:

''
'\u4e16'
'\U00004e16'

Корректность литералов рун, значение которых больше 256

Руна, значение которой меньше 256, может быть записана с помощью одной шестнадцатеричной управляющей последовательности, типа '\x41' для 'A', но для более высоких значений нужно использовать \u или \U.
То есть, \xe4xb8\x96 не является корректным литералом руны, хоть и эти три байта являются корректной кодировкой UTF-8 одного символа Unicode.

UTF-8 encoding and decoding

Operations without decoding

Многие строковые операции не требуют декодирования. Например, можно проверить, не является ли одна строка префиксом другого:

func HasPrefix(s, prefix string) bool {
	return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}

суффиксом:

func HasSuffix(s, suffix string) bool {
	return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}

подстрокой:

func Contains(s, substr string) bool {
	for i := range len(s) {
		if HasPrefix(s[i:], substr) {
			return true
		}
	}
	return false
}

используя для закодированного с помощью UTF-8 текста ту же логику, что и для обычной последовательности байтов (для других кодировок это было бы неверным решением). (Приведенные функции выше взяты из пакета strings, хотя Contains там использует хэширование для более эффективного поиска.)

Operations with decoding

Но работать с отдельными символами Unicode нужно по-другому. Ниже приведен пример представления в памяти двух иероглифов, где строка содержит 13 байт, но UTF-8 интерпретация показывает только 9 закодированных кодов Unicode (рун).

import "unicode/utf8"
 
s := "Hello, 世界"
fmt.Println(len(s))                    // 13
fmt.Println(utf8.RuneCountInString(s)) // 9

Для работы с этими символами нужно декодирование UTF-8.

for i := 0; i < len(s); {
	r, size := utf8.DecodeRuneInString(s[i:])
	fmt.Printf("%d\t%c\n", i, r)
	i += size
}
/*
	0       H
	1       e
	2       l
	3       l
	4       o
	5       ,
	6
	7       世
	10      界
*/

Размер использется для обновления индекса байтов i для следующей руны в строке:

i += size

Но это нелепое решение. К счастью, Go выполняет неявное декодирование UTF-8 в цикле по диапазону, примененному к строке:

for i, r := range "Hello, 世界" {
	fmt.Printf("%d\t%q\t%d\n", i, r, r)
}
/*
	0       'H'     72
	1       'e'     101
	2       'l'     108
	3       'l'     108
	4       'o'     111
	5       ','     44
	6       ' '     32
	7       '世'    19990
	10      '界'    30028
*/

Посчитать кол-во рун в строке можно простым циклом по range:

n := 0
for range s {
	n++
}

Но, вообще-то, можно просто вызвать utf8.RuneCountInString(s)

[]rune conversions

Преобразование строки в кодировке UTF-8 в []rune возвращает последовательность символов Unicode, закодированную в этой строке.

s := "プログラム"
fmt.Printf("% x\n", s) // e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0
r := []rune(s)
fmt.Printf("%x\n", r) // [30d7 30ed 30b0 30e9 30e0]

Преобразование []rune в string генерирует конкатенацию UTF-8 кодов для каждой руны:

fmt.Println(string(r)) // プログラム

Преобразование int в string рассматривает этот int как значение руны и дает представление этой руны в кодировке UTF-8:

fmt.Println(string(65))     // "A", но не "65"
fmt.Println(string(0x4eac)) //"京"

Если руна некорректна, вместо нее подставляется замещающий символ Unicode ('\uFFFD', который выглядит так: �):

fmt.Println(string(1234567)) // �

Соус: Книга Язык программирования Go Глава 3. Фундаментальные типы данных 3.5. Строки