Mit Go 1.24 hat das strings-Paket eine neue Generation von Split-Funktionen bekommen, die statt eines []string einen Iterator vom Typ iter.Seq[string] zurückgeben. strings.FieldsFuncSeq ist dabei das flexibelste Mitglied dieser Familie — es übernimmt das Konzept des klassischen strings.FieldsFunc und kombiniert es mit dem Range-over-Func-Mechanismus aus Go 1.23. Statt einem festen Whitespace-Begriff oder einer fixen Trennzeichen-Menge entscheidet ein benutzerdefiniertes Prädikat func(rune) bool, an welchen Runen aufgespalten wird.

Der entscheidende Vorteil gegenüber FieldsFunc: es entsteht kein zwischenzeitliches Slice. Tokens werden faul, einer nach dem anderen, produziert — und sobald die Range-Schleife per break oder return verlassen wird, hält der Iterator an. Zusätzlich behält die Funktion die „smarte" Semantik ihres Vorbilds bei: aufeinanderfolgende Trennzeichen erzeugen keine leeren Felder, und führende oder schließende Trennzeichen werden ebenfalls geschluckt. Damit eignet sich FieldsFuncSeq ideal für Tokenizer in Parsern, große Textströme oder Hot Paths, in denen Allokationen wehtun.

Die Signatur ist eine direkte Iterator-Übersetzung von FieldsFunc. Statt []string liefert sie eine iter.Seq[string], also genau die Form, die für Range-over-Func gebraucht wird.

Go signatur.go
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	// func FieldsFuncSeq(s string, f func(rune) bool) iter.Seq[string]
	text := "Hallo, Welt! Wie geht's?"

	isSep := func(r rune) bool {
		return unicode.IsSpace(r) || unicode.IsPunct(r)
	}

	for token := range strings.FieldsFuncSeq(text, isSep) {
		fmt.Printf("%q\n", token)
	}
}
Output
"Hallo"
"Welt"
"Wie"
"geht"
"s"

Der Parameter s ist der Eingabe-String, f ist das Prädikat. FieldsFuncSeq scannt s rune-weise und liefert genau dann ein neues Token, wenn zwischen zwei Trenner-Runen mindestens eine Nicht-Trenner-Rune liegt. Das Prädikat wird für jede Rune genau einmal aufgerufen, in der Reihenfolge, in der die Runen im String stehen. Es darf zustandsbehaftet sein, sollte aber in der Praxis möglichst rein bleiben — das macht Tests einfacher und schließt subtile Reentrancy-Probleme aus.

Bei FieldsFuncSeq herrscht dieselbe Konvention wie bei FieldsFunc: liefert f(r) den Wert true, gilt r als Trennzeichen und gehört zu keinem Token. Liefert f(r) den Wert false, ist die Rune ein gültiger Bestandteil eines Tokens. Diese Polarität ist die häufigste Fehlerquelle — wer ein Prädikat aus einer Whitelist baut, muss das Ergebnis negieren.

Go praedikat.go
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	s := "id_42; name=Anna; city=Bonn"

	// Trenner: alles, was weder Buchstabe noch Ziffer ist
	notAlphaNum := func(r rune) bool {
		return !unicode.IsLetter(r) && !unicode.IsDigit(r)
	}

	for tok := range strings.FieldsFuncSeq(s, notAlphaNum) {
		fmt.Println(tok)
	}
}
Output
id
42
name
Anna
city
Bonn

Das Beispiel zeigt einen weiteren wichtigen Punkt: die Prädikat-Logik bestimmt vollständig, was als „zusammengehörig" gilt. Wer Unterstriche als Wortbestandteil behalten will, muss sie im Prädikat explizit ausnehmen — r == '_' als Sonderfall mit false. Ohne diese Ausnahme zerfällt id_42 in zwei Felder.

Die Schwesterfunktion strings.FieldsSeq arbeitet ebenfalls als Iterator, hat aber ein festverdrahtetes Prädikat: unicode.IsSpace. Sie ist damit der direkte Iterator-Pendant zu strings.Fields. FieldsFuncSeq lässt sich als Verallgemeinerung verstehen — alles, was FieldsSeq kann, kann FieldsFuncSeq auch, aber mit dem Preis eines zusätzlichen Funktionsaufrufs pro Rune.

