Why does fmt.Scan() behave in a weird way?

Issue

I’m trying to validate the user input. If the user inputs an integer number it works like expected. However, if the user inputs a non-integer string, the variable userTickets gets assigned value 0, but prints Try again! It must be more than zero: many times. To be exact, it prints len(input) times and I don’t understand why.

Also tried achieving desired result using fmt.Scanf("%d", &usertickets) but got an identical result.

Why does it behave this way and how can I write a workaround for it?

package main

import "fmt"

func main() {

    var remainingTickets uint = 50
    var userTickets uint

    fmt.Print("Enter the number of tickets you want to purchase: ")
    fmt.Scan(&userTickets)

    for userTickets > remainingTickets {
        fmt.Printf("We only have %v tickets available!\n", remainingTickets)
        fmt.Print("Try again! Enter the number of tickets: ")
        fmt.Scan(&userTickets)
    }

    for userTickets == 0 {
        fmt.Print("Try again! It must be more than zero: ")
        fmt.Scan(&userTickets)
    }

    fmt.Printf("Remaining tickets: %v\n", remainingTickets-userTickets)

}

Solution

Scan is able to determine that the input isn’t numeric without reading the entire contents of stdin. This is why you validation logic loops for len(input) when non-numeric. While you can use a Scanner as well (and people do recommend that approach), below is an approach similar to yours. Note that all validation checking is done within a single "for" loop as well:

package main

import (
    "fmt"
    "strconv"
)

func main() {

    var remainingTickets uint64 = 50
    
    fmt.Print("Enter the number of tickets you want to purchase: ")

    for {
        var userTickets string
        fmt.Scanln(&userTickets)

        // parse to make sure we get a positive (unsigned) integer
        u64, err := strconv.ParseUint(userTickets,10,64)
        // make sure it's a postive integer and not equal to zero
        if err != nil || u64==0{
            fmt.Print("Try again! You must enter a number greater than zero: ")
            continue
        }

        // check to make sure we have enough tickets left
        if u64 > remainingTickets {
            fmt.Printf("We only have %v tickets available!\n", remainingTickets)
            fmt.Print("Try again! Enter the number of tickets: ")
            continue
        }

        // update remaining tickets
        remainingTickets -= u64
        break
    }

    fmt.Printf("Remaining tickets: %d\n", remainingTickets)
}

Answered By – Gari Singh

Answer Checked By – David Marino (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.