strings.EqualFold vergleicht zwei Strings unter Unicode-Case-Folding und liefert true, wenn beide nach dem Folding identisch sind. Anders als das naive Muster strings.ToLower(a) == strings.ToLower(b) legt EqualFold dabei keine Zwischen-Strings an: die Funktion läuft rune-für-rune parallel durch beide Eingaben und vergleicht jede gefoldete Rune direkt. Das spart Allokationen, schont den Garbage Collector und ist in fast allen Fällen schneller — vor allem wenn die Strings sich früh unterscheiden und EqualFold die Schleife abbrechen kann.
Die Signatur ist denkbar einfach: zwei Strings rein, ein bool raus. Es gibt keine Variante mit Locale oder Optionen — EqualFold nutzt immer Default-Unicode-Case-Folding ohne Sprach-Kontext.
func EqualFold(s, t string) boolRückgabewert ist true, wenn s und t unter Unicode-Case-Folding gleich sind. Wichtig: die Eingaben dürfen unterschiedlich lang sein — auch zwei verschiedene UTF-8-Längen können dieselbe Folge gefoldeter Runen ergeben.
Das naive Pattern strings.ToLower(a) == strings.ToLower(b) erzeugt für jeden Vergleich zwei neue Strings auf dem Heap — selbst dann, wenn die Eingaben schon klein-geschrieben sind. Bei Vergleichen in heißen Schleifen (HTTP-Header-Lookup, Tag-Matching, Routing) summiert sich das schnell zu spürbarem GC-Druck. EqualFold dagegen iteriert parallel über beide Strings und vergleicht jede Rune nach dem Folding direkt, ohne Speicher anzulegen.
package main
import (
"fmt"
"strings"
)
func main() {
a := "Content-Type"
b := "content-type"
// Naiv: erzeugt zwei neue Strings auf dem Heap
naive := strings.ToLower(a) == strings.ToLower(b)
// Idiomatisch: keine Allokation, walkt rune-für-rune
fold := strings.EqualFold(a, b)
fmt.Println("naive:", naive)
fmt.Println("fold: ", fold)
}naive: true
fold: trueFunktional liefern beide dasselbe Ergebnis, EqualFold ist aber die idiomatische Wahl: kein Heap, früher Abbruch bei Ungleichheit und ein klarer Ausdruck der Absicht „case-insensitiver Vergleich".
EqualFold läuft mit zwei Indizes durch die Eingaben und dekodiert je eine Rune aus s und t. Für reine ASCII-Bytes existiert ein Fast-Path — 'A'..'Z' wird per Bit-Trick auf 'a'..'z' abgebildet, ohne Tabellen-Lookup. Sobald eine Nicht-ASCII-Rune auftaucht, ruft EqualFold unicode.SimpleFold auf, das einen Ring aus äquivalenten Runen durchläuft (z. B. 'K' → 'k' → 'K' zurück, wobei 'K' U+212A das Kelvin-Zeichen ist).
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
// SimpleFold zyklisch durch die Äquivalenzklasse
r := 'K'
for i := 0; i < 3; i++ {
r = unicode.SimpleFold(r)
fmt.Printf("%c (U+%04X)\n", r, r)
}
// Kelvin-Zeichen U+212A foldet auf 'k'
fmt.Println(strings.EqualFold("kilo", "Kilo")) // true (Kelvin)
}k (U+006B)
K (U+212A)
K (U+004B)
trueWichtig ist die 1-zu-1-Eigenschaft: jede Rune wird auf genau eine andere Rune abgebildet, niemals auf eine Folge. Das hat Konsequenzen für Sonderfälle wie das deutsche ß oder die ffi-Ligatur, die wir gleich sehen.
Im Default-Unicode-Case-Folding gilt: 'i' ↔ 'I'. Das ist das, was EqualFold verwendet — ohne language tag. Im Türkischen wäre die korrekte Zuordnung aber 'i' ↔ 'İ' (U+0130, I mit Punkt) und 'ı' (U+0131, dotless i) ↔ 'I'. Für gewöhnliche Software-Vergleiche (Header, Konfigurationsschlüssel, Programmkonstanten) ist das Default-Verhalten richtig; für echten Text in türkischer Sprache muss man auf golang.org/x/text/cases mit language.Turkish ausweichen.
package main
import (
"fmt"
"strings"
)
func main() {
// Default-Folding: i ↔ I
fmt.Println(strings.EqualFold("istanbul", "ISTANBUL")) // true
// İ (U+0130) ist NICHT i unter Default-Folding
fmt.Println(strings.EqualFold("istanbul", "İSTANBUL")) // false
// ı (U+0131, dotless i) ist NICHT I unter Default-Folding
fmt.Println(strings.EqualFold("ıstanbul", "ISTANBUL")) // false
}true
false
falseFür die typischen Anwendungsfälle in Backend-Code — HTTP-Header, Routing, Konfigurations-Keys — ist genau das richtig: man will, dass "content-type" und "Content-Type" gleich sind, unabhängig vom Locale des Servers.
Weil Case-Folding in EqualFold strikt 1-zu-1 ist, fallen alle Fälle durch das Raster, bei denen sich Groß- und Kleinschreibung in der Rune-Anzahl unterscheiden. Das deutsche ß foldet nicht zu SS, und die Ligatur ffi (U+FB03) foldet nicht zu ffi. Solche Äquivalenzen gehören in den Bereich der Normalisierung (NFKC, NFKD) und nicht des Case-Foldings.
package main
import (
"fmt"
"strings"
)
func main() {
// ß ↔ SS: NICHT als gleich erkannt
fmt.Println(strings.EqualFold("straße", "STRASSE")) // false
// ffi-Ligatur ↔ ffi: NICHT als gleich erkannt
fmt.Println(strings.EqualFold("office", "office")) // false
// Aber: ẞ (U+1E9E, Großes Eszett) ↔ ß funktioniert (1-zu-1)
fmt.Println(strings.EqualFold("straße", "STRAẞE")) // true
}false
false
trueWer ß und SS wirklich als gleich behandeln will, muss vorher per golang.org/x/text/unicode/norm und cases.Fold() durch eine richtige Normalisierungs-Pipeline schicken. EqualFold ist explizit nicht dafür gedacht.
HTTP-Header-Namen sind laut RFC 7230 case-insensitiv — Content-Type, content-type und CONTENT-TYPE bezeichnen denselben Header. Das net/http-Paket normalisiert eingehende Header zwar bereits per textproto.CanonicalMIMEHeaderKey, aber sobald man Header aus fremden Quellen vergleicht (Proxy-Eingaben, Custom-Parser, externe APIs), ist EqualFold das Mittel der Wahl.
package main
import (
"fmt"
"strings"
)
type header struct {
name string
value string
}
func findHeader(hs []header, name string) (string, bool) {
for _, h := range hs {
if strings.EqualFold(h.name, name) {
return h.value, true
}
}
return "", false
}
func main() {
hs := []header{
{"Content-Type", "application/json"},
{"X-Request-ID", "abc-123"},
}
if v, ok := findHeader(hs, "content-type"); ok {
fmt.Println("Content-Type:", v)
}
if v, ok := findHeader(hs, "X-REQUEST-ID"); ok {
fmt.Println("Request-ID:", v)
}
}Content-Type: application/json
Request-ID: abc-123In einem Hot-Path-Server-Handler vermeidet EqualFold pro Lookup zwei ToLower-Allokationen — bei Tausenden Requests pro Sekunde spart das messbar GC-Aufwand.
Wenn Nutzer Tags eingeben, sollen "Go", "go" und "GO" als derselbe Tag gelten. Statt jeden eingehenden Tag eifrig zu lowercasen (und damit auch dann zu allokieren, wenn der Tag schon klein ist), kann man die kanonische Liste behalten und per EqualFold matchen.
package main
import (
"fmt"
"strings"
)
var knownTags = []string{"go", "rust", "python", "typescript"}
func canonicalTag(input string) (string, bool) {
for _, t := range knownTags {
if strings.EqualFold(t, input) {
return t, true
}
}
return "", false
}
func main() {
for _, in := range []string{"Go", "RUST", "Python", "C++"} {
if c, ok := canonicalTag(in); ok {
fmt.Printf("%-8s -> %s\n", in, c)
} else {
fmt.Printf("%-8s -> unbekannt\n", in)
}
}
}Go -> go
RUST -> rust
Python -> python
C++ -> unbekanntDer Charme ist, dass die kanonische Form (kleingeschrieben) als „Source of Truth" erhalten bleibt, während die Eingabe so akzeptiert wird, wie sie kommt — ohne Zwischen-Strings und ohne dass der Caller wissen muss, in welcher Schreibweise die Tags intern gespeichert sind.
Allokationsfrei
EqualFold legt keine Zwischen-Strings an — anders als ToLower(a) == ToLower(b), das pro Vergleich zwei Heap-Strings erzeugt.
Unicode-Case-Folding
Intern nutzt EqualFold unicode.SimpleFold mit den Unicode-Foldings-Tabellen, nicht naives ASCII-tolower.
Strikt 1-zu-1
Jede Rune foldet auf genau eine andere Rune — Mehrfach-Mappings wie ß → SS oder ffi → ffi werden nicht unterstützt.
Default ohne language tag
EqualFold kennt keinen Locale-Parameter; das Default-Folding ist Sprach-unabhängig und für Backend-Code (Header, Keys) richtig.
Türkisches İ als Sonderfall
Für echten türkischen Text liefern İ/ı unter Default-Folding falsche Ergebnisse — dann auf golang.org/x/text/cases mit language.Turkish ausweichen.
Ideal für HTTP-Header
RFC-7230-Header sind case-insensitiv — EqualFold passt zu dieser Semantik exakt und vermeidet GC-Druck im Hot-Path.
Threadsafe
Reine Funktion auf unveränderlichen Strings — beliebig viele Goroutines können parallel vergleichen.
Schneller als ToLower-Vergleich
Früher Abbruch bei erster ungleicher Rune und keine Allokation machen EqualFold in Benchmarks regelmäßig schneller als das naive Muster.
Weiterführende Ressourcen
Externe Quellen
strings.EqualFoldgolang.org/x/text/casesfür language-aware Casing- Unicode Case Folding