Rune
Der Datentyp rune in Go repräsentiert einen Unicode-Codepoint und wird als Alias für int32 implementiert. Er ermöglicht die Verarbeitung und Darstellung einzelner Zeichen unabhängig von deren Kodierung. rune ist besonders nützlich beim Umgang mit Unicode-Zeichenfolgen, da er eine korrekte Interpretation und Manipulation von Zeichen außerhalb des ASCII-Bereichs gewährleistet. In diesem Artikel werden die Eigenschaften, die Verwendung und typische Anwendungsfälle des rune-Typs in Go anhand von Beispielen erläutert.
Inhaltsverzeichnis
Einführung
Ein Rune in Go ist ein Alias für den Typ int32 und repräsentiert einen Unicode-Codepoint. Dies ist ein Konzept in Go’s Textverarbeitung, das es ermöglicht, mit internationalen Zeichen und Symbolen korrekt umzugehen.
type rune int32Runes existieren, um eine wichtige Herausforderung der modernen Programmierung zu lösen: Die korrekte Verarbeitung internationaler Texte. In der frühen Computerzeit reichte ASCII (7-bit) aus, um englische Texte zu kodieren. Heute muss man jedoch Texte in allen Sprachen der Welt verarbeiten können.
Runes werden in Go mit einfachen Anführungszeichen definiert.
Hier ein grundlegendes Beispiel.
package main
import "fmt"
func main() {
// Verschiedene Runes definieren
var r1 rune = 'A'
var r2 rune = 'ä'
var r3 rune = 'Ω'
var r4 rune = '中'
var r5 rune = '🚀'
fmt.Printf("ASCII: %c (Unicode: %U, Dezimal: %d)\n", r1, r1, r1)
fmt.Printf("Umlaut: %c (Unicode: %U, Dezimal: %d)\n", r2, r2, r2)
fmt.Printf("Griechisch: %c (Unicode: %U, Dezimal: %d)\n", r3, r3, r3)
fmt.Printf("Chinesisch: %c (Unicode: %U, Dezimal: %d)\n", r4, r4, r4)
fmt.Printf("Emoji: %c (Unicode: %U, Dezimal: %d)\n", r5, r5, r5)
}ASCII: A (Unicode: U+0041, Dezimal: 65)
Umlaut: ä (Unicode: U+00E4, Dezimal: 228)
Griechisch: Ω (Unicode: U+03A9, Dezimal: 937)
Chinesisch: 中 (Unicode: U+4E2D, Dezimal: 20013)
Emoji: 🚀 (Unicode: U+1F680, Dezimal: 128640)Unicode und UTF-8 Grundlagen
Um Runes vollständig zu verstehen, muss man die Grundlagen von Unicode und UTF-8 verstehen.
Unicode: Das universelle Zeichensystem
Unicode ist ein internationaler Standard, der jedem Zeichen, Symbol und Emoji eine eindeutige Nummer zuweist - den sogenannten Code Point. Diese Nummern werden normalerweise in hexadezimaler Form dargestellt (z.B. U+0041 für ‘A’).
package main
import (
"fmt"
"unicode"
)
func main() {
text := "Hello, 世界! 🌍"
for i, r := range text {
// Eigenschaften sammeln
properties := []string{}
if unicode.IsLetter(r) {
properties = append(properties, "Buchstabe")
}
if unicode.IsDigit(r) {
properties = append(properties, "Ziffer")
}
if unicode.IsSpace(r) {
properties = append(properties, "Leerzeichen")
}
if unicode.IsPunct(r) {
properties = append(properties, "Satzzeichen")
}
if unicode.IsSymbol(r) {
properties = append(properties, "Symbol")
}
if len(properties) == 0 {
properties = append(properties, "Sonstige")
}
fmt.Printf("\nZeichen: %c\n", r)
fmt.Printf("Unicode: U+%04X\n", r)
fmt.Printf("Kategorie: %s\n", getUnicodeCategory(r))
fmt.Printf("Eigenschaften: %v\n", properties)
fmt.Printf("Position: %d\n", i)
fmt.Println("--- --- ---")
}
}
func getUnicodeCategory(r rune) string {
switch {
case unicode.IsLetter(r):
return "Buchstabe"
case unicode.IsDigit(r):
return "Ziffer"
case unicode.IsSpace(r):
return "Leerraum"
case unicode.IsPunct(r):
return "Zeichen"
case unicode.IsSymbol(r):
return "Symbol"
default:
return "Sonstige"
}
}Zeichen: H
Unicode: U+0048
Kategorie: Buchstabe
Eigenschaften: [Buchstabe]
Position: 0
--- --- ---
Zeichen: e
Unicode: U+0065
Kategorie: Buchstabe
Eigenschaften: [Buchstabe]
Position: 1
--- --- ---
Zeichen: l
Unicode: U+006C
Kategorie: Buchstabe
Eigenschaften: [Buchstabe]
Position: 2
--- --- ---
Zeichen: l
Unicode: U+006C
Kategorie: Buchstabe
Eigenschaften: [Buchstabe]
Position: 3
--- --- ---
Zeichen: o
Unicode: U+006F
Kategorie: Buchstabe
Eigenschaften: [Buchstabe]
Position: 4
--- --- ---
Zeichen: ,
Unicode: U+002C
Kategorie: Zeichen
Eigenschaften: [Satzzeichen]
Position: 5
--- --- ---
Zeichen:
Unicode: U+0020
Kategorie: Leerraum
Eigenschaften: [Leerzeichen]
Position: 6
--- --- ---
Zeichen: 世
Unicode: U+4E16
Kategorie: Buchstabe
Eigenschaften: [Buchstabe]
Position: 7
--- --- ---
Zeichen: 界
Unicode: U+754C
Kategorie: Buchstabe
Eigenschaften: [Buchstabe]
Position: 10
--- --- ---
Zeichen: !
Unicode: U+0021
Kategorie: Zeichen
Eigenschaften: [Satzzeichen]
Position: 13
--- --- ---
Zeichen:
Unicode: U+0020
Kategorie: Leerraum
Eigenschaften: [Leerzeichen]
Position: 14
--- --- ---
Zeichen: 🌍
Unicode: U+1F30D
Kategorie: Symbol
Eigenschaften: [Symbol]
Position: 15
--- --- ---UTF-8 Kodierung
UTF-8 ist eine variable Längen-Kodierung für Unicode. Das bedeutet:
- ASCII-Zeichen (0-127) werden mit 1 Byte kodiert
- Andere Zeichen verwenden 2-4 Bytes
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// Verschiedene Zeichen mit unterschiedlicher Byte-Länge
examples := []string{"A", "ä", "中", "🚀"}
for _, char := range examples {
r, size := utf8.DecodeRuneInString(char)
fmt.Printf("\nZeichen: %s\n", char)
fmt.Printf("Unicode: U+%04X (%d)\n", r, r)
fmt.Printf("UTF-8 Bytes: %d\n", size)
fmt.Printf("Binär UTF-8: \n")
for _, b := range []byte(char) {
fmt.Printf("\t%08b \n", b)
}
fmt.Printf("Hex UTF-8: ")
for _, b := range []byte(char) {
fmt.Printf("%02X ", b)
}
fmt.Printf("\n" + "--- --- ---\n")
}
}
Zeichen: A
Unicode: U+0041 (65)
UTF-8 Bytes: 1
Binär UTF-8:
01000001
Hex UTF-8: 41
--- --- ---
Zeichen: ä
Unicode: U+00E4 (228)
UTF-8 Bytes: 2
Binär UTF-8:
11000011
10100100
Hex UTF-8: C3 A4
--- --- ---
Zeichen: 中
Unicode: U+4E2D (20013)
UTF-8 Bytes: 3
Binär UTF-8:
11100100
10111000
10101101
Hex UTF-8: E4 B8 AD
--- --- ---
Zeichen: 🚀
Unicode: U+1F680 (128640)
UTF-8 Bytes: 4
Binär UTF-8:
11110000
10011111
10011010
10000000
Hex UTF-8: F0 9F 9A 80
--- --- ---Rune vs. Byte - Unterschied
Dies ist einer der wichtigsten Unterschiede, den man verstehen muss. Verwechslungen hier führen zu den häufigsten Bugs bei der Textverarbeitung.
Byte-basierte Verarbeitung (FALSCH)
package main
import "fmt"
func main() {
text := "Hëllø, 世界!"
fmt.Printf("Text: %s\n", text)
fmt.Printf("len(text): %d (Anzahl Bytes)\n", len(text))
// Falsche Iteration - über Bytes
for i := 0; i < len(text); i++ {
fmt.Printf("text[%d] = %c (Byte: %d, 0x%02X)\n", i, text[i], text[i], text[i])
}
}Text: Hëllø, 世界!
len(text): 16 (Anzahl Bytes)
text[0] = H (Byte: 72, 0x48)
text[1] = Ã (Byte: 195, 0xC3)
text[2] = « (Byte: 171, 0xAB)
text[3] = l (Byte: 108, 0x6C)
text[4] = l (Byte: 108, 0x6C)
text[5] = Ã (Byte: 195, 0xC3)
text[6] = ¸ (Byte: 184, 0xB8)
text[7] = , (Byte: 44, 0x2C)
text[8] = (Byte: 32, 0x20)
text[9] = ä (Byte: 228, 0xE4)
text[10] = ¸ (Byte: 184, 0xB8)
text[11] = (Byte: 150, 0x96)
text[12] = ç (Byte: 231, 0xE7)
text[13] = (Byte: 149, 0x95)
text[14] = (Byte: 140, 0x8C)
text[15] = ! (Byte: 33, 0x21)Hier sieht man deutlich, dass der Buchstabe ‘ë’ nicht korrekt verarbeitet wird. Wir iterieren hier nicht über das eigentliche Zeichen, sondern über Teile von diesem Zeichen, da dieses Zeichen aus mehreren Bytes besteht.
text[1] = Ã (Byte: 195, 0xC3)
text[2] = « (Byte: 171, 0xAB)Rune-basierte Verarbeitung (RICHTIG)
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
text := "Hëllø, 世界!"
fmt.Printf("Text: %s\n", text)
fmt.Printf("len(text): %d Bytes\n", len(text))
fmt.Printf("utf8.RuneCountInString(text): %d Runes\n", utf8.RuneCountInString(text))
// Richtige Iteration - über Runes
runeIndex := 0
for byteIndex, r := range text {
fmt.Printf("Rune %d (Byte-Position: %d): %c (U+%04X, %d)\n", runeIndex, byteIndex, r, r, r)
runeIndex++
}
// Alternative: Manuelle Dekodierung
byteIndex := 0
runeIndex = 0
for byteIndex < len(text) {
r, size := utf8.DecodeRuneInString(text[byteIndex:])
fmt.Printf("Rune %d: %c (Bytes: %d-%d, Größe: %d)\n", runeIndex, r, byteIndex, byteIndex+size-1, size)
byteIndex += size
runeIndex++
}
}Text: Hëllø, 世界!
len(text): 16 Bytes
utf8.RuneCountInString(text): 10 Runes
Rune 0 (Byte-Position: 0): H (U+0048, 72)
Rune 1 (Byte-Position: 1): ë (U+00EB, 235)
Rune 2 (Byte-Position: 3): l (U+006C, 108)
Rune 3 (Byte-Position: 4): l (U+006C, 108)
Rune 4 (Byte-Position: 5): ø (U+00F8, 248)
Rune 5 (Byte-Position: 7): , (U+002C, 44)
Rune 6 (Byte-Position: 8): (U+0020, 32)
Rune 7 (Byte-Position: 9): 世 (U+4E16, 19990)
Rune 8 (Byte-Position: 12): 界 (U+754C, 30028)
Rune 9 (Byte-Position: 15): ! (U+0021, 33)
Rune 0: H (Bytes: 0-0, Größe: 1)
Rune 1: ë (Bytes: 1-2, Größe: 2)
Rune 2: l (Bytes: 3-3, Größe: 1)
Rune 3: l (Bytes: 4-4, Größe: 1)
Rune 4: ø (Bytes: 5-6, Größe: 2)
Rune 5: , (Bytes: 7-7, Größe: 1)
Rune 6: (Bytes: 8-8, Größe: 1)
Rune 7: 世 (Bytes: 9-11, Größe: 3)
Rune 8: 界 (Bytes: 12-14, Größe: 3)
Rune 9: ! (Bytes: 15-15, Größe: 1)Rune-Literale und Syntax
Rune-Literale sind die Art, wir man Unicode-Zeichen in Go-Code definiert. Ein Rune-Literal wird immer in einfache Anführungszeichen eingeschlossen und repräsentiert genau ein Unicode-Zeichen (einen Unicode Code Point).
Direkte Zeichen-Literale
Die einfachste Form sind direkte Zeichen, die einfach in einfache Anführungszeichen gesetzt werden.
package main
import "fmt"
func main() {
// Direkte Zeichen-Literale
var r1 rune = 'A' // ASCII-Buchstabe
var r2 rune = 'ä' // Latin-1 Zeichen mit Umlaut
var r3 rune = '中' // Chinesisches Zeichen (CJK)
var r4 rune = '🚀' // Emoji (4-Byte UTF-8)
var r5 rune = 'Ω' // Grichisches Omega
var r6 rune = '5' // Ziffer als Zeichen
var r7 rune = ' ' // Leerzeichen
var r8 rune = '+' // Sonderzeichen
fmt.Printf("'A' (ASCII) ==> %c = %d (0x%X)\n", r1, r1, r1)
fmt.Printf("'ä' (Latin-1) ==> %c = %d (0x%X)\n", r2, r2, r2)
fmt.Printf("'中' (CJK) ==> %c = %d (0x%X)\n", r3, r3, r3)
fmt.Printf("'🚀' (Emoji) ==> %c = %d (0x%X)\n", r4, r4, r4)
fmt.Printf("'Ω' (Greek) ==> %c = %d (0x%X)\n", r5, r5, r5)
fmt.Printf("'5' (Ziffer) ==> %c = %d (0x%X)\n", r6, r6, r6)
fmt.Printf("' ' (Leerzeichen) ==> %c = %d (0x%X)\n", r7, r7, r7)
fmt.Printf("'+' (Symbol) ==> %c = %d (0x%X)\n", r8, r8, r8)
}'A' (ASCII) ==> A = 65 (0x41)
'ä' (Latin-1) ==> ä = 228 (0xE4)
'中' (CJK) ==> 中 = 20013 (0x4E2D)
'🚀' (Emoji) ==> 🚀 = 128640 (0x1F680)
'Ω' (Greek) ==> Ω = 937 (0x3A9)
'5' (Ziffer) ==> 5 = 53 (0x35)
' ' (Leerzeichen) ==> = 32 (0x20)
'+' (Symbol) ==> + = 43 (0x2B)Wichtiger Hinweis: '5' ist das Zeichen ‘5’ (Unicode 53), nicht die Zahl 5.
Escape-Sequenzen für spezielle Zeichen
Manche Zeichen können nicht direkt dargestellt werden oder haben spezielle Bedeutungen. Dafür gibt es Escape-Sequenzen mit Backslash (\).
package main
import "fmt"
func main() {
// Standard Escape-Sequenzen
var newline rune = '\n' // Zeilenumbruch
var tab rune = '\t' // Tabulator
var carriageReturn rune = '\r' // Wagenrücklauf
var backslash rune = '\\' // Backslash selbst
var singleQuote rune = '\'' // Einfaches Anführungszeichen
var doubleQoute rune = '"' // Doppeltes Anführungszeichen
var bell rune = '\a' // Klingelzeichen
var backspace rune = '\b' // Rückschritt
var formfeed rune = '\f' // Seitenvorschub
var vtab rune = '\v' // Vertikaler Tabulator
fmt.Printf("Neue Zeile (\\n) ==> ASCII %d | Hex 0x%02X\n", newline, newline)
fmt.Printf("Tabulator (\\t) ==> ASCII %d | Hex 0x%02X\n", tab, tab)
fmt.Printf("Wagenrücklauf (\\r) ==> ASCII %d | Hex 0x%02X\n", carriageReturn, carriageReturn)
fmt.Printf("Backslash (\\\\) ==> ASCII %d | Hex 0x%02X\n", backslash, backslash)
fmt.Printf("Einfaches ' (\\') ==> ASCII %d | Hex 0x%02X\n", singleQuote, singleQuote)
fmt.Printf("Doppeltes \" ('\"') ==> ASCII %d | Hex 0x%02X\n", doubleQoute, doubleQoute)
fmt.Printf("Klingelzeichen (\\a) ==> ASCII %d | Hex 0x%02X\n", bell, bell)
fmt.Printf("Rückschritt (\\b) ==> ASCII %d | Hex 0x%02X\n", backspace, backspace)
fmt.Printf("Seitenvorschub (\\f) ==> ASCII %d | Hex 0x%02X\n", formfeed, formfeed)
fmt.Printf("Vert. Tabulator (\\v) ==> ASCII %d | Hex 0x%02X\n", vtab, vtab)
}Neue Zeile (\n) ==> ASCII 10 | Hex 0x0A
Tabulator (\t) ==> ASCII 9 | Hex 0x09
Wagenrücklauf (\r) ==> ASCII 13 | Hex 0x0D
Backslash (\\) ==> ASCII 92 | Hex 0x5C
Einfaches ' (\') ==> ASCII 39 | Hex 0x27
Doppeltes " ('"') ==> ASCII 34 | Hex 0x22
Klingelzeichen (\a) ==> ASCII 7 | Hex 0x07
Rückschritt (\b) ==> ASCII 8 | Hex 0x08
Seitenvorschub (\f) ==> ASCII 12 | Hex 0x0C
Vert. Tabulator (\v) ==> ASCII 11 | Hex 0x0BWichtiger Unterschied
- In Rune-Literalen:
'"'(doppeltes Anführungszeichen braucht kein Escape) - In String-Literalen:
"\""(doppeltes Anführungszeichen braucht Escape)
Numerische Escape-Sequenzen
Für Zeichen, die sich nicht direkt tippen lassen, gibt es numeische Escape-Sequenzen.
Oktal-Escapes
Oktal-Escapes verwenden die Form \nnn wobei nnn 1-3 oktale Ziffern (0-7) sind.
package main
import "fmt"
func main() {
// Oktal-Escapes (Basis 8)
// Wichtig: Nur gültig für Werte 0-255 (0-\377 oktal)
var r1 rune = '\141' // 141 oktal = 97 dezimal = 'a'
var r2 rune = '\101' // 101 oktal = 65 dezimal = 'A'
var r3 rune = '\040' // 040 oktal = 32 dezimal = ' ' (Leerzeichen)
var r4 rune = '\012' // 012 oktal = 10 dezimal = '\n'
var r5 rune = '\000' // 000 oktal = 0 dezimal = NULL-Zeichen
var r6 rune = '\377' // 377 oktal = 255 dezimal = höchstes Byte-Zeichen
// Verkürzte Oktal-Schreibweise (1-2 Ziffern)
var r7 rune = '\007' // 007 oktal = 7 dezimal = Bell
var r8 rune = '\033' // 033 oktal = 27 dezimal = ESC
fmt.Printf("\\141 (oktal) = %d (dezimal) = '%c'\n", r1, r1)
fmt.Printf("\\101 (oktal) = %d (dezimal) = '%c'\n", r2, r2)
fmt.Printf("\\040 (oktal) = %d (dezimal) = '%c' (Leerzeichen)\n", r3, r3)
fmt.Printf("\\012 (oktal) = %d (dezimal) = Zeilenumbruch\n", r4)
fmt.Printf("\\000 (oktal) = %d (dezimal) = NULL-Zeichen\n", r5)
fmt.Printf("\\377 (oktal) = %d (dezimal) = höchstes Byte\n", r6)
fmt.Printf("\\007 (oktal) = %d (dezimal) = Bell\n", r7)
fmt.Printf("\\033 (oktal) = %d (dezimal) = ESC\n", r8)
}\141 (oktal) = 97 (dezimal) = 'a'
\101 (oktal) = 65 (dezimal) = 'A'
\040 (oktal) = 32 (dezimal) = ' ' (Leerzeichen)
\012 (oktal) = 10 (dezimal) = Zeilenumbruch
\000 (oktal) = 0 (dezimal) = NULL-Zeichen
\377 (oktal) = 255 (dezimal) = höchstes Byte
\007 (oktal) = 7 (dezimal) = Bell
\033 (oktal) = 27 (dezimal) = ESCHier ein Beispiel zur Umrechnung von oktal nach dezimal. Bei Berechnung mit oktalen Zahlen ist die Basis = 8.
package main
import "fmt"
func main() {
octals := []string{
"007",
"033",
"040",
"101",
"141",
"377",
}
for _, oct := range octals {
fmt.Printf("\\%s oktal = ", oct)
switch oct {
case "007":
fmt.Printf("%d dezimal\n", 7)
case "033":
fmt.Printf("%d dezimal (0*64 + 3*8 + 3 = %d)\n", 0*64+3*8+3, 0*64+3*8+3)
case "040":
fmt.Printf("%d dezimal (0*64 + 4*8 + 0 = %d)\n", 0*64+4*8+0, 0*64+4*8+0)
case "101":
fmt.Printf("%d dezimal (1*64 + 0*8 + 1 = %d)\n", 1*64+0*8+1, 1*64+0*8+1)
case "141":
fmt.Printf("%d dezimal (1*64 + 4*8 + 1 = %d)\n", 1*64+4*8+1, 1*64+4*8+1)
case "377":
fmt.Printf("%d dezimal (3*64 + 7*8 + 7 = %d)\n", 3*64+7*8+7, 3*64+7*8+7)
}
}
}\007 oktal = 7 dezimal
\033 oktal = 27 dezimal (0*64 + 3*8 + 3 = 27)
\040 oktal = 32 dezimal (0*64 + 4*8 + 0 = 32)
\101 oktal = 65 dezimal (1*64 + 0*8 + 1 = 65)
\141 oktal = 97 dezimal (1*64 + 4*8 + 1 = 97)
\377 oktal = 255 dezimal (3*64 + 7*8 + 7 = 255)