Question
In Go, I often see the statement that rune is just an alias for int32, but I do not understand what that means in practice.
For example, in this case-swapping function:
func SwapRune(r rune) rune {
switch {
case 'a' <= r && r <= 'z':
return r - 'a' + 'A'
case 'A' <= r && r <= 'Z':
return r - 'A' + 'a'
default:
return r
}
}
I do not understand:
- Why an integer-like type is used for characters
- What expressions like
'a' <= r && r <= 'z'mean - Why subtraction and addition are used, such as
r - 'a' + 'A' - Why
switchhas no expression after it
And in this function:
func SwapCase(str string) string {
return strings.Map(SwapRune, str)
}
I understand that it maps over the string and returns a new string, but I do not clearly understand how rune and byte work here, or why rune is the right type for this kind of character processing in Go.
Short Answer
By the end of this page, you will understand what a rune is in Go, why it is used for Unicode characters, how it differs from byte, and how character comparisons and arithmetic work in code like a case-swapping function. You will also see how strings.Map processes a string rune by rune.
Concept
In Go, a rune represents a Unicode code point. It is defined as an alias for int32.
That does not mean Go is treating characters as random numbers. It means that each character is stored using its numeric Unicode value.
For example:
fmt.Println('A') // 65
fmt.Println('a') // 97
Characters have numeric codes behind the scenes. This is true in many programming languages.
Why rune exists
Go strings are stored as UTF-8 encoded bytes. UTF-8 is efficient, but some characters use more than one byte.
Examples:
'A'uses 1 byte in UTF-8'é'uses 2 bytes in UTF-8'世'uses 3 bytes in UTF-8
A byte is just one 8-bit value. It is useful when working with raw binary data or the raw UTF-8 encoding of a string.
A rune represents the actual character code point, which is what you usually want when doing character-based logic like:
- checking if a character is a letter
- converting case
Mental Model
Think of a string in Go as a box of encoded bytes.
A byte is one small piece from that box.
A rune is the decoded character value you get after interpreting those bytes as UTF-8.
So:
byte= raw storage unitrune= actual character code point
For the case-swapping example, imagine a row of lowercase letters and a row of uppercase letters:
Lowercase: a b c d e ... z
Uppercase: A B C D E ... Z
If you know a letter is 2 places after a, then its uppercase version is 2 places after A.
That is exactly what this does:
r - 'a' + 'A'
It says: "find the position of this lowercase letter, then move to the same position in the uppercase row."
Syntax and Examples
Basic rune syntax
var r rune = 'A'
fmt.Println(r) // 65
fmt.Printf("%c\n", r) // A
Single quotes in Go are used for rune literals.
'A'is a rune literal"A"is a string literal
Comparing runes
func isLowercaseASCII(r rune) bool {
return 'a' <= r && r <= 'z'
}
This checks whether r is between 'a' and 'z' inclusive.
Simple case conversion with rune arithmetic
func toUpperASCII(r rune) rune {
if <= r && r <= {
r - +
}
r
}
Step by Step Execution
Consider this example:
func SwapRune(r rune) rune {
switch {
case 'a' <= r && r <= 'z':
return r - 'a' + 'A'
case 'A' <= r && r <= 'Z':
return r - 'A' + 'a'
default:
return r
}
}
Now trace it with:
result := SwapRune('c')
Step 1: Call the function
r receives the rune 'c'.
Its numeric value is 99.
Step 2: Evaluate the switch
This is a condition-based switch:
switch {
Go checks each from top to bottom.
Real World Use Cases
When runes are useful
Text processing
When you need to inspect or transform characters in a string:
- convert case
- remove unwanted characters
- validate input
- normalize text for search
Parsers and tokenizers
Compilers, interpreters, and config parsers often examine input one character at a time. Using runes is safer than bytes when Unicode input is possible.
User input validation
Examples:
- allow only letters and digits
- reject control characters
- detect spaces or punctuation
Search and indexing tools
Programs may lowercase or clean text before storing or comparing it.
APIs and web applications
If your app receives names, messages, or comments from users, rune-aware code helps avoid breaking non-ASCII text.
When bytes are more appropriate
Use byte when working with:
- binary file formats
- network protocols
- image or audio data
- manual UTF-8 encoding details
- high-performance operations on raw data
Real Codebase Usage
In real Go codebases, developers rarely write manual ASCII case logic unless they specifically want ASCII-only behavior.
Common patterns
Use range to iterate runes
for _, r := range s {
fmt.Printf("%c\n", r)
}
This decodes UTF-8 automatically.
Use the unicode package for character categories
import "unicode"
func isLetter(r rune) bool {
return unicode.IsLetter(r)
}
This works beyond ASCII.
Use Unicode-aware case conversion
import "unicode"
func swapRuneUnicode(r rune) rune {
if unicode.IsLower(r) {
return unicode.ToUpper(r)
}
if unicode.IsUpper(r) {
return unicode.ToLower(r)
}
r
}
Common Mistakes
1. Confusing rune literals and strings
Broken:
var r rune = "A"
Why it fails:
"A"is a string'A'is a rune
Correct:
var r rune = 'A'
2. Treating bytes as characters
Broken assumption:
s := "é"
fmt.Println(len(s)) // beginner may expect 1
Actually, this prints 2 because the UTF-8 encoding uses 2 bytes.
If you want characters, iterate over runes:
for _, r := range s {
fmt.Printf("%c\n", r)
}
3. Assuming ASCII logic works for all languages
Broken idea:
{
<= r && r <= {
r - +
}
r
}
Comparisons
rune vs byte
| Concept | rune | byte |
|---|---|---|
| Underlying type | int32 | uint8 |
| Represents | Unicode code point | Raw 8-bit value |
| Good for | Characters and text logic | Raw bytes and binary data |
| String indexing result | Not directly | Yes, s[i] gives a byte |
| Unicode-safe character handling | Yes | No |
Rune literal vs string literal
Cheat Sheet
Quick reference
What is a rune?
type rune = int32
A rune represents a Unicode code point.
What is a byte?
type byte = uint8
A byte is an 8-bit raw value.
Rune literal vs string literal
'A' // rune
"A" // string
Check ASCII lowercase
'a' <= r && r <= 'z'
Check ASCII uppercase
'A' <= r && r <= 'Z'
Convert lowercase ASCII to uppercase
r - 'a' + 'A'
FAQ
What is a rune in Go in simple terms?
A rune is a value that represents one Unicode character code point. Go uses rune when you want to work with characters instead of raw bytes.
Why is rune an alias for int32?
Because Unicode code points are numbers, and int32 is large enough to store them. The alias makes the purpose clearer when reading code.
What is the difference between byte and rune in Go?
A byte is raw 8-bit data. A rune is a decoded Unicode character value. Strings are stored as bytes, but you often process text as runes.
Why does 'a' <= r && r <= 'z' work?
Because rune literals like 'a' and 'z' have numeric code point values. The expression checks whether r falls in that range.
Why does r - 'a' + 'A' convert lowercase to uppercase?
It keeps the same alphabet position. For example, 'c' is 2 positions after 'a', so the result is 2 positions after , which is .
Mini Project
Description
Build a small text transformer that swaps the case of ASCII letters in a string. This project helps you practice working with rune, using strings.Map, and understanding the difference between text processing by bytes and by Unicode code points.
Goal
Create a Go program that converts uppercase ASCII letters to lowercase, lowercase ASCII letters to uppercase, and leaves all other characters unchanged.
Requirements
Requirement 1
Keep learning
Related questions
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.
Concatenating Slices in Go with append
Learn how to concatenate two slices in Go using append and the ... operator, with examples, pitfalls, and practical usage.