Mastering Go

4.8 (13 reviews total)
By Mihalis Tsoukalos
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Go and the Operating System

About this book

Often referred to as Golang (albeit wrongly), the Go programming language is really making strides thanks to some masterclass developments, architected by the greatest programming minds. Shopify CEO Tobias Lutke has been recently quoted as saying “Go will be the server language of the future.” Go programmers are in high demand, but - more controversially - Go takes the stage where C and Unix programmers previously led the way.

The growth of the Go language has seen it become the means by which systems, networking, web, and cloud applications are implemented. If you’re a Go programmer, you’ll already know some Go syntax and will have written some small projects. However, most Go programmers face the difficulty of having to integrate their Golang skills with production code. With Mastering Go, the author shows you just how to tackle this problem. You'll benefit by mastering the use of the libraries and utilize its features, speed, and efficiency for which the Go ecology is justly famous.

Offering a compendium of Go, the book begins with an account of how Go has been implemented. You'll also benefit from an in-depth account of concurrency and systems and network programming imperative for modern-day native cloud development through the course of the book.

Publication date:
April 2018
Publisher
Packt
Pages
606
ISBN
9781788626545

 

Chapter 1. Go and the Operating System

This chapter will serve as an introduction to various Go topics that may appear slightly ingenuous and naïve at first. The topics contained in this chapter, however, will be used throughout the entire book, so you'll need to make sure that you completely understand them. As happens with most practical subjects, the best way to understand something is to experiment with it. In this case, experimenting means writing Go code on your own, making your own mistakes, and learning from them! Just don't let the error messages discourage you.

In the first chapter, you will learn the following topics:

  • The history of the Go programming language
  • The reasons that Go is a good choice for developing your applications
  • Compiling Go code
  • Executing Go code
  • Downloading and using external Go packages
  • Unix standard input, output, and error
  • Printing data on the screen
  • Getting user input
  • Printing data to standard error
  • Working with log files
  • Dealing with error handling in Go
 

The structure of the book


Mastering Go can be divided into three logical parts. The first part consists of four chapters, and it takes a sophisticated look at some important Go concepts, including user input and output, downloading external Go packages, compiling Go code, calling C code from Go, as well as using Go basic types and Go composite types.

The second part consists of three chapters that deal with Go code organization, the design of Go projects, and some advanced features of Go, respectively.

The third part includes the remaining six chapters and deals with the more practical Go topics, including systems programming in Go, concurrency in Go, code testing, optimization, and profiling. The last two chapters of this book will also talk about network programming in Go.

The book will present relatively small, yet complete Go programs that illustrate the concepts presented. This has two main advantages: first, you do not have to look at an endless code listing when trying to learn a single technique, and second, you can use this code as a starting point when creating your own applications and utilities.

Note that the focus of this book is machines that run a variant of the Unix operating system; this does not mean that the Go code presented will not run on Microsoft Windows machines—after all Go is portable! It just means that the included Go code has been tested on various Unix variants, mainly on macOS High Sierra and Debian Linux.

 

The history of Go


Go is a modern, generic purpose open-source programming language that was officially announced at the end of 2009. It began as an internal Google project, which means that it was started as an experiment, and it is inspired by many other programming languages, including C, Pascal, Alef, and Oberon. Its spiritual fathers are Robert Griesemer, Ken Thomson, and Rob Pike, who are professional programmers who designed Go as a language for professional programmers who want to build reliable, robust, and efficient software. Apart from its syntax and its standard functions, Go comes with a pretty rich standard library.

At the time of writing this chapter, the current stable Go version is 1.9.1, but version 1.9.2 is on its way:

$ date Sat Oct 21 20:09:20 EEST 2017
$ go version go version go1.9.1 darwin/amd64

I am pretty confident that by the time this book is published, the output of the go version command will be different! The good news is that due to the way that Go progresses, this book will remain relevant for many years!

If you are installing Go for the first time, you can start by visiting https://golang.org/dl/. However, there is a good chance that your Unix variant already has a ready-to-install package for the Go programming language, so you might want to get Go, using your favorite package manager.

 

Why learn Go?


Go is a modern programming language that allows you to write safe code without silly bugs—do not worry, you can still create complex bugs! Most of all, though, Go wants to have happy developers; therefore, by design, Go code looks attractive and familiar, and it is easy to write.

The next section talks more analytically about the advantages of Go.

 

Go advantages


Go has many advantages—some of them are unique to Go, while others are shared with other programming languages.

The most significant Go advantages and features are as follows:

  • Go is a modern programming language that was created by experienced developers.
  • Go release candidates are used first by Google staff for production use!
  • Go code is easy to read and easy to understand.
  • Go wants happy developers, because a happy developer writes better code!
  • The Go compiler prints practical warning and error messages that help you solve the actual problem. Put simply, the Go compiler is here to help you, and not to make your life miserable by printing pointless output!
  • Go code is portable, especially between Unix machines.
  • Go has support for procedural, concurrent, and distributed programming.
  • Go supports Garbage Collection, so you do not have to deal with memory allocation and deallocation.
  • Go does not have a preprocessor. It does high-speed compilation. As a consequence, Go can also be used as a scripting language.
  • Go can build web applications, and it provides a simple web server for testing purposes.
  • The standard Go library offers many packages that simplify the work of the developer. Additionally, the functions found in the standard Go library are tested and debugged in advance by the people who develop Go, which means that most of the time these functions come without bugs.
  • Go uses static linking by default, which means that the binary files produced can be easily transferred to other machines with the same OS. As a consequence, once a Go program is compiled successfully and an executable file is generated, the developer does not need to worry about libraries, dependencies, and different library versions anymore.
  • You will not need a GUI for developing, debugging, and testing Go applications, as Go can be used from the command-line, which many Unix people prefer.
  • Go supports Unicode, which means that you do not need any extra code for printing characters from multiple human languages.
  • Go keeps concepts orthogonal, because a few orthogonal features work better than many overlapping ones.

