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]:

Go signatur.go
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)
	}
}
Output
eins
zwei
drei

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

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

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

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

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

Go edge_cases.go
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")
}
Output
fertig

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

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

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

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht