Creating a simple "read number from STDIN" demo

I’m creating a Go tutorial aimed at beginners, but it seems like you have to know a substantial portion of the language before you can interact with a user in a way that would be interesting to a non-programmer. You can’t use os.Args without an understanding of slices, for example. Reading a text file requires extensive knowledge of error handling. And so on.

I’m having trouble even finding examples of reading user input from standard input. Not something that belongs in a production program, I know, but it’s useful when teaching beginners. So here’s my attempt.

Code review would be appreciated, since I’m not a Go expert myself. Not looking to make this bullet proof, I just want to achieve a good code-length-to-stability ratio.

// pass_fail asks the user for a percentage grade, and outputs a
// message saying whether that grade is passing or failing.
package main

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

func main() {
	fmt.Print("Enter a grade: ")
	reader := bufio.NewReader(os.Stdin)
	input, err := reader.ReadString('\n')
	if err != nil {
		log.Fatal(err)
	}
	input = strings.TrimSpace(input)
	grade, err := strconv.ParseFloat(input, 64)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("A grade of %0.2f%% is", grade)
	if grade >= 60 {
		fmt.Println(" passing.")
	} else {
		fmt.Println(" failing!")
	}
}

Thanks in advance!

Since you’re making an example of reading user input from stdin, why not just use fmt.Scanf? It’s true that there are a few concepts you haven’t explained yet in your guide, but I think it’s far better to quickly explain pointers or dismiss them for a later stage rather than having then to explain how bufio and strconv work.

package main

import (
	"fmt"
	"log"
)

func main() {
	fmt.Print("Enter a grade: ")
	var grade int // or grade := 0
	_, err := fmt.Scanf("%d", &grade) // or %f and change grade to float64
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("A grade of %d%% is", grade)
	if grade >= 60 {
		fmt.Println(" passing.")
	} else {
		fmt.Println(" failing!")
	}
}
3 Likes

Thanks for the feedback! I actually tried Scanf first, but I discovered that if Scanf reads an invalid character, it stops reading and leaves the remaining characters in the input buffer. That means the remaining characters get dumped back to the shell. Here’s what happens if I enter letters in the Scanf version:

$ go run temp.go
Enter a grade: dasfhj # Only first character consumed
2017/01/29 12:40:49 expected integer
exit status 1
$ asfhj # Remaining characters dumped in shell
-bash: asfhj: command not found

…Whereas ReadString eats the whole input, and then ParseFloat fails if it’s not the expected format. That behavior seems much less “scary” to me.

And yes, I’d have to either quickly explain pointers or dismiss them for a later stage, which I was dreading doing. bufio and strconv seem much less difficult to quickly explain (you’re just making function calls, just like Println) than pointers (I haven’t shown them anything remotely similar).

Use log.SetFlags(log.Lshortfile) at the beginning of main. This tells the log package to print the filename and line number for any log messages. This makes it much easier to figure out where that log.Fatal message came from.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.