fmt.Sscan verhält sich zu einem String wie fmt.Scan zu Stdin: Es liest whitespace-getrennte Tokens und füllt damit die übergebenen Pointer. In praktischem Go-Code ist Sscan die mit Abstand häufigste Funktion der Scan-Familie, weil man die Eingabe meistens schon als String vorliegen hat — aus einer Datei, einer HTTP-Response, einem Test-Fixture oder einer Konfig-Zeile. Erst wenn echte interaktive Eingabe gefragt ist, kommt Scan selbst zum Zug. Die offizielle Referenz liegt auf pkg.go.dev/fmt#Sscan.
Signatur
func Sscan(str string, a ...any) (n int, err error)
Das erste Argument ist der Eingabe-String, danach folgen — wie bei allen Scan-Varianten — beliebig viele Pointer auf die Ziel-Variablen. Der Rückgabewert n zählt, wie viele Werte erfolgreich gelesen wurden; err ist nil, wenn alles geklappt hat. Anders als bei Scan wird kein Reader involviert: Der String wird intern in einen strings.Reader gewickelt und Token für Token verarbeitet.
Whitespace als Trenner
Wie Scan zerlegt Sscan die Eingabe an Whitespace — Leerzeichen, Tabs, Newlines, mehrere hintereinander zählen als ein Trenner. Das macht es ideal für einfache, durch Spaces separierte Werte und unbrauchbar, sobald die Eingabe Strukturzeichen wie = oder , enthält.
package main
import (
"fmt"
)
func main() {
input := " 42 3.14 gopher "
var i int
var f float64
var s string
n, err := fmt.Sscan(input, &i, &f, &s)
fmt.Println("gelesen:", n, "err:", err)
fmt.Printf("i=%d f=%.2f s=%q\n", i, f, s)
}gelesen: 3 err: <nil>
i=42 f=3.14 s="gopher"Beachte: Die führenden und trailing Leerzeichen sind egal, ebenso wie die Anzahl der Spaces zwischen den Tokens. Sscan interessiert sich nur für die Sequenz nicht-leerer Tokens.
Pointer-Pflicht
Wie bei allen Scan-Funktionen müssen die Argumente Pointer sein, damit Sscan die Werte überhaupt schreiben kann. Ein Werte-Argument ohne & führt zur Laufzeit zum Fehler Sscan: type int is not a pointer.
package main
import "fmt"
func main() {
var x int
// Falsch — keine Adresse:
_, err := fmt.Sscan("7", x)
fmt.Println("ohne &:", err)
// Richtig:
_, err = fmt.Sscan("7", &x)
fmt.Println("mit &:", err, "x=", x)
}ohne &: Sscan: type int is not a pointer
mit &: <nil> x= 7Der Compiler kann diesen Fehler nicht abfangen, weil die variadischen Argumente ...any heißen. Erst zur Laufzeit prüft fmt per Reflection, ob ein Pointer übergeben wurde.
Wann Sscan, wann nicht
Sscan glänzt bei einfachen, whitespace-getrennten Eingaben mit fester Werte-Reihenfolge. Sobald Trennzeichen ins Spiel kommen oder einzelne Felder optional sind, wird es sperrig:
- Whitespace-Liste (
"42 3.14 gopher") —Sscanist perfekt. - Strukturierte Form (
"timeout=30 retries=3") — besserstrings.Splitplusstrconv. - Festes Format mit Trennern (
"2026-05-22") —Sscanfmit%d-%d-%dodertime.Parse. - JSON, YAML, TOML — die jeweiligen Pakete (
encoding/json& Co.), niemals Sscan. - Großer Stream-Parser —
text/scanneroderbufio.Scannermit eigener Split-Funktion.
Die Faustregel: Sobald du anfängst, das Eingabeformat „zurechtzubiegen", bevor du Sscan aufrufst, ist es das falsche Werkzeug.
Sscan vs. Sscanln vs. Sscanf
Die drei String-Scanner unterscheiden sich in zwei Dimensionen: ob sie am Newline stoppen und ob sie ein Format-Template benutzen.
| Funktion | Trenner | Stopp am \n | Format-String | Typischer Einsatz |
|---|---|---|---|---|
Sscan | Whitespace (inkl. \n) | nein | nein | flache Werte-Liste in einer Zeile oder mehreren |
Sscanln | Whitespace ohne \n | ja — \n beendet | nein | genau eine Zeile, danach soll Schluss sein |
Sscanf | laut Format-String | abhängig vom Format | ja | feste Muster wie Datum, Logzeile, Protokoll |
package main
import "fmt"
func main() {
in := "10 20\n30 40"
var a, b, c, d int
n1, _ := fmt.Sscan(in, &a, &b, &c, &d)
fmt.Printf("Sscan: n=%d a=%d b=%d c=%d d=%d\n", n1, a, b, c, d)
var e, f, g, h int
n2, err := fmt.Sscanln(in, &e, &f, &g, &h)
fmt.Printf("Sscanln: n=%d err=%v e=%d f=%d\n", n2, err, e, f)
var x, y int
n3, _ := fmt.Sscanf("Punkt(10,20)", "Punkt(%d,%d)", &x, &y)
fmt.Printf("Sscanf: n=%d x=%d y=%d\n", n3, x, y)
}Sscan: n=4 a=10 b=20 c=30 d=40
Sscanln: n=2 err=unexpected newline e=10 f=20
Sscanf: n=2 x=10 y=20Sscan zieht alle vier Werte über den Newline hinweg, Sscanln bricht beim Zeilenumbruch ab und meldet das per Fehler, Sscanf zerlegt die Klammer-Notation am Format-Template.
Praxis 1 — Konfig-Zeile parsen
Angenommen, aus einer Konfigdatei kommt eine Zeile "timeout=30 retries=3" und daraus sollen zwei Integer werden. Der naive Reflex, das mit Sscan zu erledigen, scheitert, weil timeout=30 als ein einzelnes Whitespace-Token gelesen wird — und Sscan müsste es in einen int schieben, was natürlich nicht klappt.
package main
import "fmt"
func main() {
line := "timeout=30 retries=3"
var timeout, retries int
n, err := fmt.Sscan(line, &timeout, &retries)
fmt.Println("n:", n)
fmt.Println("err:", err)
}n: 0
err: expected integerErwartbar: Sscan will einen Integer und bekommt die Buchstabenfolge timeout=30. Für solche Eingaben ist die Kombination aus strings.Split und strconv der saubere Weg — sie macht das Format explizit und liefert pro Feld einen sprechenden Fehler.
package main
import (
"fmt"
"strconv"
"strings"
)
func parseKV(token string) (string, int, error) {
parts := strings.SplitN(token, "=", 2)
if len(parts) != 2 {
return "", 0, fmt.Errorf("kein key=value: %q", token)
}
v, err := strconv.Atoi(parts[1])
if err != nil {
return "", 0, fmt.Errorf("%s: %w", parts[0], err)
}
return parts[0], v, nil
}
func main() {
line := "timeout=30 retries=3"
cfg := map[string]int{}
for _, tok := range strings.Fields(line) {
k, v, err := parseKV(tok)
if err != nil {
fmt.Println("fehler:", err)
continue
}
cfg[k] = v
}
fmt.Println("cfg:", cfg)
}cfg: map[retries:3 timeout:30]strings.Fields zerlegt an Whitespace (genau wie Sscan es täte), strings.SplitN trennt Schlüssel und Wert am =, strconv.Atoi macht den Integer. Jeder Schritt ist einzeln testbar, und Fehler tragen den Schlüssel im Text — das hilft enorm beim Debuggen einer realen Konfig.
Praxis 2 — Test-Helper
In Tests ist die Welt umgekehrt: Hier kontrolliert man die Eingabe selbst, weiß genau, dass die Tokens whitespace-getrennt sind, und will mit möglichst wenig Boilerplate aus einer Fixture drei Werte gewinnen. Genau dafür ist Sscan gemacht.
package main
import (
"fmt"
)
// parseFixture liest "id score name" aus einer Testzeile.
func parseFixture(line string) (id int, score float64, name string, err error) {
_, err = fmt.Sscan(line, &id, &score, &name)
return
}
func main() {
fixtures := []string{
"1 99.5 alice",
"2 87.0 bob",
"3 73.25 carol",
}
for _, line := range fixtures {
id, score, name, err := parseFixture(line)
if err != nil {
fmt.Println("fehler:", err)
continue
}
fmt.Printf("id=%d score=%.2f name=%s\n", id, score, name)
}
}id=1 score=99.50 name=alice
id=2 score=87.00 name=bob
id=3 score=73.25 name=carolDer Helper ist drei Zeilen lang und bleibt lesbar, weil das Format trivial und unter eigener Kontrolle ist. In Production-Code, wo die Eingabe von außen kommt, wäre derselbe Helper zu fragil — dort gehört eine richtige Format-Validierung hin. In Tests ist dieser Pragmatismus aber genau richtig.
String als erstes Argument
Sscan(str, ...) nimmt die Eingabe direkt als String entgegen — kein Reader, kein File-Handle. Intern wird der String in einen strings.Reader gewickelt.
Pointer-Pflicht
Jedes Ziel braucht &var. Ohne Adresse gibt es einen Laufzeitfehler, weil der Compiler die any-Argumente nicht prüft.
Whitespace-getrenntes Parsing
Spaces, Tabs und Newlines sind austauschbare Trenner; Mehrfach-Whitespace wird wie ein einzelner behandelt.
Häufigste der Scan-Familie
In realem Go-Code begegnet einem Sscan deutlich öfter als Scan selbst, weil die Eingabe meistens schon als String vorliegt.
Type-aware Konvertierung
int, float64, string, bool werden anhand des Pointer-Typs automatisch erkannt und passend geparst.
Strukturiertes lieber anders
Für JSON, YAML oder eigene Token-Sprachen sind encoding/json und text/scanner die besseren Werkzeuge — Sscan ist ein Whitespace-Splitter, kein Parser.
n zählt erfolgreiche Werte
Der Rückgabewert n sagt, wie viele Pointer tatsächlich gefüllt wurden — nützlich, um Teilerfolge von Totalausfällen zu unterscheiden.
Ideal für Test-Helper
Bei kontrollierten Test-Eingaben ist Sscan der kürzeste Weg von Fixture-Zeile zu typisierten Variablen.