strings.FieldsFunc ist die flexible Schwester von strings.Fields: Statt fest auf Whitespace zu trennen, übergibst du ein Prädikat func(rune) bool, das für jede Rune entscheidet, ob sie als Trenner gilt. Alles, was im Prädikat false zurückgibt, wird zum Token; alles, was true ergibt, fällt weg. Aufeinanderfolgende Trenn-Runen werden zu einem einzigen Bruch zusammengezogen, sodass nie leere Tokens entstehen. Damit lassen sich Eingaben sauber an „allem, was kein Buchstabe ist" zerlegen, ohne vorher mit Replacern oder Regex hantieren zu müssen.
Die Signatur ist bewusst minimal: ein Eingabe-String und ein Prädikat, das jede Rune einzeln klassifiziert. Der Rückgabewert ist ein frisch allozierter Slice mit den Tokens zwischen den Trennern — die Trenner selbst tauchen nie im Ergebnis auf.
func FieldsFunc(s string, f func(rune) bool) []stringDas Prädikat wird für jede Rune (nicht jedes Byte) genau einmal aufgerufen. UTF-8-sichere Entscheidungen lassen sich also direkt im Funktionskörper treffen, ohne dass du den Slice selbst rune-weise traversieren musst.
Die Regel ist denkbar einfach: Gibt f(r) für eine Rune true zurück, dann ist diese Rune ein Trenner und gehört zu keinem Token. Gibt sie false zurück, gehört die Rune zum aktuellen Token. Ein klassisches Muster ist „trenne an allem, was nicht alphanumerisch ist" — perfekt für Slug-Generierung oder Suchindex-Tokenisierung.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
nichtAlnum := func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsDigit(r)
}
in := "Hallo, Welt! 2026 — Go ist cool."
tokens := strings.FieldsFunc(in, nichtAlnum)
fmt.Printf("%q\n", tokens)
}["Hallo" "Welt" "2026" "Go" "ist" "cool"]Komma, Ausrufezeichen, Gedankenstrich und Punkt werden alle als Trenner behandelt, weil sie weder Buchstabe noch Ziffer sind. Übrig bleiben sechs saubere Tokens, ohne dass du eine Trennzeichenliste pflegen musst.
Genau wie bei strings.Fields werden mehrere Trenner hintereinander zu einem einzigen Bruch kollabiert. Ein Ergebnis-Slice enthält daher niemals leere Strings, selbst wenn die Eingabe mit Trennern beginnt, endet oder Cluster aus Trennern enthält.
package main
import (
"fmt"
"strings"
)
func main() {
sep := func(r rune) bool { return r == ',' || r == ';' }
in := ",,a,;;b,,,c;;"
tokens := strings.FieldsFunc(in, sep)
fmt.Printf("len=%d %q\n", len(tokens), tokens)
}len=3 ["a" "b" "c"]Führende und nachgestellte Trenner verschwinden, Cluster werden zu einem einzigen Bruch — eine Eigenschaft, die FieldsFunc deutlich von strings.Split unterscheidet, das leere Tokens stehen lässt.
Das unicode-Paket liefert die richtigen Klassifizierer: IsSpace, IsPunct, IsControl, IsLetter, IsDigit. Diese lassen sich beliebig in einem Closure kombinieren, das FieldsFunc als Prädikat erhält. So entstehen ausdrucksstarke Tokenizer ohne externe Bibliotheken.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
// Trenne an Whitespace ODER Interpunktion
trenner := func(r rune) bool {
return unicode.IsSpace(r) || unicode.IsPunct(r)
}
in := "Café-Bar; Müller, et al. — Berlin/Bonn."
tokens := strings.FieldsFunc(in, trenner)
fmt.Printf("%q\n", tokens)
}["Café" "Bar" "Müller" "et" "al" "Berlin" "Bonn"]Der Bindestrich, das Semikolon, das Komma und der Slash werden alle als Interpunktion erkannt; Umlaute wie é, ü bleiben Teil der Tokens, weil sie Buchstaben sind. Genau dieses Verhalten erwartet man von einem Unicode-tauglichen Tokenizer.
FieldsFunc läuft die Eingabe einmal komplett durch und alloziert intern einen Slice plus eine Hilfsstruktur für die gefundenen Spans. Bei einer einzelnen Operation ist das vernachlässigbar — in heißen Pfaden mit Millionen Strings summiert es sich aber. Seit Go 1.24 gibt es strings.FieldsFuncSeq, das einen iter.Seq[string] zurückgibt und so die Slice-Allokation einspart, wenn man die Tokens ohnehin nur durchläuft.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
in := "a,b;c d\te"
sep := func(r rune) bool {
return unicode.IsSpace(r) || r == ',' || r == ';'
}
// Go 1.24+: ohne Slice-Allokation
for tok := range strings.FieldsFuncSeq(in, sep) {
fmt.Println(tok)
}
}a
b
c
d
eDie Iterator-Variante eignet sich, wenn du Tokens nur einmal sequenziell verarbeitest. Brauchst du wahlfreien Zugriff oder die Länge, bleibt das klassische FieldsFunc die richtige Wahl.
Die drei Funktionen erfüllen ähnliche Aufgaben mit unterschiedlichen Trenn-Konzepten. Ein direkter Vergleich macht klar, wann welche Variante passt.
| Funktion | Trenner-Quelle | Kollabiert? | Leere Tokens? |
|---|---|---|---|
FieldsFunc | eigenes func(rune) bool | ja | nein |
Fields | unicode.IsSpace (fest) | ja | nein |
Split | fester Substring | nein | ja |
SplitN | fester Substring + Limit | nein | ja |
Ein SplitFunc existiert in strings nicht — die Kollabier-Semantik von FieldsFunc und die Aufteilungs-Semantik von Split schließen sich konzeptuell aus. Wenn du eine Prädikat-basierte Aufteilung ohne Kollabieren brauchst, musst du das selbst per IndexFunc in einer Schleife bauen.
Ein typischer Slug-/Such-Tokenizer zerlegt einen Titel in seine alphanumerischen Bestandteile, lowercase'd sie und joined sie mit Bindestrichen. Mit FieldsFunc ist das ein Dreizeiler.
package main
import (
"fmt"
"strings"
"unicode"
)
func slug(s string) string {
nichtAlnum := func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsDigit(r)
}
tokens := strings.FieldsFunc(strings.ToLower(s), nichtAlnum)
return strings.Join(tokens, "-")
}
func main() {
fmt.Println(slug("Go 1.24 — Release Notes & Highlights!"))
fmt.Println(slug("Müller's Café (Berlin/Mitte)"))
}go-1-24-release-notes-highlights
müller-s-café-berlin-mitteBeachte: Der Apostroph in Müller's wird als Trenner gewertet, weil er weder Buchstabe noch Ziffer ist. Möchtest du ihn als Wortbestandteil behalten, erweiterst du das Prädikat einfach um r != '\''.
Ein CSV-ähnliches Format, das gemischt Komma, Semikolon und Tab als Feldtrenner zulässt, lässt sich mit FieldsFunc und einem Set-artigen Prädikat lösen. Das vermeidet mehrfache Replace-Aufrufe.
package main
import (
"fmt"
"strings"
)
func main() {
cutset := ",;\t"
sep := func(r rune) bool {
return strings.ContainsRune(cutset, r)
}
in := "alice,42;berlin\tdev"
felder := strings.FieldsFunc(in, sep)
fmt.Printf("%q\n", felder)
}["alice" "42" "berlin" "dev"]Das Closure schließt cutset ein und macht das Prädikat damit datenabhängig konfigurierbar — ein häufiges Muster, wenn die Trennzeichen erst zur Laufzeit aus Konfiguration kommen.
Prädikat entscheidet, was Trenner ist
Gibt f(r) true zurück, ist die Rune ein Trenner und taucht nie im Ergebnis auf — alles dazwischen wird zum Token.
Aufeinanderfolgende Trenner kollabieren
Mehrere true-Runen hintereinander gelten als ein einziger Bruch; das Ergebnis enthält keine leeren Strings.
Keine leeren Tokens
Auch führende und nachgestellte Trenner verschwinden spurlos; FieldsFunc liefert nur nicht-leere Substrings.
Unicode-Kategorien als natürliche Prädikate
unicode.IsSpace, unicode.IsPunct, unicode.IsLetter und Co. lassen sich direkt oder via Closure-Kombination als Prädikat verwenden.
Closures erlauben Capture
Das Prädikat darf externe Werte einschließen — etwa ein Cutset-String oder ein vorbereitetes unicode.RangeTable.
Leere Eingabe = leerer Slice, nicht nil
Bei "" ist das Ergebnis ein Slice der Länge 0 — die Iteration bleibt sicher, eine explizite nil-Prüfung ist nicht nötig.
FieldsFuncSeq für Iterator (Go 1.24+)
Wenn nur sequenziell konsumiert wird, spart strings.FieldsFuncSeq die Slice-Allokation und liefert einen iter.Seq[string].
Threadsicher, wenn das Prädikat es ist
FieldsFunc selbst hat keinen geteilten State; nebenläufige Aufrufe sind sicher, solange das übergebene Prädikat keinen mutablen State teilt.
Weiterführende Ressourcen
Externe Quellen
strings.FieldsFuncstrings.FieldsFuncSeq(Go 1.24+)unicode-Paket