strings.FieldsSeq ist mit Go 1.24 in die Standardbibliothek gekommen und ist die Iterator-Schwester von strings.Fields. Beide trennen einen String an jeder Folge von Unicode-Whitespace (alles, was unicode.IsSpace als true einstuft — also Leerzeichen, Tabs, Zeilenumbrüche, vertikaler Tab, Form-Feed sowie diverse exotische Space-Codepoints wie U+00A0 oder U+2028). Im Gegensatz zu SplitSeq werden leere Tokens dabei zusammengefasst — zwei Tabs hintereinander erzeugen also kein leeres Element, sondern werden als eine einzige Trennsequenz behandelt.
Der Rückgabewert ist eine iter.Seq[string]. Damit lässt sich die Funktion direkt in for ... range einsetzen, ohne dass intern ein []string aufgebaut wird. Bei großen Logs, langen CSV-freien Textströmen oder Wort-Statistiken auf mehreren MB Text spart das eine komplette Allokation pro Aufruf — und macht ein frühes break möglich, sobald die gesuchte Information gefunden ist.
Die Signatur ist bewusst minimal gehalten und spiegelt strings.Fields exakt — nur der Rückgabetyp wechselt von []string zu iter.Seq[string]:
package main
import (
"fmt"
"strings"
)
func main() {
// func FieldsSeq(s string) iter.Seq[string]
for word := range strings.FieldsSeq("eins zwei drei") {
fmt.Println(word)
}
}eins
zwei
dreiiter.Seq[string] ist ein Typalias für func(yield func(string) bool). Die Funktion gibt also keinen Container zurück, sondern eine Push-Funktion, die Werte an einen Konsumenten weiterreicht. Erst beim range über den Rückgabewert läuft die eigentliche Arbeit los — vorher passiert effektiv nichts. Das ist der Grund, warum die Iterator-Varianten so günstig in der Hot-Path-Performance sind.
Was zählt als Whitespace? Genau das, was unicode.IsSpace zurückgibt — also nicht nur das ASCII-Set, sondern alle Codepoints, die Unicode als Space-Kategorie führt. Wichtig ist das für internationale Texte mit Non-Breaking-Space (U+00A0) oder typografischen Sonderzeichen.
package main
import (
"fmt"
"strings"
)
func main() {
// Tabs, mehrfache Spaces, Newlines, NBSP (U+00A0)
s := "alpha\tbeta gamma\ndelta epsilon"
for word := range strings.FieldsSeq(s) {
fmt.Printf("%q\n", word)
}
}"alpha"
"beta"
"gamma"
"delta"
"epsilon"Jede zusammenhängende Folge von Whitespace — ganz gleich aus welchen Codepoints sie besteht — wird als ein einziger Separator behandelt. Das ist der zentrale Unterschied zu Split / SplitSeq mit einem konkreten Trennzeichen.
strings.SplitSeq(s, " ") und strings.FieldsSeq(s) sehen oberflächlich ähnlich aus, verhalten sich aber bei mehrfachem Whitespace fundamental anders. SplitSeq ist mechanisch: jedes Vorkommen des Separators erzeugt ein Token, auch wenn es leer ist. FieldsSeq ist semantisch — leere Tokens werden geschluckt.
package main
import (
"fmt"
"strings"
)
func main() {
s := "a b" // zwei Spaces zwischen a und b
fmt.Println("SplitSeq:")
for tok := range strings.SplitSeq(s, " ") {
fmt.Printf(" %q\n", tok)
}
fmt.Println("FieldsSeq:")
for tok := range strings.FieldsSeq(s) {
fmt.Printf(" %q\n", tok)
}
}SplitSeq:
"a"
""
"b"
FieldsSeq:
"a"
"b"Faustregel: wenn die Eingabe natürlicher Text mit unregelmäßigem Whitespace ist (Logs, User-Input, Copy-Paste aus PDFs), willst du fast immer FieldsSeq. Wenn die Eingabe ein streng formatiertes CSV-artiges Format mit definiertem Separator ist, willst du SplitSeq — dort sind leere Felder bedeutsam.
Der größte praktische Gewinn gegenüber strings.Fields ist das vorzeitige Verlassen. Bei Fields muss erst der komplette Slice gebaut werden, bevor du das erste Element ansehen kannst. Bei FieldsSeq reicht ein break im Loop — der Iterator hört sofort auf zu produzieren.
package main
import (
"fmt"
"strings"
"unicode"
)
func firstCapitalized(s string) (string, bool) {
for word := range strings.FieldsSeq(s) {
if r := []rune(word)[0]; unicode.IsUpper(r) {
return word, true
}
}
return "", false
}
func main() {
w, ok := firstCapitalized("ein zwei Drei vier fünf")
fmt.Printf("%q (gefunden=%v)\n", w, ok)
}"Drei" (gefunden=true)Genau dieses Muster — „scan, bis Bedingung erfüllt, dann raus" — war vor Go 1.23 entweder hässlich (bufio.Scanner mit ScanWords) oder verschwenderisch (strings.Fields mit anschließendem Abbruch). Mit FieldsSeq wird daraus eine drei-Zeilen-Funktion ohne Allokation.
Funktional sind die beiden austauschbar — slices.Collect(strings.FieldsSeq(s)) liefert dasselbe wie strings.Fields(s). Der Unterschied liegt ausschließlich in der Allokationsstrategie.
package main
import (
"fmt"
"slices"
"strings"
)
func main() {
s := "the quick brown fox jumps over the lazy dog"
// Klassisch: ein []string mit 9 Elementen wird allokiert
words := strings.Fields(s)
fmt.Println("Fields len:", len(words))
// Iterator: nichts wird allokiert, bis du iterierst
count := 0
for range strings.FieldsSeq(s) {
count++
}
fmt.Println("FieldsSeq count:", count)
// Wenn du am Ende doch einen Slice brauchst
all := slices.Collect(strings.FieldsSeq(s))
fmt.Println("Collect:", all)
}Fields len: 9
FieldsSeq count: 9
Collect: [the quick brown fox jumps over the lazy dog]Wann was nehmen? Wenn du alle Wörter ohnehin brauchst und sie mehrfach durchlaufen willst (Sortierung, Index-Zugriff, mehrere Passes), bleibt Fields die bessere Wahl. Sobald du nur einmal sequenziell drüberläufst, vielleicht mit Early-Exit, ist FieldsSeq strikt überlegen.
Beide Edge-Cases verhalten sich erwartbar: keine Tokens, also auch keine Iteration. Der range-Block wird schlicht nicht betreten — kein Panic, kein leeres Token.
package main
import (
"fmt"
"strings"
)
func main() {
for word := range strings.FieldsSeq("") {
fmt.Println("nie:", word)
}
for word := range strings.FieldsSeq(" \t\n ") {
fmt.Println("auch nie:", word)
}
fmt.Println("fertig")
}fertigDas ist konsistent mit strings.Fields, das in denselben Fällen einen leeren Slice zurückgibt. In Praxis-Code bedeutet das: du brauchst keinen Vorab-Check auf len(s) == 0 oder TrimSpace(s) == "" — der Loop kümmert sich selbst darum.
Klassische Anwendung: eine Textdatei einlesen und bis zu N Wörter sammeln. Mit Fields müsstest du erst alles zerlegen und dann den Slice schneiden — bei 100 MB Text eine Verschwendung. Mit FieldsSeq brichst du ab, sobald N erreicht ist.
package main
import (
"fmt"
"strings"
)
func firstN(text string, n int) []string {
out := make([]string, 0, n)
for word := range strings.FieldsSeq(text) {
out = append(out, word)
if len(out) == n {
break
}
}
return out
}
func main() {
t := "alpha beta gamma delta epsilon zeta eta theta"
fmt.Println(firstN(t, 3))
}[alpha beta gamma]Der Trick liegt in der Kombination aus make(..., 0, n) (Ziel-Kapazität vorab reservieren) und dem Iterator (keine Zwischenrepräsentation des Inputs). Damit ist die Funktion O(Größe der Antwort), nicht O(Größe der Eingabe).
Ein häufiger Use-Case in Tools, die Befehle als einzelnen String aus einer Config oder über stdin bekommen — etwa interaktive REPLs, Chatbot-Frontends oder Test-Harnesses. Statt eines vollwertigen Shell-Parsers reicht oft FieldsSeq, um Kommando und Argumente zu trennen.
package main
import (
"fmt"
"strings"
)
func dispatch(line string) string {
var cmd string
var args []string
for tok := range strings.FieldsSeq(line) {
if cmd == "" {
cmd = tok
continue
}
args = append(args, tok)
}
switch cmd {
case "":
return "(leer)"
case "echo":
return strings.Join(args, " ")
default:
return fmt.Sprintf("unbekannt: %q", cmd)
}
}
func main() {
fmt.Println(dispatch("echo hallo welt"))
fmt.Println(dispatch(" "))
fmt.Println(dispatch("foo a b"))
}hallo welt
(leer)
unbekannt: "foo"Beachtenswert: doppelte Spaces im Input stören nicht (FieldsSeq schluckt sie), tabulator-getrennte Eingaben funktionieren automatisch, und ein leerer Input führt zum sauberen case ""-Pfad. Für echtes Shell-Parsing mit Quotes braucht man natürlich einen richtigen Lexer — für 80% der hausgemachten Mini-DSLs reicht das hier.
Go 1.24, iter.Seq[string]
FieldsSeq kam mit Go 1.24 und ist die Iterator-Variante von Fields. Rückgabetyp ist iter.Seq[string], nutzbar direkt in for ... range.
Unicode-Whitespace via IsSpace
Getrennt wird an jeder Folge von Codepoints, für die unicode.IsSpace true ist — also auch NBSP, Tab, Newline, vertikale Spaces.
Leere Tokens werden geschluckt
Im Gegensatz zu SplitSeq erzeugt mehrfacher Whitespace kein leeres Token — zwei Tabs hintereinander sind ein einziger Separator.
Keine Slice-Allokation
FieldsSeq allokiert kein []string. Bei großen Strings spart das im Hot-Path eine komplette Allokation pro Aufruf.
Early break ist erlaubt
break im range-Loop stoppt den Iterator sofort. Damit wird „scan bis Bedingung" zu einer trivialen Operation.
Äquivalent via slices.Collect
slices.Collect(strings.FieldsSeq(s)) ist semantisch identisch zu strings.Fields(s) — gleicher Output, andere Allokationsstrategie.
Leerer Input = leere Iteration
Bei "" oder reinen Whitespace-Strings wird der range-Body schlicht nicht betreten. Kein Panic, kein leeres Token, kein Sonderfall.
Wann Fields, wann FieldsSeq
Mehrfache Pässe oder Index-Zugriff brauchen Fields. Einmaliges sequenzielles Scannen — besonders mit Early-Exit — gehört zu FieldsSeq.