Chapter 1. Introducing Go

In this chapter

  • Solving modern computing challenges with Go
  • Using the Go tools

Computers have evolved, but programming languages haven’t kept up the same pace of evolution. The cell phones we carry might have more CPU cores than the first computer we used. High-powered servers now have 64, 128, or even more cores, but we’re still programming using the techniques we were using for a single core.

The art of programming has evolved too. Most programs aren’t written by a single developer any more: they’re written by teams of people sitting in different time zones and working at different times of the day. Large projects are broken up into smaller pieces and assigned to programmers who then deliver their work back to the team in the form of a library or package that can be used across an entire suite of applications.

Today’s programmers and companies believe more than ever in the power of open source software. Go is a programming language that makes sharing code easy. Go ships with tools that make it simple to use packages written by others, and Go makes it easy to share our own packages too.

In this chapter you’ll see how Go is different from other programming languages. Go rethinks the traditional object-oriented development you might be used to, while still providing an efficient means for code reuse. Go makes it easier for you to effectively use all of the cores on your expensive server, and it takes away the penalty of compiling a very large project.

As you read this chapter, you’ll get a feeling for the many decisions that shaped the creation of Go, from its concurrency model to its lightning-fast compiler. We mentioned it in the preface, but it bears repeating: this book has been written for an intermediate-level developer who has some experience with other programming languages and wants to learn Go. Our goal in writing this book is to provide you an intensive, comprehensive, and idiomatic view of the language. We focus on both the specification and implementation of the language, including the wide-ranging topics of language syntax, Go’s type system, concurrency, channels, testing, and more. We believe this book is perfect for anyone who wants a jump-start in learning Go or who wants a more thorough understanding of the language and its internals.

The source code for the examples in the book is available at https://github.com/goinaction/code.

We hope you’ll appreciate the tools that ship with Go to make your life as a developer easier. In the end, you’ll appreciate why so many developers are choosing Go when they start up that new project.

1.1. Solving modern programming challenges with Go

The Go team went to great lengths to solve the problems facing software developers today. Developers have to make an uncomfortable choice between rapid development and performance when choosing a language for their projects. Languages like C and C++ offer fast execution, whereas languages like Ruby and Python offer rapid development. Go bridges these competing worlds and offers a high-performance language with features that make development fast.

As we explore Go, you’ll find well-planned features and concise syntax. As a language, Go is defined not only by what it includes, but by what it doesn’t include. Go has a concise syntax with few keywords to memorize. Go has a compiler that’s so fast, sometimes you’ll forget it’s running. As a Go developer, you’ll spend significantly less time waiting for your project to build. Because of Go’s built-in concurrency features, your software will scale to use the resources available without forcing you to use special threading libraries. Go uses a simple and effective type system that takes much of the overhead out of object-oriented development and lets you focus on code reuse. Go also has a garbage collector, so you don’t have to manage your own memory. Let’s look quickly at these key features.

1.1.1. Development speed

Compiling a large application in C or C++ takes more time than getting a cup of coffee. Figure 1.1 shows an XKCD classic excuse for messing around in the office.

Figure 1.1. Working hard? (via XKCD)

Go offers lightning-quick compiles by using a smart compiler and simplified dependency resolution algorithms. When you build a Go program, the compiler only needs to look at the libraries that you directly include, rather than traversing the dependencies of all the libraries that are included in the entire dependency chain like Java, C, and C++. Consequently, many Go applications compile in under a second. The entire Go source tree compiles in under 20 seconds on modern hardware.

Writing applications in dynamic languages makes you productive quickly because there are no intermediate steps between writing code and executing it. The trade-off is that dynamic languages don’t offer the type safety that static languages do and often need a comprehensive test suite to avoid discovering incorrect type bugs at runtime.

Imagine writing a large application in a dynamic language like JavaScript and coming across a function that expects to receive a field called ID. Is that an integer, a string, or a UUID? The way to find out is to look at the source. You could try to execute the function with a number or a string and see what happens. In Go, you wouldn’t spend time wondering, because the compiler will catch type differences for you.

1.1.2. Concurrency

One of the hardest things to do as a programmer is to write an application that effectively uses the available resources of the hardware running it. Modern computers have many cores, but most programming languages don’t have effective tools for utilizing those additional resources easily. They often require a lot of thread synchronization code, which is prone to errors.

