Golang fmt.Scanner Interface
last modified May 8, 2025
This tutorial explains how to use the fmt.Scanner
interface in Go.
We'll cover interface basics with practical examples of custom input scanning.
The fmt.Scanner interface allows types to define their own scanning
behavior. It's used by functions like fmt.Scan
and fmt.Fscan
to parse input according to custom rules.
In Go, fmt.Scanner
requires implementing a single Scan
method. This method defines how the type should read and interpret input data.
Basic Scanner Interface Definition
The fmt.Scanner
interface is simple but powerful. It consists of
just one method that must be implemented.
Note: The method signature must match exactly for proper implementation.
package main import "fmt" type Scanner interface { Scan(state fmt.ScanState, verb rune) error }
The Scan
method takes a fmt.ScanState
and a verb rune.
It returns an error if scanning fails. The method defines custom scanning logic.
Implementing Scanner for a Custom Type
This example shows how to implement fmt.Scanner
for a custom type.
We'll create a Coordinate
type that scans "(x,y)" formatted input.
package main import ( "fmt" "io" "strconv" ) type Coordinate struct { X, Y int } func (c *Coordinate) Scan(state fmt.ScanState, verb rune) error { _, err := fmt.Fscanf(state, "(%d,%d)", &c.X, &c.Y) return err } func main() { var c Coordinate _, err := fmt.Sscan("(10,20)", &c) if err != nil { fmt.Println("Error:", err) return } fmt.Printf("Scanned coordinate: %+v\n", c) }
The Coordinate
type implements Scan
to parse "(x,y)"
format. fmt.Fscanf
helps with the actual parsing from the state.
Scanning CSV Data with Scanner
We can use fmt.Scanner
to parse CSV-formatted input. This example
shows a CSVRecord
type that scans comma-separated values.
package main import ( "fmt" "io" "strings" ) type CSVRecord struct { Fields []string } func (r *CSVRecord) Scan(state fmt.ScanState, verb rune) error { data, err := state.Token(true, func(r rune) bool { return r != '\n' && r != '\r' }) if err != nil { return err } r.Fields = strings.Split(string(data), ",") return nil } func main() { var record CSVRecord fmt.Sscan("John,Doe,42,New York", &record) fmt.Printf("Scanned record: %v\n", record.Fields) }
The Scan
method uses state.Token
to read until newline.
It then splits the input by commas to create the record fields.
Scanning Key-Value Pairs
This example demonstrates scanning key-value pairs with custom formatting.
We'll implement a KeyValue
type that parses "key=value" input.
package main import ( "fmt" "strings" ) type KeyValue struct { Key string Value string } func (kv *KeyValue) Scan(state fmt.ScanState, verb rune) error { data, err := state.Token(false, func(r rune) bool { return r != '=' }) if err != nil { return err } kv.Key = string(data) // Read the '=' separator _, _, err = state.ReadRune() if err != nil { return err } // Read the rest as value value, err := state.Token(false, nil) kv.Value = string(value) return err } func main() { var kv KeyValue fmt.Sscan("name=John", &kv) fmt.Printf("Scanned: %+v\n", kv) }
The method first reads until '=' for the key, then reads the remainder as value.
state.Token
and ReadRune
provide precise control.
Scanning Hexadecimal Numbers
This example shows how to scan hexadecimal numbers with a custom type.
We'll create a HexNumber
that parses "0x" prefixed values.
package main import ( "fmt" "strconv" ) type HexNumber int func (h *HexNumber) Scan(state fmt.ScanState, verb rune) error { // Check for 0x prefix prefix := make([]rune, 2) for i := 0; i < 2; i++ { r, _, err := state.ReadRune() if err != nil { return err } prefix[i] = r } if prefix[0] != '0' || (prefix[1] != 'x' && prefix[1] != 'X') { return fmt.Errorf("missing 0x prefix") } // Read the hex digits digits, err := state.Token(false, nil) if err != nil { return err } value, err := strconv.ParseInt(string(digits), 16, 64) if err != nil { return err } *h = HexNumber(value) return nil } func main() { var num HexNumber fmt.Sscan("0xFF", &num) fmt.Printf("Scanned hex: %d (0x%X)\n", num, num) }
The Scan
method verifies the "0x" prefix before parsing the hex
digits. strconv.ParseInt
converts the string to an integer.
Scanning Custom Date Format
This example implements a Date
type that scans dates in "YYYY-MM-DD"
format. It demonstrates more complex parsing logic.
package main import ( "fmt" "strconv" "time" ) type Date struct { time.Time } func (d *Date) Scan(state fmt.ScanState, verb rune) error { // Read exactly 10 characters (YYYY-MM-DD) data := make([]rune, 10) for i := 0; i < 10; i++ { r, _, err := state.ReadRune() if err != nil { return err } data[i] = r } // Parse the components year, err := strconv.Atoi(string(data[0:4])) if err != nil { return err } month, err := strconv.Atoi(string(data[5:7])) if err != nil { return err } day, err := strconv.Atoi(string(data[8:10])) if err != nil { return err } // Validate separators if data[4] != '-' || data[7] != '-' { return fmt.Errorf("invalid date format") } d.Time = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) return nil } func main() { var date Date fmt.Sscan("2025-05-08", &date) fmt.Printf("Scanned date: %s\n", date.Format("January 02, 2006")) }
The method reads exactly 10 characters and validates the format. It parses year, month, and day components separately before constructing the final date.
Scanning with Multiple Values
This example shows how to scan multiple values with a single Scan
call. We'll create a Person
type that scans name and age together.
package main import ( "fmt" "strings" ) type Person struct { Name string Age int } func (p *Person) Scan(state fmt.ScanState, verb rune) error { // Read name (until whitespace) name, err := state.Token(true, func(r rune) bool { return !(r == ' ' || r == '\t' || r == '\n') }) if err != nil { return err } p.Name = string(name) // Skip whitespace _, _, err = state.ReadRune() if err != nil { return err } // Read age ageStr, err := state.Token(false, nil) if err != nil { return err } // Simple age parsing (in real code, use strconv) p.Age = len(strings.TrimSpace(string(ageStr))) return nil } func main() { var person Person fmt.Sscan("John 42", &person) fmt.Printf("Scanned person: %+v\n", person) }
The method first reads the name until whitespace, then skips whitespace before reading the age. This shows how to handle multi-value scanning in one call.
Source
This tutorial covered the fmt.Scanner
interface in Go with practical
examples of custom input scanning implementations.
Author
List all Golang tutorials.