}

Go: Convert Rune to String and String to Rune — Complete Guide (2026)

Go: Convert Rune to String and String to Rune — Complete Guide (2026)

To convert a rune to a string in Go, use string(r). To convert a string to a slice of runes, use []rune(s). These two conversions are built into the language — no import required.

r := 'A'
s := string(r)   // "A"

str := "hello"
runes := []rune(str)  // [104 101 108 108 111]

Last updated: March 2026


What Is a Rune in Go?

A rune is an alias for int32. It represents a single Unicode code point. Go source files are UTF-8 by default, so any character literal you write between single quotes — 'A', '€', '日' — is a rune.

var r rune = '€'
fmt.Printf("type: %T, value: %d, char: %c\n", r, r, r)
// type: int32, value: 8364, char: €

The key insight: a rune is a number. 'A' is 65. '€' is 8364. Converting a rune to a string produces the UTF-8 encoding of that code point, not its decimal representation.


Convert Rune to String

Method 1: string(r) — the standard approach

r := '日'
s := string(r)
fmt.Println(s)        // 日
fmt.Println(len(s))   // 3  (bytes, not characters — UTF-8 encodes 日 as 3 bytes)

string(r) takes the rune's Unicode code point and returns its UTF-8 byte sequence as a Go string.

Method 2: fmt.Sprintf with %c

r := 'Go'[0]  // won't compile — string indexing yields bytes, not runes
r2 := '€'
s := fmt.Sprintf("%c", r2)
fmt.Println(s)  // €

Use %c when you need to embed a rune inside a larger formatted string.

Method 3: string([]rune{r})

r := '☺'
s := string([]rune{r})
fmt.Println(s)  // ☺

This is equivalent to string(r) but makes it explicit that you are building a one-rune string from a rune slice.


Convert String to Rune Slice

Use []rune(s) to convert a string into a slice where each element is one Unicode code point.

s := "Hello, 世界"
runes := []rune(s)

fmt.Println(len(s))      // 13 — byte count
fmt.Println(len(runes))  // 9  — character count

for i, r := range runes {
    fmt.Printf("[%d] %c (%d)\n", i, r, r)
}

This is the correct way to index into a string by character position rather than byte position.


Iterating a String with for range — You Already Get Runes

Go's for range over a string automatically decodes UTF-8 and yields (byteIndex, rune) pairs:

s := "café"
for i, r := range s {
    fmt.Printf("byte index %d: %c (U+%04X)\n", i, r, r)
}
// byte index 0: c (U+0063)
// byte index 1: a (U+0061)
// byte index 2: f (U+0066)
// byte index 3: é (U+00E9)   ← 'é' is 2 bytes, but you get one rune

Note that i is the byte index, not the character index. If you need the character index, count yourself or use []rune.


Rune vs Byte Comparison

Operation Bytes ([]byte) Runes ([]rune)
Type of element uint8 (0–255) int32 (any Unicode code point)
Handles non-ASCII No — multi-byte chars split Yes — each char is one element
len(s) after conversion Number of bytes Number of characters
Index s[i] Byte at position i Code point at character position i
Use case Binary data, ASCII only, HTTP headers Human-readable text, internationalization
s := "日本語"

bytes := []byte(s)
fmt.Println(len(bytes))   // 9  (3 bytes per kanji)

runes := []rune(s)
fmt.Println(len(runes))   // 3  (3 characters)

Counting Characters with utf8.RuneCountInString

When you just need the character count without creating a []rune, use utf8.RuneCountInString:

import "unicode/utf8"

s := "日本語"
fmt.Println(len(s))                          // 9 (bytes)
fmt.Println(utf8.RuneCountInString(s))       // 3 (characters)

This is more memory-efficient than len([]rune(s)) because it does not allocate a new slice.

The unicode/utf8 package also provides:

  • utf8.RuneLen(r rune) int — number of bytes needed to encode a rune
  • utf8.ValidString(s string) bool — check if a string contains valid UTF-8
  • utf8.DecodeRuneInString(s string) (rune, int) — decode the first rune and return its byte width
r := '€'
fmt.Println(utf8.RuneLen(r))  // 3

fmt.Println(utf8.ValidString("hello"))  // true
fmt.Println(utf8.ValidString("\xff\xfe"))  // false

Practical Example: Reverse a Unicode String

The classic "reverse a string" exercise breaks if you reverse bytes instead of runes:

// WRONG — breaks multi-byte characters
func reverseBad(s string) string {
    b := []byte(s)
    for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
        b[i], b[j] = b[j], b[i]
    }
    return string(b)
}

// CORRECT — swap runes
func reverseString(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

fmt.Println(reverseString("Hello, 世界"))  // 界世 ,olleH

Practical Example: Extract a Substring by Character Position

func substrByRune(s string, start, end int) string {
    runes := []rune(s)
    return string(runes[start:end])
}

s := "Hello, 世界!"
fmt.Println(substrByRune(s, 7, 9))  // 世界

Common Mistakes

Mistake 1: Converting int to string gives a character, not a digit

n := 65
s := string(n)       // "A" — not "65"!
s2 := strconv.Itoa(n) // "65" — correct for numbers

If you need to convert a number to its string representation, use strconv.Itoa (for int) or strconv.FormatInt. See the Go string to int conversion guide for all conversion methods.

Mistake 2: Using len(s) for character count

s := "日本語"
fmt.Println(len(s))   // 9 — BYTES, not characters

Always use utf8.RuneCountInString(s) or len([]rune(s)) when you need the character count.

Mistake 3: Indexing a string directly for a character

s := "café"
fmt.Println(s[3])   // 195 — a BYTE, not the rune 'é'

Use []rune(s)[3] or a for range loop to get the rune at a character position.


Related Pages


Summary

Goal Code
Rune to string string(r)
String to rune slice []rune(s)
Character count utf8.RuneCountInString(s)
Iterate characters for i, r := range s
Rune byte width utf8.RuneLen(r)
Number to string strconv.Itoa(n) (not string(n))