Is Go perfect?

There is no such thing as the perfect programming language, and Go is no exception to this rule. However, some programming languages are better at some areas of programming, or we just like them more than other programming languages. Personally, I do not like Java, and while I used to like C++, I do not like it anymore. This is mainly because I find the look of Java and C++ code to be unpleasant.

Some of the disadvantages of Go are as follows:

  • Go does not have direct support for object-oriented programming (OOP), which can be a problem for programmers who are used to writing code in an object-oriented manner. Nevertheless, you can use composition in Go to mimic inheritance.
  • For some people who still prefer C, Go will never replace C!
  • C is still faster than any other programming language for systems programming, mainly because Unix is written in C.

Nevertheless, Go is a pretty decent and modern programming language that will not disappoint if you find the time to learn it and program in it.

What is a preprocessor?

Earlier, I said that Go does not have a preprocessor, and that this is a good thing. A preprocessor is a program that processes your input data and generates output that will be used as the input to another program. In the context of programming languages, the input of a preprocessor is source code that will be processed by the preprocessor before given as input to the compiler of the programming language. The biggest disadvantage of a preprocessor is that it knows nothing about the underlying language or its syntax!

Put simply, this means that when a preprocessor is used, you cannot be certain that the final version of your code will do what you really want it to do because the preprocessor might alter the logic as well as the semantics of your original code!

The list of programming languages with a preprocessor includes, but is not limited to C, C++, Ada, and PL/SQL. The infamous C preprocessor processes lines that begin with # and are called directives or pragmas. Directives and pragmas are not part of the C programming language!

The godoc utility

The Go distribution comes with a plethora of tools that can simplify your life as a programmer. One of these tools is the godoc utility, which allows you to see the documentation of existing Go functions and packages without needing an internet connection.

The godoc utility can be executed either as a normal command-line application that displays its output on a Terminal window, or as a command-line application that starts a web server. In the latter case, you will need a web browser to look at the Go documentation.

Note

If you type godoc without any command-line parameters, you will get the list of the command-line options supported by godoc.

The first way of executing godoc is similar to using the man(1) command, but for Go functions and packages. So, in order to find out information about the Printf() function of the fmt package, you should execute the following command:

$ godoc fmt Printf  

Similarly, you can find out information about the entire fmt package by running the next command:

$ godoc cmd/fmt

The second way requires executing godoc with the -http parameter:

$ godoc -http=:8001

The numeric value used in the preceding command, 8001, is the port number to which the HTTP server will listen. You can choose any port number that is available provided that you have the right privileges. However, note that port numbers 0-1023 are restricted and can only be used by the root user. Thus, it is better to avoid choosing one of them and to pick something else provided that it is not already in use by a different process.

You can omit the equal sign in the command presented and put a space character in its place. So, the next command is the complete equivalent of the previous one:

$ godoc -http :8001

After that, you should point your web browser to the http://localhost:8001/pkg/ URL in order to get the list of the available Go packages and to browse their documentation.

 

Compiling Go code


In this section, you will learn how to compile Go code. The good news is that you can compile your Go code from the command-line without needing a graphical application.

Furthermore, Go does not care about the name of the source file of an autonomous program as long as the package name is main and there is a single main() function in it, because the main() function is where the program execution begins. As a result, you cannot have multiple main() functions in the files of a single project.

We will start our first Go program compilation with a program named aSourceFile.go that contains the next Go code:

package main 
 
import ( 
    "fmt" 
) 
 
func main() { 
    fmt.Println("This is a sample Go program!") 
} 

So, in order to compile aSourceFile.go and create a statically linked executable file, you will need to execute this command:

$ go build aSourceFile.go

After that, you will have a new executable file named aSourceFile:

$ file aSourceFile
aSourceFile: Mach-O 64-bit executable x86_64
$ ls -l aSourceFile -rwxr-xr-x 1 mtsouk staff 1933104 Oct 14 21:50 aSourceFile
$ ./aSourceFile This is a sample Go program!

The main reason that aSourceFile is that big is because it is statically linked, which means that it does not require any external libraries in order to run.

 

Executing Go code


There is another way to execute your Go code that does not create any permanent executable files—it just generates some intermediate files that are automatically deleted afterwards.

Note

The method presented in this chapter allows you to use Go as if it were a scripting language like Python, Ruby, and Perl.

In order to run aSourceFile.go without creating an executable file, you will need to execute the next command:

$ go run aSourceFile.go
This is a sample Go program!

As you can see, the output of the preceding command is exactly the same as before.

Note

With go run, the Go compiler still needs to create an executable file. The fact that you will not see it, that it is automatically executed, and that it is automatically deleted after the program has finished might make you think that there is no need for an executable file!

This book mainly uses go run to execute the example code, primarily because it is simpler than running go build and then running the executable file. Additionally, go run does not leave any files on your hard drive after the program has finished its execution.

 

Two Go rules


Go has strict coding rules that are there to help you avoid silly errors and bugs in your code, as well as to make your code easier to read among the Go community. This section will present two such Go rules that you need to know.

Please remember that the Go compiler is here to help and not make your life miserable. As a result, the main purpose of the Go compiler is to compile and increase the quality of your Go code.

You either use a Go package or do not include it

Go has strict rules about package usage. Therefore, you cannot just include any package that you might think you will need and not use it afterwards. You will learn more about Go packages in Chapter 6, What You Might Not Know About Go Packages.

Look at the following naïve program, which is saved as packageNotUsed.go:

package main 
 
import ( 
    "fmt" 
    "os" 
) 
 
func main() { 
    fmt.Println("Hello there!") 
} 

Note

In this book, you are going to see lots of error messages, error situations, and warnings. I believe that examining code that fails to compile is useful and sometimes even more valuable than just looking at Go code that compiles without any errors. The Go compiler usually displays useful error messages and warnings that will most likely help you resolve an erroneous situation, so do not underestimate these error messages and warnings.

If you execute packageNotUsed.go, you will get the next error message from Go and the program will not be executed:

$ go run packageNotUsed.go
# command-line-arguments
./packageNotUsed.go:5:2: imported and not used: "os"

If you remove the os package from the import list of the program, packageNotUsed.go will compile just fine—try it on your own.

Although, this is not the perfect time to start talking about breaking Go rules, there is a way to bypass this restriction, which is showcased in the next Go code listing that is saved in the packageNotUsedUnderscore.go file:

package main 
 
import ( 
    "fmt" 
    _ "os" 
) 
 
func main() { 
    fmt.Println("Hello there!") 
} 

Using an underscore character in front of a package name in the import list will not create an error message in the compilation process, even if that package is not used in the program:

$ go run packageNotUsedUnderscore.go
Hello there!

Note

The reason that Go allows you to bypass this rule will become more evident in Chapter 6, What You Might Not Know About Go Packages.

There is only one way to format curly braces

Look at the next Go program, which is named curly.go:

package main 
 
import ( 
    "fmt" 
) 
 
func main() 
{ 
    fmt.Println("Go has strict rules for curly braces!") 
}

Although it looks just fine, if you try to execute it, you will be fairly disappointed because you will get the next syntax error message, and the code will not compile and therefore not run:

$ go run curly.go
# command-line-arguments
./curly.go:7:6: missing function body for "main"
./curly.go:8:1: syntax error: unexpected semicolon or newline before
{

The official explanation for this error message is that Go requires the use of semicolons as statement terminators in many contexts, and the compiler automatically inserts the required semicolons when it thinks that they are necessary. Therefore, putting the opening brace ({) in its own line will make the Go compiler insert a semicolon at the end of the previous line (func main()), which produces the error message.

 

Downloading Go packages


Although the standard Go library is very rich, there are times when you will need to download external Go packages in order to use their functionality. This section will teach you how to download an external package and where it will be placed on your Unix machine.

Look at the next simple Go program that is saved as getPackage.go:

package main 
 
import ( 
    "fmt" 
    "github.com/mactsouk/go/simpleGitHub" 
) 
 
func main() { 
    fmt.Println(simpleGitHub. AddTwo (5, 6)) 
} 

This program uses an external package, because one of the import commands uses an internet address. In this case, the external package is called simpleGitHub and is located at https://github.com/mactsouk/go.

If you try to execute getPackage.go right away, you will be disappointed:

$ go run getPackage.go
getPackage.go:5:2: cannot find package "github.com/mactsouk/go/simpleGitHub" in any of:
    /usr/local/Cellar/go/1.9.1/libexec/src/github.com/
mactsouk/go/simpleGitHub (from $GOROOT)
    /Users/mtsouk/go/src/github.com/mactsouk/go/
simpleGitHub (from $GOPATH)

The thing is that you will need to get the missing package onto your computer. In order to download it, you will need to execute the following command:

$ go get -v github.com/mactsouk/go/simpleGitHub
github.com/mactsouk/go (download)
github.com/mactsouk/go/simpleGitHub

After that, you can find the downloaded files at the following directory:

$ ls -l ~/go/src/github.com/mactsouk/go/simpleGitHub/
total 8
-rw-r--r--  1 mtsouk  staff  66 Oct 17 21:47 simpleGitHub.go

However, the go get command also compiles the package. The relevant files can be found at the following place:

$ ls -l ~/go/pkg/darwin_amd64/github.com/mactsouk/go/simpleGitHub.a
-rw-r--r--  1 mtsouk  staff  1050 Oct 17 21:47 /Users/mtsouk/go/pkg/
darwin_amd64/github.com/mactsouk/go/simpleGitHub.a

You are now ready to execute getPackage.go without any problems:

$ go run getPackage.go
11

You can delete the intermediate files of a downloaded Go package as follows:

$ go clean -i -v -x github.com/mactsouk/go/simpleGitHub
cd /Users/mtsouk/go/src/github.com/mactsouk/go/simpleGitHub
rm -f simpleGitHub.test simpleGitHub.test.exe
rm -f /Users/mtsouk/go/pkg/darwin_amd64/github.com/mactsouk/
go/simpleGitHub.a

Similarly, you can delete an entire Go package that you have downloaded locally using the rm(1) Unix command to delete its Go source after using go clean:

$ go clean -i -v -x github.com/mactsouk/go/simpleGitHub
$ rm -rf ~/go/src/github.com/mactsouk/go/simpleGitHub

After executing the former commands, you will need to download the Go package again.

You will learn a lot more about Go packages in Chapter 6, What You Might Not Know About Go Packages.

 

Unix stdin, stdout, and stderr


Every Unix operating system has three files open all the time for its processes. As you know, Unix considers everything a file, even a printer or your mouse. Unix uses file descriptors, which are positive integer values, as an internal representation for accessing all of its open files, which is much more convenient than using long paths.

By default, all Unix systems support three special and standard filenames: /dev/stdin, /dev/stdout, and /dev/stderr, which can also be accessed using file descriptors 0, 1, and 2, respectively. These three file descriptors are also called standard input, standard output, and standard error, respectively. Additionally, file descriptor 0 can be accessed as /dev/fd/0 on a macOS machine and as both /dev/fd/0 and /dev/pts/0 on a Debian Linux machine.

Go uses os.Stdin for accessing standard input, os.Stdout for accessing standard output, and os.Stderr for accessing standard error. Although you can still use /dev/stdin, /dev/stdout, and /dev/stderr, or the related file descriptor values for accessing the same devices, it is better, safer, and more portable to stick with the os.Stdin, os.Stdout, and os.Stderr standard filenames that Go offers.

 

About printing output


As is the case with Unix and C, Go also offers a variety of ways for printing your output on the screen. All of the printing functions in this section require the use of the fmt Go standard package and are illustrated in the printing.go program, which will be presented in two parts.

The simplest way to print something in Go is by using the fmt.Println() and fmt.Printf() functions. The fmt.Printf() function has many similarities to the C printf(3) function. You can also use the fmt.Print() function instead of fmt.Println(). The main difference between fmt.Print() and fmt.Println() is that the latter automatically adds a newline character each time you call it, whereas the biggest difference between fmt.Println() and fmt.Printf() is that the latter requires a format specifier for each thing that you want to print, just like the C printf(3) function, which means that you have better control over what you are doing, though you have to write more code. Go calls these format specifiers verbs. You can find more information about verbs at https://golang.org/pkg/fmt/. If you have to perform any formatting before printing something, or you have to arrange multiple variables, then using fmt.Printf() might be a better choice. However, if you only have to print a single variable, then you might need to choose either fmt.Print() or fmt.Println(), depending on whether you need the newline character or not.

The first part of printing.go contains the next Go code:

package main 
 
import ( 
    "fmt" 
) 
 
func main() { 
    v1 := "123" 
    v2 := 123 
    v3 := "Have a nice day\n" 
    v4 := "abc" 

In this part, you can see the import of the fmt package and the definition of four Go variables. The \n used in v3 is the line break character-if you just want to insert a line break in your output, however, you can call fmt.Println() without any arguments instead of using something like fmt.Print("\n").

The second part follows:

    fmt.Print(v1, v2, v3, v4) 
    fmt.Println() 
    fmt.Println(v1, v2, v3, v4) 
    fmt.Print(v1, " ", v2, " ", v3, " ", v4, "\n") 
    fmt.Printf("%s%d %s %s\n", v1, v2, v3, v4) 
} 

In this part, you print the four variables using fmt.Println(), fmt.Print(), and fmt.Printf() in order to understand their differences better.

If you execute printing.go, you will get the following output:

$ go run printing.go
123123Have a nice day
abc
123 123 Have a nice day
abc
123 123 Have a nice day
abc
123123 Have a nice day
abc

As you can see in the preceding output, the fmt.Println() function also adds a space character between its parameters, which is not the case with fmt.Print(). As a result, a statement like fmt.Println(v1, v2) is equivalent to fmt.Print(v1, " ", v2, "\n").

Apart from fmt.Println(), fmt.Print(), and fmt.Printf(), which are the simplest functions that can be used for generating output on the screen, there is also the S family of functions that includes fmt.Sprintln(), fmt.Sprint(), and fmt.Sprintf(), which are used for creating strings based on the given format and the F family of functions. This includes fmt.Fprintln(), fmt.Fprint() and fmt.Fprintf(), which are used for writing to files using an io.Writer.

Note

You will learn more about the io.Writer and io.Reader interfaces in Chapter 8, Telling a Unix System What to Do.

The next section will teach you how to print your data using standard output, which is pretty common in the Unix world.

 

Using standard output


Standard output is more or less equivalent to printing on the screen. However, using standard output might require the use of functions that do not belong to the fmt package, which is why it is presented in its own section.

The relevant technique will be illustrated in stdOUT.go, which will be offered in three parts. The first part of the program follows:

package main 
 
import ( 
    "io" 
    "os" 
) 

Here stdOUT.go uses the io package instead of the fmt package. The os package is used for reading the command-line arguments of the program and for accessing os.Stdout.

The second portion of stdOUT.go contains the next Go code:

func main() { 
    myString := "" 
    arguments := os.Args 
    if len(arguments) == 1 { 
        myString = "Please give me one argument!" 
    } else { 
        myString = arguments[1] 
    } 

The myString variable holds the text that will be printed on the screen, which is either the first command-line argument of the program or, if the program was executed without any command-line arguments, a hard-coded text message.

The third part of the program is as follows:

    io.WriteString(os.Stdout, myString) 
    io.WriteString(os.Stdout, "\n") 
} 

In this case, the io.WriteString() function works in the same way as the fmt.Print() function; however, it takes only two parameters. The first parameter is the file to which you want to write, which in this case is os.Stdout, and the second parameter is a string variable.

Note

NOTE: Strictly speaking, the type of the first parameter of the io.WriteString() function should be an io.Writer interface, which requires a slice of bytes as the second parameter. However, in this case, a string does the job just fine. You will learn more about slices in Chapter 3, Working with Basic Go Data Types.

Executing stdOUT.go will produce the following output:

$ go run stdOUT.go
Please give me one argument!
$ go run stdOUT.go
123 12 123

The preceding output verifies that the io.WriteString() function sends the contents of its second parameter on the screen when its first parameter is os.Stdout.

 

Getting user input


There are three main ways to acquire user input:

  1. By reading the command-line arguments of a program
  1. By asking the user for input
  1. By reading external files

This section will present the first two ways. Should you wish to learn how to read an external file, visit Chapter 8, Telling a Unix System What to Do.

 

About := and =


Before continuing, it would 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.

Note

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 next error message, which will make perfect sense:

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

Now you might 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 shown in the next 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 should use := even though k has already been defined in the first statement.

Although it seems 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 follows:

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

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

Note

You will learn more about the bufio package, which is related to file input and output, 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 of the time in this book because it contains many handy functions. Its most common functionality is that it provides you with 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 operating system 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()) 
    } 
} 

