2. Learn Go programming
This eBook is an introduction to the Go language. The intended audience are people who are familiar with programming and know some programming language. This eBook does not teach you how to program, rather it teaches you how to use Go.
We shall be using the Go Playground1 to write and run all the programs in this eBook.
I have purposely left the details of installing Go on your local computer and its various settings in Appendix A, as I first want you to get your hands wet with writing Go code!
|
Why Go Playground?Go developers use the Playground to share code ideas, test theories, and debug their code, as you soon will too. Every time you create a new application on the Playground, you can click the Share button to get a sharable URL that anyone else can open. Try this one: http://play.golang.org/p/sOqjtiXkRF2 |
There are limitations to the programs that can be run in the playground, which please be aware:
- The playground can use most of the standard library, with some exceptions. The only communication a playground program has to the outside world is by writing to standard output and standard error.
- In the playground the time begins at 2009-11-10 23:00:00 UTC (determining the significance of this date is an exercise for the reader). This makes it easier to cache programs by giving them deterministic output.
- There are also limits on execution time and on CPU and memory usage.
As such, there are certain programs in the eBook that will not run in the Go Playground.
Program: hello_world.go
1 package main
2
3 import "fmt"
4
5 func main() {
6 fmt.Printf("Hello World")
7 }
Once the Go Playground loads up, clear whatever is written on the screen and type our first program:
Click on the Run button and you will see “Hello World” at the bottom of the screen.
Let’s quickly understand the above program:
- Go programs are organized as packages.
- The
importstatement allows you to use external code. Thefmtpackage, in this case, which is provided by the standard library allows you to format and print data. - The
main()function is what gets executed when you run your application - just like in C.
Let’s look at another bigger program in more detail:
Program: hello.go
1 // Single line comment. Line comments start with the
2 // character sequence // and stop at the end of the
3 // line. A line comment acts like a newline.
4 // Comments do not nest.
5
6 /*
7 Multi-
8 line comment
9 */
10
11 /*
12 Package main.
13 All Go files start with package <something>.
14 package main is required for a standalone executable.
15 */
16 package main
17
18 import "fmt"
19
20 func main() {
21 // Multiple statements separated by ;
22 fmt.Printf("Hello "); fmt.Printf("World\n")
23
24 // Single statement then ; not needed
25 fmt.Printf("Learning Go\n")
26 }
Once the Go Playground loads up, clear whatever is written on the screen and type out the above program.
Click on the Run button and check the output at the bottom of the screen.
Let’s understand this program:
2.1 Comments
Go provides:
-
/* */are block comments. Block comments appear mostly as package comments, but are useful within an expression or to disable large swaths of code. - Line comments start with the
//character sequence and stop at the end of the line. - A line comment acts like a newline.
- Comments do not nest.
Line comments are the norm.
2.2 Statements
The Go code is structured in statements. A statement doesn’t need to end with a ;. The Go compiler automatically inserts semicolons at the end of statements. However if multiple statements are written on one line (a practice which is not encouraged for readability reasons), they must be separated by ;.
For more information, please read “Semicolons” in a later chapter.
2.3 Identifiers
Identifiers name program entities such as variables and types.
- An identifier is a sequence of one or more letters and digits.
- The first character in an identifier must be a letter.
- Identifiers are case-sensitive.
2.3.1 Blank identifier
The _ itself is a special identifier, called the blank identifier. It can be used in declarations or variable assignments like any other identifier (and any type can be assigned to it), but its value is discarded, so it cannot be used anymore in the code that follows.
2.3.2 Pre-declared identifiers
The following identifiers are implicitly declared:
| Types | bool, byte, complex64, complex128, error, float32, |
| float64, int, int8, int16, int32, int64, rune, string, | |
| uint, uint8, uint16, uint32, uint64, uintptr | |
| Constants | true, false, iota |
| Null or no value | nil |
| Functions | append, cap, close, complex, copy, delete, imag, len, |
| make, new, panic, print, println, real, recover |
func new(Type) *Type
The new built-in function allocates memory. The first argument is a type, not a value, and the value returned is a pointer to a newly allocated zero value of that type.
func len(v Type) int
The len built-in function returns the length of v, according to its type:
Array: the number of elements in v. String: the number of bytes in v. if v is nil, len(v) is zero.
print and println are low level printing functions that can be used without reverting to the fmt package. These are mainly used for debugging. For more information on this, please read this3.
2.4 Keywords
This is the set of 25 keywords4 or reserved words and may not be used as identifiers.
| break | default | func | interface | select |
| case | defer | go | map | struct |
| chan | else | goto | package | switch |
| const | fallthrough | if | range | type |
| continue | for | import | return | var |
2.5 package
- All Go files start with
package <something>. - In Go,
packageis always first, thenimport, then everything else. -
package mainis required for a standalone executable.. - Every package should have a package comment, a block comment preceding the package clause.
- The package comment should introduce the package and provide information relevant to the package as a whole.
- Package comments should begin with the name of the thing being described and end in a period.
- By convention, packages are given lower case, single-word names; there should be no need for underscores or mixedCaps.
- Go’s convention is that the package name is the last element of the import path: the package imported as “crypto/rot13” should be named
rot13.
2.6 import
-
importdeclaration declares library packages referenced in this file. - The package names are enclosed within
" ". - A package other than
mainis commonly called a library. We are using thefmtlibrary. - A Go program is created by linking together a set of packages through the
importkeyword. -
import "fmt"tells Go that this program needs (functions, or other elements, from) the package fmt5, which implements functionality for formatted IO. -
importloads the public declarations from the compiled package, it does not insert the source code. - The Go installation contains a number of ready-to-use packages, which form the standard library. The entire standard library is documented, with examples, at http://golang.org/pkg/6.
If you are importing a single package then you write it like:
import "fmt"
If multiple packages are needed, they can each be imported by a separate statement:
1 import (
2 "fmt"
3 "os"
4 )
Only apply this when there is more than one entry; in that case it is also clearer to list the package names in alphabetical order.
Note: importing a package which is not used in the rest of the code is a build-error (for example: imported and not used: os).
|
Unused variables or importsThe presence of an unused variable may indicate a bug, while unused imports just slow down compilation, an effect that can become substantial as a program accumulates code and programmers over time. For these reasons, Go refuses to compile programs with unused variables or imports, trading short-term convenience for long-term build speed and program clarity. The reason for having no warnings. If it’s worth complaining about, it’s worth fixing in the code. And if it’s not worth fixing, it’s not worth mentioning. Nowadays, most Go programmers use a tool, goimports7, which automatically rewrites a Go source file to have the correct imports, eliminating the unused imports issue in practice. |
imports8 are organized in groups, with blank lines between them. The standard library packages are in the first group. For example:
1 package main
2
3 import (
4 "fmt"
5 "hash/adler32"
6 "os"
7
8 "appengine/user"
9 "appengine/foo"
10
11 "code.google.com/p/x/y"
12 "github.com/foo/bar"
13 )
The above is a convention.
- After importing a package, you can refer to the names it exports.
- In Go, a name is exported if it begins with a capital letter.
Foois an exported name, as isFOO. The namefoois not exported. A concrete example is in our use offmt.Println, note thePrintlnwith “P” not “p”. - In Go, the use of the words public and private is really not accurate. It is more accurate to say an identifier is exported or not exported from a package.
- When an identifier is exported from a package, it means the identifier can be directly accessed from any other package in the code base.
- When an identifier is not exported from a package, it can’t be directly accessed from any other package.
After the import statement, zero or more constants (const), variables (var), and types (type) can be declared; these are global (have package scope) and are known to all functions in the code, and they are followed by one or more functions (func).
2.6.1 Aliasing of imports
Go allows the aliasing of imports. For instance, the code tpl "text/template" imports the text/template package under the name tpl.
Then the application can call functions in text/templete under the alias name:
txt, _ := tpl.New("test").Parse("Hello {{.Name}}")
Aliases are a great (and necessary) feature of Go package management. But don’t go overboard. Conventionally, aliases are only used when necessary (such as when package names would otherwise collide). Code is more readable without unnecessary aliases.
2.6.2 Removing explicit package reference
We might choose to simplify matters a little by importing the fmt namespace into the namespace of the current source file, which requires we change our import statement.
import . "fmt"
This consequently allows the explicit package reference to be removed from the Println() function call.
Println("hello world")
1 package main
2
3 import . "fmt"
4
5 func main() {
6 Println("hello world")
7 }
|
Exercise 1What is wrong with the following program? |
Program: what_error.go
1 package main
2
3 import "fmt"
4 import "os"
5
6 func main()
7 {
8 fmt.println("Hello, world.")
9 }
Please use this link9 to run the program.
2.7 functions
-
func main() {is a function definition.mainis special. It is the entry point for the executable program. - Go uses brace brackets. The first { must be on the same line as the func-declaration: this is imposed by the compiler. The last } is positioned after the function-code in the column beneath function.
-
mainhas no parameters and no return type. -
fmt.Printf("")here we call a functionPrintffrom the packagefmtto print a string to the screen. - In Go, we use a dot notation to access the function
Printfof the packagefmt.
For more information on functions, please read “More on Functions” in a later chapter.
2.8 String literal
- Interpreted string literals are UTF-8 characters between double quotes
""and may contain non-ASCII characters. - Once assigned to a variable the string can not be changed: strings in Go are immutable.
For more information on strings, please read “More on Strings” in a later chapter.
Go source code is Unicode text encoded in UTF-8. UTF-8 is a character encoding capable of encoding all possible characters (called code points) in Unicode.
The character encoding scheme ASCII comprises 128 code points, Extended ASCII comprises 256 code points and Unicode comprises 1,114,112 code points.
Go programs are made of keywords, constants, variables, operators, types and functions.
2.9 Numbers
Go has several different types to represent numbers. Generally, we split numbers into two different kinds: integers and floating-point numbers.
2.9.1 Integers
Integers – like their mathematical counterpart – are numbers without a decimal component (-3, -2, -1, 0, 1,).
Our system is made up of 10 different digits. Once we’ve exhausted our available digits we represent larger numbers by using 2 (then 3, 4, 5, …) digits put next to each other. For example the number after 9 is 10, the number after 99 is 100 and so on. Computers do the same, but they only have 2 digits instead of 10. So counting looks like this: 0, 1, 10, 11, 100, 101, 110, 111 and so on. The other difference between the number system we use and the one computers use is that all of the integer types have a definite size. They only have room for a certain number of digits. So a 4 bit integer might look like this: 0000, 0001, 0010, 0011, 0100. Eventually we run out of space and most computers just wrap around to the beginning (which can result in some very strange behavior).
Go’s integer types are:
uint8, uint16, uint32, uint64, int8, int16, int32 and int64.
8, 16, 32 and 64 tell us how many bits each of the types use. uint means “unsigned integer” while int means “signed integer”. Unsigned integers only contain positive numbers (or zero). In addition there two alias types: byte which is the same as uint8 and rune which is the same as int32. Bytes are an extremely common unit of measurement used on computers (1 byte = 8 bits, 1024 bytes = 1 kilobyte, 1024 kilobytes = 1 megabyte, …) and therefore Go’s byte data type is often used in the definition of other types.
There are also 3 machine dependent integer types:
uint, int and uintptr.
They are machine dependent because their size depends on the type of architecture you are using.
int64 is big enough to allow accuracy of millionths of a cent.
Generally, if you are working with integers you should just use the int type.
2.9.2 Floating Point Numbers
Floating point numbers are numbers that contain a decimal component (real numbers eg. 1.234, 123.4, 0.00001234, 12340000.0). Their actual representation on a computer is fairly complicated and not really necessary to know in order to know how to use them.
We need only to keep the following in mind:
-
Floating point numbers are inexact. Occasionally, it is not possible to accurately calculate a number. For example computing
1.01 - 0.99results in0.020000000000000018– A number extremely close to what we would expect, but not exactly the same. We can, of course, represent 0.02 with no difficulty. - Like integers, floating point numbers have a certain size (32 bit or 64 bit). Using a larger sized floating point number increases it’s precision (how many digits it can represent).
- There are no constants denoting the IEEE-754 infinity and not-a-number values, but the math10 package’s Inf11, NaN12, IsInf13, and IsNaN14 functions return and test for those values at run time.
Go has two floating point types: float32 and float64 (also often referred to as single precision and double precision respectively) as well as two additional types for representing complex numbers (numbers with imaginary parts): complex64 and complex128.
Floating point precision is finite and you should always be very cautious when making comparisons on real numbers.
Generally, we should stick with float64 when working with floating point numbers.
Later on do read up on the big package.
2.9.3 Some default values
| Type | Initialized Value |
|---|---|
| Boolean | false |
| Integer | 0 |
| Floating Point | 0 |
| String |
"" (empty string) |
2.10 Console output
2.10.1 Print and Println
The function Print print formats using the default formats for its operands and writes to standard output. Spaces are added between operands when neither is a string.
The function Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended.
If you have Go installed on your local machine, then you can read the documentation of any of these functions. Example:
godoc fmt Println
2.10.2 Formatting using Printf
Function Printf from the package fmt use the following format verbs:
| General | %v | the value in a default format |
| Boolean | %t | the word true or false |
| Integer | %d | base 10 |
| Floating-point and complex | %f | decimal point but no exponent, e.g. 123.456 |
| %e | scientific notation, e.g. -1234.456e+78 | |
| %g | %e for large exponents, %f otherwise | |
| String | %s | string |
The default value for %v is:
| bool | %t |
| int, int8 etc. | %d |
| uint, uint8 etc. | %d |
| float32, complex64, etc. | %g |
| string | %s |
Please refer to fmt.Printf format reference - cheat sheet15
Let’s look at an example.
Program: format.go
1 package main
2
3 import "fmt"
4
5 func main() {
6 // Formatting booleans
7 fmt.Printf("%v\n", false)
8 fmt.Printf("%t\n", true)
9
10 // Formatting integers
11 fmt.Printf("%v\n", 987)
12 fmt.Printf("%d\n", 345)
13
14 // Formatting floats
15 fmt.Printf("%v\n", 987123456.987345123432)
16 fmt.Printf("%f\n", 987123456.987345123432)
17 // Use %g for large exponents
18 fmt.Printf("%g\n", 987123456.987345123432)
19
20 // When formatting numbers you will often
21 // want to control the width and precision
22 // of the resulting figure. To specify the
23 // width of an integer, use a number after
24 // the % in the verb. By default the result
25 // will be right-justified and padded with
26 // spaces.
27 fmt.Printf("|%6d|%6d|\n", 23, 987)
28
29 // You can also specify the width of printed
30 // floats, though usually you'll also want to
31 // restrict the decimal precision at the same
32 // time with the width.precision syntax.
33 fmt.Printf("|%6.2f|%6.2f|\n", 3.2, 4.37)
34
35 // To left-justify, use the - flag.
36 fmt.Printf("|%-6.2f|%-6.2f|\n", 3.2, 4.37)
37
38 // So far we’ve seen Printf, which prints
39 // the formatted string to os.Stdout.
40 // Sprintf formats and returns a string
41 // without printing it anywhere.
42 s := fmt.Sprintf("a %s", "string")
43 fmt.Println(s)
44
45 fmt.Println(2001,3456)
46 fmt.Print(20,30)
47 }
Once the Go Playground loads up, clear whatever is written on the screen and type out the above program.
Click on the Run button and check the output at the bottom of the screen.
false
true
987
345
9.871234569873451e+08
987123456.987345
9.871234569873451e+08
| 23| 987|
| 3.20| 4.37|
|3.20 |4.37 |
a string
2001 3456
20 30
2.11 Console input
The functions Scan, Scanf and Scanln from the fmt package read from os.Stdin.
Scan scans text read from standard input, storing successive space-separated values into successive arguments. Newlines count as space. It returns the number of items successfully scanned.
Scanf scans text read from standard input, storing successive space-separated values into successive arguments as determined by the format. It returns the number of items successfully scanned. Scanf parses the arguments according to a format string, analogous to that of Printf with certain restrictions.
Scanln is similar to Scan, but stops scanning at a newline and after the final item there must be a newline or EOF.
Write the following program and store it in the same folder where you had stored hello.go:
Program: ci.go
1 package main
2
3 import "fmt"
4
5 func main() {
6 var username string
7
8 fmt.Println("Enter Username: ")
9 fmt.Scanf("%s", &username)
10
11 fmt.Println("Username: ", username)
12 }
For Windows users, use fmt.Scanf("%s\n", &username) instead of fmt.Scanf("%s", &username).
Run the program.
We shall learn about var and the & operator in later chapters.
2.12 Command-line arguments - Args
|
For the programs in this section, please download, install and setup Go as per Appendix A. Do not use the Go Playground. |
When we invoke a command on the terminal it’s possible to pass the command arguments.
In the command go run hello.go, run and hello.go are arguments to the go command.
In the package os we have a variable Args: var Args []string
Args hold the command-line arguments, starting with the program name. os.Args provides access to raw command-line arguments. Note that the first value in this slice is the path to the program, and os.Args[1:] holds the arguments to the program.
Note: We talk about slices in a later chapter.
Write the program cmd_line_args.go as follows and store it in the same folder where you had stored hello.go:
Program: cmd_line_args.go
1 package main
2
3 import "fmt"
4 import "os"
5
6 func main() {
7 //You can get individual args with normal indexing
8 fmt.Println(os.Args[1], os.Args[2], os.Args[3], os.Args[4])
9 }
Run this program, as follows:
go run cmd_line_args.go a1 a2 a3 a4
The output you will see is something like this:
a1 a2 a3 a4
2.13 Solutions
- https://play.golang.org/↩
- http://play.golang.org/p/sOqjtiXkRF↩
- http://golang.org/ref/spec#Bootstrapping↩
- http://golang.org/ref/spec#Keywords↩
- http://golang.org/pkg/fmt/↩
- http://golang.org/pkg/↩
- https://godoc.org/golang.org/x/tools/cmd/goimports↩
- https://github.com/golang/go/wiki/CodeReviewComments#imports↩
- http://play.golang.org/p/9vq29NXfno↩
- https://golang.org/pkg/math/↩
- https://golang.org/pkg/math/#Inf↩
- https://golang.org/pkg/math/#NaN↩
- https://golang.org/pkg/math/#IsInf↩
- https://golang.org/pkg/math/#IsNaN↩
- http://golang-examples.tumblr.com/post/86795367134/fmt-printf-format-reference-cheat-sheet↩
- http://play.golang.org/p/7Ofcns7kWR↩