Go vergleich.go
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	s := "  rot   gruen\tblau\n"

	fmt.Println("FieldsSeq:")
	for f := range strings.FieldsSeq(s) {
		fmt.Printf("  %q\n", f)
	}

	fmt.Println("FieldsFuncSeq:")
	for f := range strings.FieldsFuncSeq(s, unicode.IsSpace) {
		fmt.Printf("  %q\n", f)
	}
}
Output
FieldsSeq:
  "rot"
  "gruen"
  "blau"
FieldsFuncSeq:
  "rot"
  "gruen"
  "blau"

Die Faustregel ist einfach: solange das Trennkriterium „irgendeine Form von Whitespace" ist, ist FieldsSeq schneller und klarer. Sobald das Kriterium spezifisch wird — Satzzeichen, Custom-Delimiter, kontextabhängige Regeln —, ist FieldsFuncSeq die richtige Wahl. Beide Funktionen teilen sich die Smart-Skip-Semantik: leere Tokens kommen nie vor.

Das Prädikat erhält eine rune (also einen Unicode-Codepoint), nicht ein einzelnes Byte. Damit ist FieldsFuncSeq UTF-8-sicher und behandelt Mehrbyte-Zeichen korrekt. Bytes wie 0xC3 aus einer UTF-8-Sequenz tauchen im Prädikat nie auf — die Funktion dekodiert intern.

Go utf8_sicher.go
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	// Zeichen mit Diakritika, Emojis, CJK
	s := "Café · 寿司 · Smørrebrød · 🍣sushi"

	isSep := func(r rune) bool {
		return !unicode.IsLetter(r) && r != '\''
	}

	for tok := range strings.FieldsFuncSeq(s, isSep) {
		fmt.Printf("%q\n", tok)
	}
}
Output
"Café"
"寿司"
"Smørrebrød"
"sushi"

Wer mit Bytes statt Runen splitten muss — etwa in einem reinen ASCII-Protokoll, in dem jeder Byte-Wert genau ein Zeichen ist —, sollte trotzdem nicht versuchen, das Prädikat zu „casten". Eine ungültige UTF-8-Sequenz erzeugt eine Ersatzrune (utf8.RuneError), und das Prädikat sieht diese — nicht das ursprüngliche Byte. Für solche Fälle ist bytes.FieldsFunc der passendere Ansatz.

Der größte Vorteil gegenüber strings.FieldsFunc zeigt sich beim frühzeitigen Abbrechen. FieldsFunc alloziert ein komplettes []string und scannt den gesamten String, auch wenn nur das erste Token gebraucht wird. FieldsFuncSeq produziert Tokens faul — ein break stoppt sowohl die Schleife als auch das Scannen.

Go early_break.go
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	log := "ERROR 2026-05-25T10:30:42Z user=42 path=/api ..."

	// Wir wollen nur das erste Wort — den Log-Level
	for level := range strings.FieldsFuncSeq(log, unicode.IsSpace) {
		fmt.Println("level:", level)
		break // bricht den Iterator sofort ab
	}
}
Output
level: ERROR

In einem Log-Aggregator, der pro Zeile nur das Level entscheidet, summiert sich diese Ersparnis: keine Slice-Allokation, kein Scannen unnötiger Tokens. Bei langen Zeilen und hohen Durchsatzraten ist das ein messbarer Faktor — und der Code bleibt trotzdem lesbar.

Wenn s leer ist oder ausschließlich aus Trenner-Runen besteht, liefert der Iterator schlicht keine Werte. Die Range-Schleife läuft also null Mal — es gibt keinen „leeren String" als Token. Das ist konsistent mit FieldsFunc, aber ein häufiger Stolperstein für Entwickler, die das Verhalten von strings.Split gewohnt sind.

Go leer.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	isComma := func(r rune) bool { return r == ',' }

	cases := []string{"", ",", ",,,,", ",a,", "a,,b"}

	for _, c := range cases {
		var out []string
		for tok := range strings.FieldsFuncSeq(c, isComma) {
			out = append(out, tok)
		}
		fmt.Printf("%-8q -> %q\n", c, out)
	}
}
Output
""       -> []
","      -> []
",,,,"   -> []
",a,"    -> ["a"]
"a,,b"   -> ["a" "b"]

Diese Smart-Skip-Semantik ist Stärke und Schwäche zugleich. Für Word-Tokenizer ist sie genau richtig. Für CSV-Parsing, bei dem leere Felder eine eigene Bedeutung haben (a,,b sollte drei Spalten ergeben), ist sie ungeeignet — hier ist strings.SplitSeq die passende Wahl.

