Strings, bytes and runes

Contents
Strings are byte slices
Range gives runes
strings.Builder
Cast carefully

Strings are byte slices

A Go string is a read-only run of bytes, usually UTF-8. Indexing gives one byte, len counts bytes, and plain ASCII text hides this until a wider alphabet arrives.

package main import "fmt" func main() { s := "hello" fmt.Println(len(s)) fmt.Println(s[0]) b := []byte(s) fmt.Println(b) }

5
104
[104 101 108 108 111]

Five letters mean five bytes, and s[0] is the number 104, the code for h. Converting to a byte slice exposes the same numbers in a form you can edit.

Range gives runes

Ranging over a string decodes UTF-8 and yields the byte index plus one rune per step. The index jumps by the width of each character, which the numbers below show: every letter of the sample word fills two bytes.

package main import "fmt" func main() { s := "кот" for i, r := range s { fmt.Printf("%d %c\n", i, r) } fmt.Println(len(s)) }

0 к
2 о
4 т
6

Three letters produce three runes, but six bytes, so the indexes skip ahead by two. Whenever you count characters for people to read, range for runes instead of trusting len.

strings.Builder

Adding strings in a loop with plus copies the growing text again and again. A Builder collects the parts and renders once, which keeps heavy concatenation fast and allocation-light.

package main import ( "fmt" "strings" ) func main() { var b strings.Builder for i := 0; i < 3; i++ { b.WriteString("Go") } fmt.Println(b.String()) }

GoGoGo

WriteString appends each piece to an internal buffer, and String renders the result once at the end. For simple joins outside loops, Join and Sprintf stay more readable than a Builder.

Cast carefully

Conversions copy: string from bytes, bytes from string, and runes from string each allocate a fresh value. Convert at the edges of your program, keep the hot path in one type, and never slice a string in the middle of a multibyte character.

package main import "fmt" func main() { b := []byte{104, 105} fmt.Println(string(b)) r := []rune("кот") fmt.Println(len(r)) fmt.Println(string(r[0])) }

hi
3
к

Bytes 104 and 105 become hi, and the rune slice holds three characters regardless of byte width. Round-trip through runes when you must index characters, and stay in bytes for machine-sized work.

Next: Time

Article author: Arthur Isaev

Related articles
Golang
Go course: zero to hero in 50 lessons
What is Go and where it runs
Install Go and check the version
Your first Go program
Modules with go mod
Variables
Constants
Types
Numbers
Strings
Conditions with if
Switch in Go
Loops with for
Arrays in Go
Slices in Go
Maps in Go
Functions in Go
Errors as values
Pointers in Go
Structs in Go
Methods in Go
Interfaces in Go
Embedding instead of inheritance
Generics in Go
Packages and imports
The go toolchain
Testing with go test
Advanced errors
defer in Go
Strings, bytes and runes
Time in Go
JSON
Files and IO
HTTP servers
HTTP clients
Context
Goroutines
Channels
select and sync
CLI: args, flags, env
Logging
Regular expressions
Sorting
Concurrency patterns
Databases with database/sql
Benchmarks and profiling
Toolchain and CI
Project layout
Capstone: wordfreq CLI
Capstone: JSON API server
Hero roadmap: the whole course on one page
Install Go on Windows 11
Install Go on Ubuntu
Install Go on Rocky Linux
Install Go on macOS
Install Go on FreeBSD
Go in Docker
Banner image

Search this site

Channel @aofeed Chat @aofeedchat

Contacts and cooperation:
I recommend our hosting beget.ru
Write to info@urn.su if you:
1. Want to write an article for our site or translate an article into your native language.
2. Want to place thematically relevant ads on the site.
3. Ads on my site pass maximum censorship. If you see an ad block unsuitable for school-age children, shocking or misleading - please contact us by e-mail
4. Found a mistake, inaccuracy, bug, etc. on the site. ... .......
5. Articles can be shared on social media by clicking a network icon: