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.

Go signatur.go
func FieldsFunc(s string, f func(rune) bool) []string

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

Go praedikat.go
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)
}
Output
["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.

Go kollabieren.go
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)
}
Output
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.

Go unicode_combo.go
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)
}
Output
["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.

Go hot_path.go
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)
	}
}
Output
a
b
c
d
e

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

FunktionTrenner-QuelleKollabiert?Leere Tokens?
FieldsFunceigenes func(rune) booljanein
Fieldsunicode.IsSpace (fest)janein
Splitfester Substringneinja
SplitNfester Substring + Limitneinja

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.

Go slug.go
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)"))
}
Output
go-1-24-release-notes-highlights
müller-s-café-berlin-mitte

Beachte: 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.

Go multi_sep.go
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)
}
Output
["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

/ Weiter

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

Zur Übersicht