Ein häufiger Real-World-Fall: eine Konfigurationszeile mit gemischten Trennzeichen — Kommas, Semikolons, Leerzeichen, Tabs. Statt mit strings.NewReplacer vorzubehandeln, lässt sich ein einziges Prädikat formulieren, das alle Varianten gleichzeitig akzeptiert.

Go custom_delim.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	raw := "redis-1 ; redis-2,redis-3\tredis-4   redis-5"

	isDelim := func(r rune) bool {
		switch r {
		case ',', ';', ' ', '\t':
			return true
		}
		return false
	}

	var hosts []string
	for h := range strings.FieldsFuncSeq(raw, isDelim) {
		hosts = append(hosts, h)
	}

	fmt.Printf("%d Hosts: %v\n", len(hosts), hosts)
}
Output
5 Hosts: [redis-1 redis-2 redis-3 redis-4 redis-5]

Beachtenswert: Mehrfach-Delimiter wie , (Komma plus Leerzeichen) werden korrekt als ein einziger Trennblock behandelt — keine leeren Tokens, kein Trimmen nötig. Genau dafür ist die Smart-Skip-Semantik gemacht. Bei strenger CSV mit signifikanten Leerfeldern wäre dieser Code falsch — dort braucht es einen echten CSV-Parser oder SplitSeq.

Ein klassischer Anwendungsfall ist ein einfacher Lexer, der Bezeichner aus einem Code-Schnipsel extrahiert. Trenner sind alle Runen, die weder Buchstabe noch Ziffer noch Unterstrich sind. Mit FieldsFuncSeq lässt sich der Lexer streamen — ideal, wenn nur die ersten paar Bezeichner für eine Heuristik gebraucht werden.

Go ident_count.go
package main

import (
	"fmt"
	"sort"
	"strings"
	"unicode"
)

func main() {
	src := "func Add(a, b int) int { return a + b }"

	isWordSep := func(r rune) bool {
		return !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_'
	}

	seen := map[string]int{}
	for ident := range strings.FieldsFuncSeq(src, isWordSep) {
		seen[ident]++
	}

	keys := make([]string, 0, len(seen))
	for k := range seen {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		fmt.Printf("%-8s x%d\n", k, seen[k])
	}
}
Output
Add      x1
a        x2
b        x2
func     x1
int      x2
return   x1

In einer echten Codebasis würde man das mit go/scanner oder einem dedizierten Lexer machen — der Aufwand für korrekte Stringliterale, Kommentare und Schlüsselwörter ist beträchtlich. Aber für eine schnelle Heuristik (etwa: „kommt der Bezeichner TODO in diesem File vor?") ist FieldsFuncSeq mit einem schlanken Prädikat oft die richtige Balance aus Aufwand und Genauigkeit.

Iterator-Variante von FieldsFunc

FieldsFuncSeq ist die iter.Seq[string]-Form von FieldsFunc — gleiche Token-Folge, aber lazy und ohne Slice-Allokation.

Prädikat-Polarität

f(r) == true heißt „trennen", false heißt „Teil des Tokens". Wer aus einer Whitelist denkt, muss negieren.

Smart-Skip aktiv

Aufeinanderfolgende Trenner und führende/schließende Trenner erzeugen keine leeren Tokens — anders als SplitSeq.

Rune-basiert

Das Prädikat bekommt rune, nicht byte. UTF-8 wird intern dekodiert, Mehrbyte-Zeichen funktionieren transparent.

Early break spart Arbeit

break in der Range-Schleife stoppt sowohl Konsum als auch Scannen — ideal für „nur erstes Token"-Pfade.

Verallgemeinerung von FieldsSeq

FieldsSeq ist FieldsFuncSeq mit unicode.IsSpace als festem Prädikat — letzteres ist flexibler, ersteres minimal schneller.

Materialisierung über slices.Collect

Wer doch ein []string braucht, ruft slices.Collect(strings.FieldsFuncSeq(...)) — Wahlfreiheit zwischen lazy und eager.

Nicht für signifikante Leerfelder

Sobald leere Felder Bedeutung haben (echtes CSV, fixe Spaltenzahl), ist SplitSeq die richtige Wahl — FieldsFuncSeq schluckt sie.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht