'How to flush Stdin after fmt.Scanf() in Go?

Here's an issue that's bedeviling me at the moment. When getting input from the user, I want to employ a loop to ask the user to retry until they enter valid input:

// user_input.go
package main

import (
    "fmt"
)

func main() {
    fmt.Println("Please enter an integer: ")

    var userI int

    for {
        _, err := fmt.Scanf("%d", &userI)
        if err == nil {
            break
        }
        fmt.Println("Sorry, invalid input. Please enter an integer: ")
    }

    fmt.Println(userI)    
}

Running the above, if the user enters valid input, no problem:

Please enter an integer: 

3

3

exit code 0, process exited normally.

But try inputting a string instead?

Please enter an integer: 
what?
Sorry, invalid input. Please enter an integer:

Sorry, invalid input. Please enter an integer:

Sorry...

Etc, and it keeps looping character by character until the string is exhausted. Even inputting a single character loops twice, I assume as it parses the newline.

Anyways, there must be a way to flush Stdin in Go?

P.S. In the absence of such a feature, how would you work around it to provide equivalent functionality? I've failed even at that...



Solution 1:[1]

I would fix this by reading until the end of the line after each failure. This clears the rest of the text.

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    stdin := bufio.NewReader(os.Stdin)

    fmt.Println("Please enter an integer: ")

    var userI int

    for {
        _, err := fmt.Fscan(stdin, &userI)
        if err == nil {
            break
        }

        stdin.ReadString('\n')
        fmt.Println("Sorry, invalid input. Please enter an integer: ")
    }

    fmt.Println(userI)
}

Solution 2:[2]

Is it bad to wake up an old question?

I prefer to use fmt.Scanln because A) it doesn't require importing another library (e.g. reader) and B) it doesn't involve an explicit for loop.

func someFunc() {
    fmt.Printf("Please enter an integer: ")

    // Read in an integer
    var i int
    _, err := fmt.Scanln(&i)
    if err != nil {
            fmt.Printf("Error: %s", err.Error())

            // If int read fails, read as string and forget
            var discard string
            fmt.Scanln(&discard)
            return
    }
    fmt.Printf("Input contained %d", i)
}

However, it seems like there ought to be a more elegant solution. Particularly in the case of fmt.Scanln it seems odd that the read stops after the first non-number byte rather than "scanning the line".

Solution 3:[3]

I ran into a similar problem for getting user input but solved it in a slightly different way. Adding to the thread in case someone else finds this useful:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

// Get first word from stdin
func getFirstWord() (string) {
    input := bufio.NewScanner(os.Stdin)
    input.Scan()
    ans := strings.Fields(input.Text())

    if len(ans) == 0 {
        return ""
    } else {
        return ans[0]
    }
}

func main() {
    fmt.Printf("Would you like to play a game?\n> ")
    ans := getFirstWord()
    fmt.Printf("Your answer: %s\n", ans)
}

Solution 4:[4]

I know this has already been answered but this was my implementation:

func flush (reader *bufio.Reader) {
    var i int
    for i = 0; i < reader.Buffered(); i++ {
        reader.ReadByte()
    }
}

This should work in every situation, including ones where "stdin.ReadString('\n')" cannot be used.

Solution 5:[5]

Sorry for digging this back up, but I ran into this today and wanted to improve on the existing answers by using new standard library functionality.

import (
    "bufio"
    "fmt"
    "os"
)

func discardBuffer(r *bufio.Reader) {
    r.Discard(r.Buffered())
}

stdin := bufio.NewReader(os.Stdin)
var i int
for true {
    if _, err := fmt.Fscanln(stdin, &i); err != nil {
        discardBuffer(stdin)
        // Handle error, display message, etc.
        continue
    }
    // Do your other value checks and validations
    break
}

The basic idea is to always buffer your reads from stdin. When you encounter an error while scanning, just discard the buffer contents. That way you start with an empty buffer for your next scan.

Alternatively, you can discard the buffer before you scan, so any stray inputs by the user before then won't get picked up.

func fscanln(r *bufio.Reader, a ...interface{}) error {
    r.Discard(r.Buffered())
    _, err := fmt.Fscanln(r, a...)
    return err
}

stdin := bufio.NewReader(os.Stdin)
var i int
if err := fscanln(stdin, &i); err != nil {
    // Handle error
}

Solution 6:[6]

I use this snippet to filter unnecessary leading space/new line

in := bufio.NewReader(os.Stdin)
result, err = in.ReadString('\n')
for len(strings.TrimSpace(result)) == 0 {
    result, err = in.ReadString('\n')
}

Solution 7:[7]

I usually use bufio.Scanner since the fmt.Scan funcs always split on whitespace.

func promptYN(msg string) bool {
    s := bufio.NewScanner(os.Stdin)
    for {
        fmt.Printf("%s [y/n]: ", msg)
        s.Scan()
        input := strings.ToLower(s.Text())
        if input == "y" || input == "n" {
            return input == "y"
        }
        fmt.Println("Error: expected Y or N.")
    }
}

func promptInt(msg string) int {
    s := bufio.NewScanner(os.Stdin)
    for {
        fmt.Printf("%s [int]: ", msg)
        s.Scan()
        output, err := strconv.Atoi(s.Text())
        if err == nil {
            return output
        }
        fmt.Println("Error: expected an integer.")
    }
}

Or you could make something more universal:

func prompt(msg string, check func(string) bool) {
    s := bufio.NewScanner(os.Stdin)
    for {
        fmt.Printf("%s: ", msg)
        s.Scan()
        if check(s.Text()) {
            return
        }
    }
}

Example:

var f float64
prompt("Enter a float", func(s string) bool {
    f, err = strconv.ParseFloat(s, 64)
    return err == nil
})

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Stephen Weinberg
Solution 2 vastlysuperiorman
Solution 3 T Heath
Solution 4 titegtnodI
Solution 5
Solution 6 Qin Chenfeng
Solution 7 Benny Jobigan