漫行记Wandering
Journal 1,548 字 6 分钟 Golang

Golang Strings: A Deep Dive

What a Go string really is

In Go, a string is immutable(cannot be changed once created). Internally, it’s just :

type string struct {
	Data *byte // pointer to the underlying byte array
	Len int // length of the string in bytes
}

Actually the real definition in runtime package is as below, you can refer to link

type stringStruct struct {
	str unsafe.Pointer
	len int
}

So a string is basically a read-only slice of bytes - but not exactly a slice. It doesn’t have capacity, and you can’t modify its contents.

Encoding

Go strings are stored as UTF-8 encoded bytes by default. That means:

  1. Each string is a sequence of bytes
  2. A character(rune) might take 1-4 bytes depending on Unicode
s := "Goalng😊"
fmt.Println(len(s)) // 10
fmt.Println(utf8.RuneCountInString(s)) // 7

So len(s) counts bytes, not characters.

Indexing and slicing

Indexing a string gives you the raw byte, not a character.

s := "Golang😊"
fmt.Printf("%T\n", s[0]) // uint8
fmt.Println(s[0]) // 71 (ASCII code for 'G')

So there are two ways to iterate the string

  1. we get the raw byte
s := "Golang😊"
for i:=0; i < len(s); i++ {
	fmt.Println(s[i])
}
// 71 111 108 97 110 103 240 159 152 138
  1. we get the character
s := "Golang😊"
for _, c := range s {
	fmt.Println(c)
}
// G o l a n g 😊

Immutability

Strings can’t be changed in-place, if you do this

s := "Hello"
s[0] = 'W' // complie error

we must create a new one

s2 := "W" + s[1:]

Relation to []byte and []rune

  • []byte -> raw underlying bytes
  • []rune -> Unicode code points
s := "😊"
fmt.Println([]byte(s)) // [240 159 152 138]
fmt.Println([]rune(s)) // [128522]

memory diagram

s (string value) ──►  Data ──► [71 240 159 152 138]
                      Len = 5

if we convert to []byte

b := []byte(s)

b (slice) ──► Data ──► [71 240 159 152 138]
               Len = 5
               Cap = 5

if we convert to []rune

r := []rune(s)

r (slice of runes) ──► Data ──► [71 128522]
                        Len = 2
                        Cap = 2

Inspecting a Go string

We can user the reflect.StringHeader struct (which mirrors the internal runtime stringStruct) + unsafe to look at the memory layout of a string.

package main

import (
	"fmt"
	"unsafe"
	"reflect"
)

func main() {
	s := "Golang😊"
	hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))

	fmt.Printf("Data Pointer: %x \n", hdr.Data) //address of first byte
	fmt.Printf("Length: %d \n", hdr.Len) // 10

}

The output might vary by machine

← 随笔