Go’s concurrency support is one of its strongest features. Goroutines are like threads, but use far less memory and require less code to use. Channels are data structures that let you send typed messages between goroutines with synchronization built in. This facilitates a programming model where you send data between goroutines, rather than letting the goroutines fight to use the same data. Let’s look at these features in more detail now.

Goroutines

Goroutines are functions that run concurrently with other goroutines, including the entry point of your program. In other languages, you’d use threads to accomplish the same thing, but in Go many goroutines execute on a single thread. For example, if you write a web server and you want to handle different web requests simultaneously, you’d have to write a lot of extra code to use threads in C or Java. In Go, the net/http library has concurrency built in using goroutines. Each inbound request automatically runs on its own goroutine. Goroutines use less memory than threads and the Go runtime will automatically schedule the execution of goroutines against a set of configured logical processors. Each logical processor is bound to a single OS thread (see figure 1.2). This makes your application much more efficient with significantly less development effort.

Figure 1.2. Many goroutines execute on a single OS thread

If you want to execute some code concurrently while you move on to accomplish other things, a goroutine is perfect for the job. Here’s a quick example:

func log(msg string){
    ... some logging code here
}

// Elsewhere in our code after we've discovered an error.
go log("something dire happened")

That keyword go is all you need to schedule the log function to run as a goroutine and for that goroutine be run concurrently with other goroutines. This means you can continue executing the rest of your application while the logging happens concurrently, which often results in greater perceived performance for your end users. As stated before, goroutines have minimal overhead, so it isn’t uncommon to spawn tens of thousands of them. We’ll explore goroutines and concurrency more in-depth in chapter 6.

Channels

Channels are data structures that enable safe data communication between goroutines. Channels help you to avoid problems typically seen in programming languages that allow shared memory access.

The hardest part of concurrency is ensuring that your data isn’t unexpectedly modified by concurrently running processes, threads, or goroutines. When multiple threads change the same data without locks or synchronization, heartache always follows. In other languages, when you have global variables and shared memory, you’re required to use complicated locking disciplines to prevent unsynchronized changes to the same variables.

Channels help to solve this problem by providing a pattern that makes data safe from concurrent modification. Channels help to enforce the pattern that only one goroutine should modify the data at any time. You can see an example of this flow in figure 1.3, where channels are used to send data between several running goroutines. Imagine an application where many different processes need to know about or modify data sequentially. Using goroutines and channels, you can model this process safely.

Figure 1.3. Using channels to safely pass data between goroutines

In figure 1.3 you see three goroutines and two unbuffered channels. The first goroutine passes a data value through the channel to a second goroutine that’s already waiting. The exchange of the data between both goroutines is synchronized, and once the hand-off occurs, both goroutines know the exchange took place. After the second goroutine performs its tasks with the data, it then sends the data to a third goroutine that’s waiting. That exchange is also synchronized, and both goroutines can have guarantees the exchange has been made. This safe exchange of data between goroutines requires no other locks or synchronization mechanisms.

It’s important to note that channels don’t provide data access protection between goroutines. If copies of data are exchanged through a channel, then each goroutine has its own copy and can make any changes to that data safely. When pointers to the data are being exchanged, each goroutine still needs to be synchronized if reads and writes will be performed by the different goroutines.

1.1.3. Go’s type system

Go provides a flexible hierarchy-free type system that enables code reuse with minimal refactoring overhead. It’s still object-oriented development, but without the traditional headaches. If you’ve ever spent a week planning your abstract classes and interfaces in a complex Java or C++ program, you’ll appreciate the simplicity of Go’s type system. Go developers simply embed types to reuse functionality in a design pattern called composition. Other languages use composition, but it’s often deeply tied to inheritance, which can make it complicated and difficult to use. In Go, types are composed of smaller types, which is in contrast to traditional inheritance-based models.

In addition Go has a unique interface implementation that allows you to model behavior, rather than model types. You don’t need to declare that you’re implementing an interface in Go; the compiler does the work of determining whether values of your types satisfy the interfaces you’re using. Many interfaces in Go’s standard library are very small, exposing only a few functions. In practice this takes some time to get used to, especially if you’ve been writing in object-oriented languages like Java.

