strings.ToUpper wandelt einen kompletten String in seine Großbuchstaben-Form um. Anders als ein naives Byte-Mapping arbeitet die Funktion auf Rune-Ebene und kennt die Großschreibungsregeln der Unicode-Tabellen — sie verarbeitet also nicht nur das lateinische ASCII-Alphabet, sondern auch griechische, kyrillische, armenische oder lateinisch-erweiterte Codepoints korrekt.
Das hat Konsequenzen, die in der Praxis gern übersehen werden: manche Buchstaben wachsen beim Großschreiben (das deutsche ß wird zu SS, also zwei Runen aus einer), andere bleiben unverändert, weil sie schlicht kein Gegenstück besitzen. Wer ToUpper produktiv einsetzt — etwa für Normalisierung von Eingaben oder Vergleichszwecke — sollte diese Mechanik verstehen, sonst überrascht einen die Längenänderung oder das Verhalten in Locale-sensitiven Kontexten wie Türkisch (I/ı-Problematik).
Die Signatur ist denkbar schlicht — ein String rein, ein String raus. Das verbirgt allerdings die eigentliche Arbeit, die in den Unicode-Tabellen aus dem unicode-Paket steckt. ToUpper iteriert intern über alle Runen des Eingabe-Strings und schlägt für jede einzelne nach, ob es eine Großbuchstaben-Entsprechung gibt.
Wichtig: Strings sind in Go unveränderlich, ToUpper gibt also stets einen neuen String zurück. Wenn der Eingabe-String schon vollständig in Großbuchstaben (oder reinem ASCII ohne Casing-Bedarf) vorliegt, optimiert Go intern und vermeidet die Allokation — darauf verlassen sollte man sich für Hot-Paths trotzdem nicht.
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.ToUpper("hallo welt"))
fmt.Println(strings.ToUpper("Café à Paris"))
fmt.Println(strings.ToUpper("σίγμα")) // Griechisch
}HALLO WELT
CAFÉ À PARIS
ΣΊΓΜΑBeachte das griechische Beispiel: das kleine Sigma σ wird korrekt zu Σ — keine Magie, sondern saubere Unicode-Tabellen.
Ein häufiges Missverständnis: „ToUpper macht doch nur a→A, b→B …". Tatsächlich operiert die Funktion auf Runen (Go's Bezeichnung für Unicode-Codepoints), nicht auf Bytes. Das ist relevant, sobald Eingaben aus echter Welt kommen — Benutzernamen, Städtenamen, Produktbezeichnungen.
Ein naiver Byte-Loop über UTF-8-Daten würde Mehrbyte-Sequenzen zerschießen. ToUpper dekodiert die UTF-8-Sequenz, mappt die Rune und kodiert wieder zurück. Das ist langsamer als ein ASCII-Shortcut, dafür aber korrekt.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
in := "Ångström"
fmt.Println("Input :", in)
fmt.Println("ToUpper:", strings.ToUpper(in))
// Einzelne Rune großschreiben
r := 'å'
fmt.Printf("Rune %c -> %c\n", r, unicode.ToUpper(r))
}Input : Ångström
ToUpper: ÅNGSTRÖM
Rune å -> Åunicode.ToUpper arbeitet auf einer einzelnen Rune. Wer einen ganzen String braucht, nimmt strings.ToUpper; wer in einem Loop selektiv Runen großschreiben will, greift direkt zur Unicode-Variante.
Der prominenteste Special-Case für deutschsprachige Anwender: das Eszett ß besitzt traditionell keinen eigenen Großbuchstaben — die orthografische Konvention war jahrzehntelang SS. Go hält sich daran und gibt SS zurück. Der Output-String ist also länger (in Runen UND in Bytes) als der Input.
package main
import (
"fmt"
"strings"
"unicode/utf8"
)
func main() {
in := "Straße"
out := strings.ToUpper(in)
fmt.Printf("%q -> %q\n", in, out)
fmt.Printf("Runen: %d -> %d\n", utf8.RuneCountInString(in), utf8.RuneCountInString(out))
fmt.Printf("Bytes: %d -> %d\n", len(in), len(out))
}"Straße" -> "STRASSE"
Runen: 6 -> 7
Bytes: 7 -> 7Das ist kein Bug, sondern bewusst: wer Eingaben für Vergleiche normalisiert, sollte sich darauf einstellen, dass len(strings.ToUpper(s)) >= len(s) gilt, aber nicht zwingend Gleichheit. Speziell bei fixen Buffer-Größen kann das überraschen.
Seit Unicode 5.1 (2008) gibt es ein offizielles Großbuchstaben-Eszett: ẞ (U+1E9E). 2017 wurde es zudem in die offizielle deutsche Rechtschreibung aufgenommen. Trotzdem gibt strings.ToUpper weiterhin SS zurück — denn die Unicode-Standard-Casing-Regeln behandeln ß → SS als Default-Großschreibung, das ẞ ist nur als Titlecase/optional definiert.
In der Schweiz wird ß ohnehin nicht verwendet (dort wird durchgängig ss geschrieben), das Großbuchstaben-Eszett bleibt also primär ein Sonderfall für deutsche Typografie, Personalausweise und amtliche Schriften.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
// Default ToUpper macht aus ß ein SS
fmt.Println(strings.ToUpper("weiß"))
// Das Codepoint U+1E9E gibt es, aber Default-Casing nutzt es nicht
fmt.Printf("ẞ Codepoint: U+%04X\n", 'ẞ')
fmt.Printf("ToUpper('ß') = %q (statt 'ẞ')\n", string(unicode.ToUpper('ß')))
}WEISS
ẞ Codepoint: U+1E9E
ToUpper('ß') = "ß" (statt 'ẞ')Achtung Falle: unicode.ToUpper('ß') gibt ß zurück, weil die Rune-zu-Rune-Funktion keine 1→2-Expansion abbilden kann. Erst strings.ToUpper löst die Expansion zu SS korrekt auf.
Manche Sprachen brauchen Regeln, die vom Unicode-Default abweichen. Das prominenteste Beispiel: Türkisch unterscheidet zwischen i (mit Punkt) und ı (ohne Punkt), und beim Großschreiben werden diese unterschiedlich behandelt — i → İ (mit Punkt) und ı → I (ohne).
Dafür gibt es strings.ToUpperSpecial, das eine unicode.SpecialCase entgegennimmt. Die Standardbibliothek liefert unicode.TurkishCase und unicode.AzeriCase mit.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
word := "istanbul"
fmt.Println("Default :", strings.ToUpper(word))
fmt.Println("Türkisch:", strings.ToUpperSpecial(unicode.TurkishCase, word))
}Default : ISTANBUL
Türkisch: İSTANBULWer Locale-Awareness ernsthaft braucht (Suchen, Sortieren, Anzeigen), sollte sich golang.org/x/text/cases ansehen — das bietet vollständiges CLDR-basiertes Casing inklusive Titlecase.
ToUpper alloziert nur, wenn nötig. Intern wird zuerst geprüft, ob der String reines ASCII ist und Änderungen erforderlich sind. Ist der String schon komplett groß (oder enthält nur Zeichen ohne Großbuchstaben-Mapping), wird der originale String unverändert zurückgegeben — ohne Allokation.
Im allgemeinen Fall (gemischte Zeichen, Unicode-Inhalte) wird ein neuer Buffer gebaut, was eine Allokation kostet. Bei Hot-Paths mit Millionen kleiner Strings (Logs, HTTP-Header-Normalisierung) lohnt sich ein Benchmark, ob ein ASCII-Shortcut sinnvoll ist. Diese Optimierung ist nicht spezifiziert — sie ist ein Implementierungsdetail. Aber sie zeigt: bei „bereits korrekt" passiert nichts Teures.
Ein klassischer Anwendungsfall: ISO-4217-Währungscodes (EUR, USD, CHF …) sollen unabhängig von der Eingabeform groß sein. Benutzer tippen mal eur, mal Eur, mal EUR — die API will einen einheitlichen Key.
package main
import (
"errors"
"fmt"
"strings"
)
var allowed = map[string]struct{}{
"EUR": {}, "USD": {}, "CHF": {}, "GBP": {}, "JPY": {},
}
func NormalizeCurrency(in string) (string, error) {
code := strings.ToUpper(strings.TrimSpace(in))
if len(code) != 3 {
return "", errors.New("currency code must be 3 letters")
}
if _, ok := allowed[code]; !ok {
return "", fmt.Errorf("unsupported currency: %s", code)
}
return code, nil
}
func main() {
for _, raw := range []string{" eur ", "Usd", "btc", "chf"} {
code, err := NormalizeCurrency(raw)
if err != nil {
fmt.Printf("%-7q -> ERROR: %v\n", raw, err)
continue
}
fmt.Printf("%-7q -> %s\n", raw, code)
}
}" eur " -> EUR
"Usd" -> USD
"btc" -> ERROR: unsupported currency: BTC
"chf" -> CHFDer Pattern ist typisch: Trim → ToUpper → Whitelist-Check. Damit fängt man Tippfehler in der Eingabe ab, ohne die Geschäftslogik mit Casing-Varianten zu verseuchen.
Für Avatare, Tabellen-Platzhalter oder E-Mail-Signaturen braucht man oft die Initialen einer Person. ToUpper sorgt dafür, dass auch klein geschriebene Eingaben (max mustermann) das richtige Resultat liefern.
package main
import (
"fmt"
"strings"
"unicode/utf8"
)
func Initialen(name string) string {
parts := strings.Fields(name)
var b strings.Builder
for _, p := range parts {
r, _ := utf8.DecodeRuneInString(p)
if r == utf8.RuneError {
continue
}
b.WriteString(strings.ToUpper(string(r)))
}
return b.String()
}
func main() {
for _, n := range []string{"max mustermann", "Ärni Übel", "josé garcía lópez"} {
fmt.Printf("%-22s -> %s\n", n, Initialen(n))
}
}max mustermann -> MM
Ärni Übel -> ÄÜ
josé garcía lópez -> JGLBeachte: die erste Rune wird via utf8.DecodeRuneInString extrahiert (nicht via p[0], das würde bei Ä nur das erste UTF-8-Byte liefern). Anschließend wandelt strings.ToUpper die einzelne Rune sauber um — auch Umlaute werden korrekt groß geschrieben.
Signatur
func ToUpper(s string) string — ein String rein, ein String raus, immer Unicode-aware.
ß wird zu SS
Das deutsche Eszett expandiert zu zwei Runen — der Output ist länger als der Input.
Großes ẞ (U+1E9E)
Seit Unicode 5.1 existent, wird aber von strings.ToUpper nicht ausgegeben — Default bleibt SS.
ToUpperSpecial
Für locale-spezifische Regeln (Türkisch, Aserbaidschanisch) — bei deutschem ß macht es keinen Unterschied.
unicode.ToUpper vs. strings.ToUpper
Rune-Variante kann keine 1→2-Expansion abbilden; für vollständige Strings immer strings.ToUpper nehmen.
Keine Allokation bei bereits oben
Strings, die schon groß (oder ohne Casing-Bedarf) sind, werden ohne neue Allokation zurückgegeben.
Strings sind immutable
ToUpper modifiziert nie in-place — wer das will, baut sich []byte oder einen strings.Builder.
Locale-sensitiv? x/text/cases
Für vollständiges CLDR-Casing (inkl. Titlecase, Folding) ist golang.org/x/text/cases der ernstzunehmende Weg.
Weiterführende Ressourcen
Externe Quellen
strings.ToUpperstrings.ToUpperSpecialunicode.ToUppergolang.org/x/text/cases- Unicode Case Mapping FAQ