Here there is a call to bufio.NewScanner() using standard input (os.Stdin) as its parameter. This call returns a bufio.Scanner variable, which is then 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. Note that each line printed by the program begins with the > character.

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

$ go run stdIN.go
21
> 21
This is Mihalis!
> This is Mihalis!

In Unix, you can tell a program to stop reading data from standard input by pressing Ctrl + D.

Note

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 covered in this section will be illustrated by 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 to realize that obtaining 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 as follows:

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 or not by examining the length of os.Args, because the program needs at least one command-line argument to operate. 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 os.Args string slice that has an index value of 1.

The important point here is that 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 a 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.

Note

WARNING: Ignoring all or some of the return values of a Go function, especially the error values, is a very dangerous practice 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 visit all of the elements of the os.Args slice, which was previously assigned to the arguments variable.

Executing cla.go will create the next type 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. Worst of all, the program does not generate any warnings to inform the user that there were one or more errors while processing the command-line arguments:

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

About error output


This section presents a technique for sending data to Unix standard error, which is the Unix way of differentiating between actual values and error output.

The Go code for illustrating the use of standard error in Go is included in stdERR.go and will be presented in two parts. As writing to standard error requires the use of the file descriptor related to standard error, the Go code of stdERR.go will be based on the Go code of stdOUT.go.

