Book Image

Mastering Go - Second Edition

By : Mihalis Tsoukalos
Book Image

Mastering Go - Second Edition

By: Mihalis Tsoukalos

Overview of this book

Often referred to (incorrectly) as Golang, Go is the high-performance systems language of the future. Mastering Go, Second Edition helps you become a productive expert Go programmer, building and improving on the groundbreaking first edition. Mastering Go, Second Edition shows how to put Go to work on real production systems. For programmers who already know the Go language basics, this book provides examples, patterns, and clear explanations to help you deeply understand Go’s capabilities and apply them in your programming work. The book covers the nuances of Go, with in-depth guides on types and structures, packages, concurrency, network programming, compiler design, optimization, and more. Each chapter ends with exercises and resources to fully embed your new knowledge. This second edition includes a completely new chapter on machine learning in Go, guiding you from the foundation statistics techniques through simple regression and clustering to classification, neural networks, and anomaly detection. Other chapters are expanded to cover using Go with Docker and Kubernetes, Git, WebAssembly, JSON, and more. If you take the Go programming language seriously, the second edition of this book is an essential guide on expert techniques.
Table of Contents (15 chapters)

Getting user input

There are three main ways to get user input: firstly, by reading the command-line arguments of a program; secondly, by asking the user for input; or thirdly, by reading external files. This section will present the first two ways. Should you wish to learn how to read an external file, you should visit Chapter 8, Telling a UNIX System What to Do.

About := and =

Before continuing, it will be very useful to talk about the use of := and how it differs from =. The official name for := is the short assignment statement. The short assignment statement can be used in place of a var declaration with an implicit type.

You will rarely see the use of var in Go; the var keyword is mostly used for declaring global variables in Go programs, as well as for declaring variables without an initial value. The reason for the former is that every statement that exists outside of the code of a function must begin with a keyword such as func or var. This means that the short assignment statement cannot be used outside of a function because it is not available there.

The := operator works as follows:

m := 123 

The result of the preceding statement is a new integer variable named m with a value of 123.

However, if you try to use := on an already declared variable, the compilation will fail with the following error message, which makes perfect sense:

$ go run test.go
# command-line-arguments
./test.go:5:4: no new variables on left side of :=
  

So, you might now ask, what will happen if you are expecting two or more values from a function and you want to use an existing variable for one of them. Should you use := or =? The answer is simple: you should use :=, as in the following code example:

i, k := 3, 4 
j, k := 1, 2 

As the j variable is used for the first time in the second statement, you use := even though k has already been defined in the first statement.

Although it may seem boring to talk about such insignificant things, knowing them will save you from various types of errors in the long run!

Reading from standard input

The reading of data from the standard input will be illustrated in stdIN.go, which you will see in two parts. The first part is as follows:

package main 
 
import ( 
    "bufio" 
    "fmt" 
    "os" 
) 

In the preceding code, you can see the use of the bufio package for the first time in this book.

You will learn more about the bufio package in Chapter 8, Telling a UNIX System What to Do.

Although the bufio package is mostly used for file input and output, you will keep seeing the os package all the time in this book because it contains many handy functions; its most common functionality is that it provides a way to access the command-line arguments of a Go program (os.Args).

The official description of the os package tells us that it offers functions that perform OS operations. This includes functions for creating, deleting, and renaming files and directories, as well as functions for learning the UNIX permissions and other characteristics of files and directories. The main advantage of the os package is that it is platform independent. Put simply, its functions will work on both UNIX and Microsoft Windows machines.

The second part of stdIN.go contains the following Go code:

func main() {
var f *os.File f = os.Stdin defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { fmt.Println(">", scanner.Text()) } }

First, there is a call to bufio.NewScanner() using standard input (os.Stdin) as its parameter. This call returns a bufio.Scanner variable, which is used with the Scan() function for reading from os.Stdin line by line. Each line that is read is printed on the screen before getting the next one. Please note that each line that is printed by the program begins with the > character.

The execution of stdIN.go will produce the following kind of output:

$ go run stdIN.go
This is number 21
> This is number 21
This is Mihalis
> This is Mihalis
Hello Go!
> Hello Go!
Press Control + D on a new line to end this program!
> Press Control + D on a new line to end this program!
  

According to the UNIX way, you can tell a program to stop reading data from standard input by pressing Ctrl + D.

The Go code of stdIN.go and stdOUT.go will be very useful when we talk about UNIX pipes in Chapter 8, Telling a UNIX System What to Do, so do not underestimate their simplicity.

Working with command-line arguments

The technique of this section will be illustrated using the Go code of cla.go, which will be presented in three parts. The program will find the minimum and the maximum of its command-line arguments.

The first part of the program is as follows:

package main 
 
import ( 
    "fmt" 
    "os" 
    "strconv" 
) 

What is important here is realizing that getting the command-line arguments requires the use of the os package. Additionally, you need another package, named strconv, in order to be able to convert a command-line argument, which is given as a string, into an arithmetical data type.

The second part of the program is the following:

func main() { 
    if len(os.Args) == 1 { 
        fmt.Println("Please give one or more floats.") 
        os.Exit(1) 
    } 
 
    arguments := os.Args 
    min, _ := strconv.ParseFloat(arguments[1], 64) 
    max, _ := strconv.ParseFloat(arguments[1], 64) 

Here, cla.go checks whether you have any command-line arguments by checking the length of os.Args. This is because the program needs at least one command-line argument to operate. Please note that os.Args is a Go slice with string values. The first element in the slice is the name of the executable program. Therefore, in order to initialize the min and max variables, you will need to use the second element of the string type os.Args slice that has an index value of 1.

There is an important point here: the fact that you are expecting one or more floats does not necessarily mean that the user will give you valid floats, either by accident or on purpose. However, as we have not talked about error handling in Go so far, cla.go assumes that all command-line arguments are in the right format and therefore will be acceptable. As a result, cla.go ignores the error value returned by the strconv.ParseFloat() function using the following statement:

n, _ := strconv.ParseFloat(arguments[i], 64) 

The preceding statement tells Go that you only want to get the first value returned by strconv.ParseFloat() and that you are not interested in the second value, which in this case is an error variable, by assigning it to the underscore character. The underscore character, which is called blank identifier, is the Go way of discarding a value. If a Go function returns multiple values, you can use the blank identifier multiple times.

Ignoring all or some of the return values of a Go function, especially the error values, is a very dangerous technique that should not be used in production code!

The third part comes with the following Go code:

    for i := 2; i < len(arguments); i++ { 
        n, _ := strconv.ParseFloat(arguments[i], 64) 
 
        if n < min { 
            min = n 
        } 
        if n > max { 
            max = n 
        } 
    } 
 
    fmt.Println("Min:", min) 
    fmt.Println("Max:", max) 
} 

Here, you use a for loop that will help you to visit all the elements of the os.Args slice, which was previously assigned to the arguments variable.

Executing cla.go will create the following kind of output:

$ go run cla.go -10 0 1
Min: -10
Max: 1
$ go run cla.go -10
Min: -10
Max: -10
  

As you might expect, the program does not behave well when it receives erroneous input; the worst thing of all is that it does not generate any warnings to inform the user that there was an error (or several) while processing the command-line arguments of the program:

$ go run cla.go a b c 10
Min: 0
Max: 10