Types are simple

Go has built-in types like int and string as well as user-defined types. A typical user-defined type in Go will have typed fields to store data. If you’ve seen structs in C, Go’s user-defined types will look familiar and operate similarly. But types may also declare methods that operate on that data. Rather than building a long inheritance structure—Client extends User extends Entity—Go developers build small types—Customer and Admin—and embed them into larger ones. Figure 1.4 demonstrates the difference between inheritance and composition.

Figure 1.4. Inheritance versus composition

Go interfaces model small behaviors

Interfaces allow you to express the behavior of a type. If a value of a type implements an interface, it means the value has a specific set of behaviors. You don’t even need to declare that you’re implementing an interface; you just need to write the implementation. Other languages call this duck typing—if it quacks like a duck, then it can be a duck—and Go does it well. In Go, if your type implements the methods of an interface, a value of your type can be stored in a value of that interface type. No special declarations are required.

In a strictly object-oriented language like Java, interfaces are all-encompassing. You’re often required to think through a large inheritance chain before you’re able to even start writing code. Here’s an example of a Java interface:

interface User {
   public void login();
   public void logout();
}

Implementing this interface in Java requires you to create a class that fulfills all of the promises made in the User interface and explicitly declare that you implement the interface. In contrast, a Go interface typically represents just a single action. One of the most common interfaces you’ll use in Go is io.Reader. The io.Reader interface provides a simple way to declare that your type has data to be read in a way that other functions in the standard library understand. Here’s the definition:

type Reader interface {
    Read(p []byte) (n int, err error)
}

To write a type that implements the io.Reader interface, you only need to implement a Read method that accepts a slice of bytes and returns an integer and possible error.

This is a radical departure from the interface systems used in other object-oriented programming languages. Go’s interfaces are smaller and more aligned with single actions. In practice, this allows significant advantages in code reuse and composability. You can implement an io.Reader on nearly any type that has data available, and then pass it to any Go function that knows how to read from io.Reader.

The entire networking library in Go is built using the io.Reader interface, because it allows it to separate the network implementation required for each different network operation from the functionality of your application. It makes interfaces fun, elegant, and flexible. That same io.Reader enables simple operations with files, buffers, sockets, and any other data source. Using a single interface allows you to operate on data efficiently, regardless of the source.

1.1.4. Memory management

Improper memory management causes applications to crash and leak memory, and even crash the operating system. Go has a modern garbage collector that does the hard work for you. In other systems languages, like C or C++, you need to allocate a piece of memory before you can use it, and then free it when you’re done. If you fail to do either of these correctly, you’ll have program crashes or memory leaks. It isn’t always easy to track a piece of memory when it’s no longer needed; threads and heavy concurrency make it even harder. When you write code with garbage collection in mind, Go’s garbage collection adds little overhead to program execution time, but reduces development effort significantly. Go takes the tedium out of programming and leaves the bean counting to the accountants.

1.2. Hello, Go

It’s much easier to get the feel of a programming language by seeing it in action. Let’s look at the traditional Hello World! application written in Go:

This sample program prints a familiar phrase on your screen when you run it. But how should you run it? Without installing Go on your computer, you can use almost all that Go provides right from your web browser.

1.2.1. Introducing the Go Playground

The Go Playground allows you to edit and run Go code from your web browser. Fire up a web browser and navigate to http://play.golang.org. The code in the browser window is editable right on the screen (see figure 1.5). Click Run and see what happens!

Figure 1.5. The Go Playground

You can even change the code to make the greeting text output in a different language. Go ahead and change the greeting inside the fmt.Println() function and hit Run again.

Sharing Go code

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 Share to get a sharable URL that anyone else can open. Try this one: http://play.golang.org/p/EWIXicJdmz.

The Go Playground is the perfect way to demonstrate an idea to a coworker or friend who’s trying to learn something, or to solicit help. On the Go IRC channels, Slack group, mailing lists, and countless emails sent among Go developers, you’ll see Go Playground programs being created, modified, and shared.

1.3. Summary

  • Go is modern, fast, and comes with a powerful standard library.
  • Go has concurrency built-in.
  • Go uses interfaces as the building blocks of code reuse.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset