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.

Go signatur.go
func EqualFold(s, t string) bool

Rü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.

Go vergleich.go
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)
}
Output
naive: true
fold:  true

Funktional 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).

Go folding-ring.go
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)
}
Output
k (U+006B)
K (U+212A)
K (U+004B)
true

Wichtig 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 -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.

Go tuerkisches-i.go
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
}
Output
true
false
false

Fü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 (U+FB03) foldet nicht zu ffi. Solche Äquivalenzen gehören in den Bereich der Normalisierung (NFKC, NFKD) und nicht des Case-Foldings.

Go edge-cases.go
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
}
Output
false
false
true

Wer ß 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-insensitivContent-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.

Go http-header.go
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)
	}
}
Output
Content-Type: application/json
Request-ID: abc-123

In 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.

Go tag-match.go
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)
		}
	}
}
Output
Go       -> go
RUST     -> rust
Python   -> python
C++      -> unbekannt

Der 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 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

/ Weiter

Zurück zu Das strings-Paket — String-Manipulation

Zur Übersicht