The first part of the program follows:

package main 
 
import ( 
    "io" 
    "os" 
) 
func main() { 
    myString := "" 
    arguments := os.Args 
    if len(arguments) == 1 { 
        myString = "Please give me one argument!" 
    } else { 
        myString = arguments[1] 
    } 

So far, stdERR.go is almost identical to stdOUT.go.

The second part of the program, stdERR.go, is as follows:

    io.WriteString(os.Stdout, "This is Standard output\n") 
    io.WriteString(os.Stderr, myString) 
    io.WriteString(os.Stderr, "\n") 

Here you call io.WriteString() two times to write to standard error (os.Stderr) and one more time to write to standard output (os.Stdout).

Executing stdERR.go will create the following output:

$ go run stdERR.go
This is Standard output
Please give me one argument!

The preceding output cannot help you differentiate between data written to standard output and data written to standard error, which could be very useful at times. However, if you are using the bash(1) shell, there is a trick that you can use in order to distinguish between standard output data and standard error data. Almost all Unix shells offer this functionality in their own way.

Thus, when using bash(1), you can redirect the standard error output to a file as follows:

$ go run stdERR.go 2>/tmp/stdError
This is Standard output
$ cat /tmp/stdError
Please give me one argument!

Note

The number after the name of a Unix program or system call refers to the section of the manual to which its page belongs. Although most names can be found only once in the manual pages, which means that indicating the section number is not required, there are names that can be located in multiple sections because they have multiple meanings, such as crontab(1) and crontab(5). Therefore, if you try to retrieve the manual page of a name with multiple meanings without stating its section number, you will get the entry that has the smallest section number.

Similarly, you can discard error output by redirecting it to the /dev/null device, which is like telling Unix to ignore it completely:

$ go run stdERR.go 2>/dev/null
This is Standard output

What we did in the two preceding examples is to redirect the file descriptor of standard error into a file and /dev/null, respectively. If you want to save both standard output and standard error to the same file, you can redirect the file descriptor of standard error (2) to the file descriptor of standard output (1)! The following command shows this technique, which is pretty common in Unix systems:

$ go run stdERR.go >/tmp/output 2>&1
$ cat /tmp/output
This is Standard output
Please give me one argument!

Last, you can send both standard output and standard error to /dev/null as follows:

$ go run stdERR.go >/dev/null 2>&1
 

Writing to log files


The log package allows you to send log messages to the system logging service of your Unix machine, whereas the syslog Go package, which is part of the log package, allows you to define the logging level and the logging facility that your Go program will use.

Usually, most system log files on a Unix operating system can be found under the /var/log directory. However, the log files of many popular services, such as Apache and Nginx, can be found elsewhere, depending on their configuration.

Generally speaking, using a log file to write some information is considered a better practice than writing the same output on the screen for two reasons:

  1. The output does not get lost as it is stored in a file
  1. You can search and process log files using Unix tools such as grep(1), awk(1), and sed(1), which cannot be done when messages are printed on a Terminal window

The log package offers many functions for sending output to the syslog server of a Unix machine. The list of function includes log.Printf(), log.Print(), log.Println(), log.Fatalf(), log.Fatalln(), log.Panic(), log.Panicln(), and log.Panicf().

Note

Logging functions can be extremely handy for debugging your programs, especially server processes written in Go, so you should not undervalue their power.

Logging levels

The logging level is a value that specifies the severity of the log entry. Various logging levels exist including debug, info, notice, warning, err, crit, alert, and emerg (in reverse order of severity).

Logging facilities

A logging facility is a category used for logging information. The value of the logging facility part can be one of auth, authpriv, cron, daemon, kern, lpr, mail, mark, news, syslog, user, UUCP, local0, local1, local2, local3, local4, local5, local6, and local7. It is defined inside /etc/syslog.conf, /etc/rsyslog.conf, or another appropriate file depending on the server process used for system logging on your Unix machine. This means that if a logging facility is not defined and thus handled, the log messages you send to it might get ignored and therefore lost.

Log servers

All Unix machines have a separate server process that is responsible for receiving logging data and writing it to log files. Various log servers exist that work on Unix machines; however, only two of them are used on most Unix variants: syslogd(8) and rsyslogd(8).

On macOS machines, the name of the process is syslogd(8). On the other hard, most Linux machines use rsyslogd(8), which is an improved and more reliable version of syslogd(8), which was the original Unix system utility for message logging.

However, despite the Unix variant you are using or the name of the server process used for logging, logging works the same way on every Unix machine and therefore does not affect the Go code that you will write.

The configuration file of rsyslogd(8) is usually named rsyslog.conf and is located in /etc. The contents of a rsyslog.conf configuration file, without the lines with comments and lines starting with $, might look like the following:

$ grep -v '^#' /etc/rsyslog.conf | grep -v '^$' | grep -v '^\$'
auth,authpriv.*                /var/log/auth.log
*.*;auth,authpriv.none        -/var/log/syslog
daemon.*                      -/var/log/daemon.log
kern.*                        -/var/log/kern.log
lpr.*                          -/var/log/lpr.log
mail.*                        -/var/log/mail.log
user.*                        -/var/log/user.log
mail.info                    -/var/log/mail.info
mail.warn                     -/var/log/mail.warn
mail.err                       /var/log/mail.err
news.crit                      /var/log/news/news.crit
news.err                      /var/log/news/news.err
news.notice                   -/var/log/news/news.notice
*.=debug;\
    auth,authpriv.none;\
    news.none;mail.none       -/var/log/debug
*.=info;*.=notice;*.=warn;\
    auth,authpriv.none;\
    cron,daemon.none;\
    mail,news.none        -/var/log/messages
*.emerg                :omusrmsg:
daemon.*;mail.*;\
   news.err;\
   *.=debug;*.=info;\
   *.=notice;*.=warn    |/dev/xconsole
local7.* /var/log/cisco.log

In order to send your logging information to /var/log/cisco.log, you will need to use the local7 logging facility. The star character after the name of the facility tells the logging server to catch every logging level that goes to the local7 logging facility and write it to /var/log/cisco.log.

The syslogd(8) server has a pretty similar configuration file that is usually /etc/syslog.conf. On macOS High Sierra, the /etc/syslog.conf file is almost empty and has been replaced by /etc/asl.conf. Nevertheless, the logic behind the configuration of /etc/syslog.conf, /etc/rsyslog.conf, and /etc/asl.conf is the same.

A Go program that sends information to log files

The Go code of logFiles.go will explain the use of the log and log/syslog packages.

Note

The log/syslog package is not implemented on the Microsoft Windows version of Go.

The first part of logFiles.go follows:

package main 
 
import ( 
    "fmt" 
    "log" 
    "log/syslog" 
    "os" 
    "path/filepath" 
) 
 
func main() { 
 
    programName := filepath.Base(os.Args[0]) 
    sysLog, err := syslog.New(syslog.LOG_INFO|syslog.LOG_LOCAL7, 
 programName) 

The first parameter of the syslog.New() function is the priority, which is a combination of the logging facility and the logging level. Therefore, a priority of LOG_NOTICE | LOG_MAIL, which is mentioned as an example, will send notice logging-level messages to the MAIL logging facility.

As a result, the preceding code sets the default logging to the local7 logging facility using the info logging level. The second parameter of the syslog.New() function is the name of the process that will appear on the logs as the sender of the message. Generally speaking, it is considered a good practice to use the real name of the executable in order to be able to find the information you want easily in the log files at another time.

The second part of the program contains the following Go code:

    if err != nil { 
        log.Fatal(err) 
    } else { 
        log.SetOutput(sysLog) 
    } 
    log.Println("LOG_INFO + LOG_LOCAL7: Logging in Go!") 

After the call to syslog.New(), you will have to check the error variable that it returns so that you can make sure that everything is fine. If everything is OK, which means that the value of the error variable is equal to nil, you call the log.SetOutput() function. This sets the output destination of the default logger, which in this case is the logger you created earlier on (sysLog). Then you can use log.Println() to send information to the log server.

The third part of logFiles.go comes with the following code:

    sysLog, err = syslog.New(syslog.LOG_MAIL, "Some program!") 
    if err != nil { 
        log.Fatal(err) 
    } else { 
        log.SetOutput(sysLog) 
    } 
 
    log.Println("LOG_MAIL: Logging in Go!") 
    fmt.Println("Will you see this?") 
} 

The last part shows that you can change the logging configuration in your programs as many times as you want, and that you can still use fmt.Println() for printing output on the screen.

The execution of logFiles.go will create the following output on the screen on a Debian Linux machine:

$ go run logFiles.go
Broadcast message from [email protected] (Tue 2017-10-17 20:06:08 EEST):

logFiles[23688]: Some program![23688]: 2017/10/17 20:06:08 LOG_MAIL:
Logging in Go!

Message from [email protected] at Oct 17 20:06:08 ...

Some program![23688]: 2017/10/17 20:06:08 LOG_MAIL: Logging in Go!
Will you see this?

Executing the same Go code on a macOS High Sierra machine generated the following output:

$ go run logFiles.go
Will you see this?

Keep in mind that most Unix machines store logging information in more than one log file, which is also the case with the Debian Linux machine used in this section. As a result, logFiles.go sends its output to multiple log files, which can be verified by the output of the following shell commands:

$ grep LOG_MAIL /var/log/mail.log
Oct 17 20:06:08 mail Some program![23688]: 2017/10/17 20:06:08
LOG_MAIL: Logging in Go!

$ grep LOG_LOCAL7 /var/log/cisco.log
Oct 17 20:06:08 mail logFiles[23688]: 2017/10/17 20:06:08 LOG_INFO + LOG_LOCAL7: Logging in Go!

$ grep LOG_ /var/log/syslog
Oct 17 20:06:08 mail logFiles[23688]: 2017/10/17 20:06:08 LOG_INFO + LOG_LOCAL7: Logging in Go!
Oct 17 20:06:08 mail Some program![23688]: 2017/10/17 20:06:08 
LOG_MAIL: Logging in Go!

The preceding output shows that the message of the log.Println("LOG_INFO + LOG_LOCAL7: Logging in Go!") statement was written on both /var/log/cisco.log and /var/log/syslog, whereas the message of the log.Println("LOG_MAIL: Logging in Go!") statement was written on both /var/log/syslog and /var/log/mail.log.

The important thing to remember from this section is that if the logging server of a Unix machine is not configured to catch all logging facilities, some of the log entries that you send to it might get discarded without any warnings.

About log.Fatal()

In this section, you will see the log.Fatal() function in action. The log.Fatal() function is used when something really bad has happened, and you just want to exit your program as fast as possible after reporting the bad situation. The use of log.Fatal() is illustrated in the logFatal.go program, which contains the following Go code:

package main 
 
import ( 
    "fmt" 
    "log" 
    "log/syslog" 
) 
 
func main() { 
    sysLog, err := syslog.New(syslog.LOG_ALERT|syslog.LOG_MAIL, 
 "Some program!") 
    if err != nil { 
        log.Fatal(err) 
    } else { 
        log.SetOutput(sysLog) 
    } 
 
    log.Fatal(sysLog) 
    fmt.Println("Will you see this?") 
} 

Executing log.Fatal() will create the following output:

$ go run logFatal.go
exit status 1

As you can easily understand, the use of log.Fatal() terminates a Go program at the point where log.Fatal() was called, which is the reason that you did not see the output from the fmt.Println("Will you see this?") statement.

However, because of the parameters of the syslog.New() call, a log entry has been added to the log file that is related to mail, which is /var/log/mail.log:

$ grep "Some program" /var/log/mail.log
Oct 17 20:20:29 iMac Some program![4663]: 2017/10/17 20:20:29 &{17 
Some program! iMac.local {0 0} 0xc42000c220}

About log.Panic()

There are situations where a program will fail for good, and you want to have as much information about the failure as possible. In such difficult circumstances, you might consider using log.Panic(), which is the logging function that is illustrated in this section using the Go code of logPanic.go.

The Go code of logPanic.go follows:

package main 
 
import ( 
    "fmt" 
    "log" 
    "log/syslog" 
) 
 
func main() { 
    sysLog, err := syslog.New(syslog.LOG_ALERT|syslog.LOG_MAIL, 
 "Some program!") 
    if err != nil { 
        log.Fatal(err) 
    } else { 
        log.SetOutput(sysLog) 
    } 
 
    log.Panic(sysLog) 
    fmt.Println("Will you see this?") 
} 

Executing logPanic.go on macOS High Sierra will produce the following output:

$ go run logPanic.go
panic: &{17 Some program! iMac.local   {0 0} 0xc42000c220}

goroutine 1 [running]:
log.Panic(0xc42004ff50, 0x1, 0x1)
    /usr/local/Cellar/go/1.9.1/libexec/src/log/log.go:330 +0xc0
main.main()
    /Users/mtsouk/Desktop/masterGo/ch/ch1/code/logPanic.go:17 +0xea
exit status 2

Running the same program on a Debian Linux machine with Go version 1.3.3 will generate the following output:

$ go run logPanic.go
panic: &{17 Some program! mail   {0 0} 0xc2080400e0}

goroutine 16 [running]:
runtime.panic(0x4ec360, 0xc208000320) 
    /usr/lib/go/src/pkg/runtime/panic.c:279 +0xf5
log.Panic(0xc208055f20, 0x1, 0x1)
    /usr/lib/go/src/pkg/log/log.go:307 +0xb6
main.main()
    /home/mtsouk/Desktop/masterGo/ch/ch1/code/logPanic.go:17 +0x169

goroutine 17 [runnable]:
runtime.MHeap_Scavenger()
    /usr/lib/go/src/pkg/runtime/mheap.c:507
runtime.goexit()
    /usr/lib/go/src/pkg/runtime/proc.c:1445

goroutine 18 [runnable]:
bgsweep()
    /usr/lib/go/src/pkg/runtime/mgc0.c:1976
runtime.goexit()
    /usr/lib/go/src/pkg/runtime/proc.c:1445

goroutine 19 [runnable]:
runfinq()
    /usr/lib/go/src/pkg/runtime/mgc0.c:2606
runtime.goexit()
    /usr/lib/go/src/pkg/runtime/proc.c:1445
exit status 2

The output of log.Panic() includes additional low-level information that will hopefully help you resolve difficult and rare situations that happen in your Go code.

Analogous to the log.Fatal() function, the use of the log.Panic() function will add an entry to the proper log file and will immediately terminate the Go program.

 

Error handling in Go


Errors and error handling are two very important Go topics. Go likes error messages so much that it has a separate data type for errors, named error! This also means that you can easily create your own error messages if you find that what Go gives you is inadequate. You will most likely need to create and handle your own errors when you are developing your own Go packages.

Note that having an error condition is one thing, while deciding how to react to an error condition is a totally different thing. Put simply, not all error conditions are created equal, which means that some error conditions might require that you immediately stop the execution of a program, whereas other error situations might require printing a warning message for the user and continuing with the execution of the program. It is up to the developer to use common sense and decide what to do with each error value that the program might get.

The error data type

Many occasions exist where you might end up having to deal with a new error case while developing your own Go application. The error data type is here to help you define your own errors.

This subsection will teach you how to create your own error variables. As you will see in a while, in order to create a new error variable, you will need to call the New() function of the errors standard Go package.

The example Go code illustrating this process can be found in newError.go, and it will be presented in two parts. The first part of the program follows next:

package main 
 
import ( 
    "errors" 
    "fmt" 
) 
 
func returnError(a, b int) error { 
    if a == b { 
        err := errors.New("Error in returnError() function!") 
        return err 
    } else { 
        return nil 
    } 
} 

There are many interesting things happening here. First of all, you can see the definition of a Go function other than main() for the first time in this book. The name of this new unsophisticated function is returnError(). Additionally, you can see the errors.New() function in action, which takes a string value as its parameter. Last, if a function should return an error variable but there is no error to report, it returns nil instead.

Note

You will learn more about the various types of Go functions in Chapter 6, What You Might Not Know About Go Packages.

The second part of newError.go is as follows:

func main() { 
    err := returnError(1, 2) 
    if err == nil { 
        fmt.Println("returnError() ended normally!") 
    } else { 
        fmt.Println(err) 
    } 
 
    err = returnError(10, 10) 
    if err == nil { 
        fmt.Println("returnError() ended normally!") 
    } else { 
        fmt.Println(err) 
    } 
 
    if err.Error() == "Error in returnError() function!" { 
        fmt.Println("!!") 
    } 
} 

As the code illustrates, most of the time, you need to check whether an error variable is equal to nil or not and then act accordingly. Also presented here is the use of the err.Error() method, which allows you to convert an error variable into a string variable. This function lets you compare an error variable with a string.

Note

Sending your error messages to the logging service of your Unix machine, especially when a Go program is a server or some other critical application. However, the code presented in this book will not follow this principle everywhere in order to avoid filling your log files with unnecessary data.

Executing newError.go will produce the following output:

$ go run newError.go
returnError() ended normally!
Error in returnError() function!
!!

If you try to compare an error variable with a string variable without converting the error variable to a string first, the Go compiler will create the following error message:

# command-line-arguments
./newError.go:33:9: invalid operation: err == "Error in returnError() 
function!" (mismatched types error and string)

Error handling

Error handling is a very important feature of Go because almost all Go functions return an error message or nil, which is the Go way of saying whether there was an error condition while executing a function or not. You will most likely get tired of seeing the following Go code, not only in this book but also in every other Go program you can find on the Internet:

if err != nil { 
    fmt.Println(err) 
    os.Exit(10) 
} 

Note

Do not confuse error handling with printing to error output, because they are two totally different things. The former has to do with Go code that handles error conditions, whereas the latter has to do with writing something to the standard error file descriptor.

The preceding code prints the error message on the screen and exits using os.Exit(). Should you wish to send the error message to the logging service instead of the screen, use the following variation of the preceding Go code:

if err != nil { 
    log.Println(err) 
    os.Exit(10) 
} 

Last, there is another variation of the preceding code that is used when something really bad has happened and you want to terminate the program:

if err != nil { 
    panic(err) 
    os.Exit(10) 
} 

The panic function is a built-in Go function that stops the execution of a program and starts panicking! If you find yourself using panic too often, you might want to reconsider your Go implementation. As you will see in Chapter 2, Understanding Go Internals, Go also offers the recover function, which might be able to save you when you're in some bad situations. For now, you will need to wait for the next chapter to learn more about the power of the panic and recover function pair.

It's now time to see a Go program that not only handles error messages generated by standard Go functions, but one that also defines its own error message. The name of the program is errors.go, and it will be presented to you in five parts. As you will see, the errors.go utility tries to improve the functionality of the cla.go program that you saw earlier in this chapter by examining whether its command-line arguments are acceptable floats or not.

The first part of the program follows:

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

This part of errors.go contains the expected import statements.

The second portion of errors.go comes with the following Go code:

func main() { 
    if len(os.Args) == 1 { 
        fmt.Println("Please give one or more floats.") 
        os.Exit(1) 
    } 
 
    arguments := os.Args 
    var err error = errors.New("An error") 
    k := 1 
    var n float64 

Here you create a new error variable named err in order to initialize it with your own value.

The third part of the program comes next:

    for err != nil { 
        if k >= len(arguments) { 
            fmt.Println("None of the arguments is a float!") 
            return 
        } 
        n, err = strconv.ParseFloat(arguments[k], 64) 
        k++ 
    } 
 
    min, max := n, n 

This is the trickiest part of the program because, if the first command-line argument is not a proper float, you will need to check the next one and keep checking until you find a suitable command-line argument. If none of the command-line arguments are in the correct format, errors.go will terminate and print a message on the screen. All this checking happens by examining the error value that is returned by strconv.ParseFloat(). All of this code is there just for the accurate initialization of the min and max variables.

The fourth part of the program comes with the following Go code:

    for i := 2; i < len(arguments); i++ { 
        n, err := strconv.ParseFloat(arguments[i], 64) 
        if err == nil { 
            if n < min { 
                min = n 
            } 
            if n > max { 
                max = n 
            } 
        } 
    } 

Here you just process all of the right command-line arguments in order to find the minimum and maximum floats among them.

Finally, the last code portion of the program just deals with printing out the current values of the min and max variables:

    fmt.Println("Min:", min) 
    fmt.Println("Max:", max) 
} 

As you can see from the Go code in errors.go, the biggest part of the code is about error handling than about the actual functionality of the program. Unfortunately, this is the case for software developed in most modern programming languages, and Go is no exception.

If you execute errors.go, you will get the following output:

$ go run errors.go a b c
None of the arguments is a float!
$ go run errors.go b c 1 2 3 c -1 100 -200 a
Min: -200
Max: 100
 

Additional resources


Have a look at the following resources:

  • Visit the documentation page of the fmt package at https://golang.org/pkg/fmt/ to learn more about Go verbs and the available functions
 

Exercises


  • Write a Go program that finds the sum of all of its numeric command-line arguments
  • Write a Go program that finds the average value of all of its float command-line arguments
  • Write a Go program that keeps reading integers until it gets the word STOP as input
 

Summary


This chapter addressed many interesting Go topics including compiling Go code, working with standard input, accessing standard output and standard error in Go, processing command-line arguments, printing on the screen, and using the logging service of a Unix system as well as error handling and some general information about Go. You should consider all of these topics as foundational information about Go.

The next chapter is all about the internals of Go, which includes learning about Garbage Collection, working with the Go compiler, calling C code from Go, using the defer keyword, and working with the Go assembler as well as the panic and recover function pair.

About the Author

  • Mihalis Tsoukalos

    Mihalis Tsoukalos is an accomplished author. His previous books, Go Systems Programming and Mastering Go, have become a must-read for the Unix and Linux systems professionals. When not writing books, he spends his working life as a Unix administrator, programmer, DBA, and mathematician who enjoys writing technical articles and learning new technologies. His research interests include programming languages, visualization, and databases. He holds a BSc in Mathematics from the University of Patras and an MSc in IT from University College London, UK. He has written various technical articles for Sys Admin, MacTech, C/C++ Users Journal, Linux Journal, Linux User and Developer, Linux Format, and Linux Voice.

    Browse publications by this author

Latest Reviews

(13 reviews total)
great products, good books!
A good overview of the Go programming environment. A bit verbose in places, but covers many of the important concepts to a moderate depth
I like that the book is very detailed about everything concerning.Go development.

Recommended For You

Book Title
Unlock this full book FREE 10 day trial
Start Free Trial