A Go Primer
- The Structure of a Go Source File
- Declaring Variables
- Declaring Functions
- Looping in Go
- Creating Enumerations
- Declaring Structures
- Defining Methods
- Implementing Interfaces
- Casting Types
One of the goals of Go was a consistent and unambiguous syntax. This makes it easy for tools to examine Go programs, and also makes it easy to learn. Unhelpful compiler errors make it difficult to learn a language, as anyone who has made a typo in C++ code using templates will know.
In C, for example, function and global variable declarations have almost the same syntax. This means that the compiler can’t easily tell which one you meant if you make an error. It gives you helpful error messages like “expected ;” on a line where you don’t think a semicolon is expected at all.
The Go grammar was designed to make it possible for the compiler to tell you more accurately what you did wrong. It was also designed to avoid the need to state something that can be easily inferred. For example, if you create a variable and set its value to 42, the compiler could probably guess that this variable should be an integer, without it being explicitly stated. If you initialize it with a function call, then the compiler can definitely tell that the type should be whatever the function returned. This was the same problem that C++ 2011 solves with the auto type.
Go adopts JavaScript’s idea of semicolon insertion, and takes it a step further. Any line that can be interpreted as a complete statement has a semicolon implicitly inserted at the end by the parser.1 This means that Go programs can freely omit semicolons as statement terminators. This adds some constraints, for example enforcing a brace style where open braces are at the end of the line at the start of flow-control statements, rather than on their own. If you happen to be a human, this is unfortunate, because it means that you can’t use the highly optimized symmetry recognition paths, which evolution has spent the last million or so years optimizing in your visual cortex, for recognizing code blocks.
This chapter contains an overview of Go syntax. This is not a complete reference. Some aspects are covered in later chapters. In particular, all of the concurrency-related aspects of Go are covered in Chapter 9, Goroutines.
The Structure of a Go Source File
From: hello.go
1 package main 2 import "fmt" 3 4 func main() { 5 fmt.Printf("Hello World!\n") 6 }
A Go source file consists of three parts. The first is a package statement. Go code is arranged in packages, which fill the rôles of both libraries and header files in C. The package in this example is called main, which is special. Every program must contain a main package, which contains a main() function, which is the program entry point.
The next section specifies the packages that this file uses and how they should be imported. In this example, we’re importing the fmt package.
Once the fmt package has been imported, any of its exported types, variables, constants, and functions can be used, prefixed by the name of the package. In this simple example, we’re calling Printf(), a function similar to C’s printf, to print “Hello World!” in the terminal.
Although Go uses static compilation, it’s important to realize that import statements are much closer to Java or Python import directives than to C inclusions. They do not include source code in the current compilation unit. Unlike Java and Python packages, Go packages are imported when the code is linked, rather than when it is run. This ensures that a Go application will not fail because of a missing package on the deployment system, at the cost of increasing the size of the executable. Packages in Go are more important than in languages like Java, because Go only provides access control at the package level, while Java provides it at the class level.
When you compile a package (from one or more .go files) with the Gc compiler, you get an object code file for the package. This includes a metadata section that describes the types and functions that the package exports. It also contains a list of the packages that this package imports.
The input to the 6l linker is always a .6 file for the main package. This file contains references to every package that the main package imports, which may in turn reference further packages. The linker then combines them all.
This eliminates one of the most irritating problems with building complex C programs: you include a header, and then have to work out which library provided it and add the relevant linker flags. With Go, if a package compiles, it will link. You don’t have to provide any extra flags to the linker to tell it to link things that you’ve referenced via import directives.
The remainder of a Go file contains declarations of types, variables, and functions. We’ll explore that for the rest of this chapter.
You may find that you have two packages that you want to import that have the same name. This would cause problems in Go. The badStyleImport.go example is functionally equivalent to the example at the start of this section but renames the fmt package, calling it format. Renaming a package when you import it is usually a bad idea, because it makes your code harder for people to read. You should only ever use it when you explicitly need to disambiguate two packages with the same name.
From: badStyleImport.go
0 package main 1 import format "fmt" 2 3 func main() { 4 format.Printf("Hello World!\n") 5 }