Introducing Go: build reliable, scalable programs 9781491941959, 1491941952

2,187 436 3MB

English Pages x, 111 pagina's : illustraties ; 24 cm [124] Year 2016

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Introducing Go: build reliable, scalable programs
 9781491941959, 1491941952

Citation preview

Introducing

Go BUILD RELIABLE, SCALABLE PROGRAMS

Caleb Doxsey

Introducing Go Build Reliable, Scalable Programs

Caleb Doxsey

Boston

Introducing Go by Caleb Doxsey Copyright © 2016 Caleb Doxsey. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://safaribooksonline.com). For more information, contact our corporate/ institutional sales department: 800-998-9938 or [email protected].

Editors: Brian MacDonald and Meghan Blanchette Production Editor: Shiny Kalapurakkel Copyeditor: Jasmine Kwityn Proofreader: James Fraleigh

Indexer: WordCo Indexing Services, Inc. Interior Designer: David Futato Cover Designer: Karen Montgomery Illustrator: Rebecca Demarest

First Edition

January 2016:

Revision History for the First Edition 2016-01-06:

First Release

See http://oreilly.com/catalog/errata.csp?isbn=9781491941959 for release details. While the publisher and the author have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the author disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights.

978-1-491-94195-9 [LSI]

Table of Contents

Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii 1. Getting Started. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Machine Setup Text Editors The Terminal Environment Go Your First Program How to Read a Go Program Exercises

1

1 2 2 2 3 4 5 8

2. Types. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Numbers 10 Integers 10 Floating-Point Numbers 10 Example 11 Strings 12 Booleans 13 Exercises 16 3. Variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . How to Name a Variable Scope Constants Defining Multiple Variables An Example Program Exercises

17

20 20 21 22 22 23 iii

4. Control Structures. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The for Statement The if Statement The switch Statement Exercises

25

26 27 29 31

5. Arrays, Slices, and Maps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arrays Slices append copy Maps Exercises

33

33 36 37 37 38 42

6. Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Your Second Function Variadic Functions Closure Recursion defer, panic, and recover panic and recover Pointers The * and & operators new Exercises

43 47 47 48 49 50 51 51 52 53

43

7. Structs and Interfaces. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Structs Initialization Fields Methods Embedded Types Interfaces Exercises

56 56 57 57 58 59 62

8. Packages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The Core Packages Strings Input/Output Files and Folders Errors Containers and Sort

63 64 67 68 70 70

iv

|

Table of Contents

55

63

Hashes and Cryptography Servers TCP HTTP RPC Parsing Command-Line Arguments Creating Packages Documentation Exercises

73 75 75 77 78 79 79 81 82

9. Testing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Exercises 86 10. Concurrency. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Goroutines Channels Channel Direction Select Buffered Channels An Example Exercises

87

87 89 91 91 92 93 96

11. Next Steps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Study the Masters Make Something Team Up

97

97 98 98

A. Answers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

Table of Contents

|

v

Introduction

Although originally designed by Google for the kinds of problems Google works on —large, distributed network applications—Go is now a general-purpose program‐ ming language useful in a wide variety of software domains. Many companies have started using Go because of its simplicity, ease of use, performance, low barrier of entry, and powerful tooling. This book was written to help new programmers learn Go. Although there is an abundance of technical resources available for Go, most are geared toward experi‐ enced engineers. My goal here is to provide a more gentle introduction to the language.

Who Should Read This Book This book is written for relatively inexperienced programmers who know nothing about Go. Although not exhaustive, it does cover all of the basics, and should leave you well positioned to tackle the more advanced material available on the language. The book also covers rudimentary programming skills via the exercises at the end of each chapter.

Navigating This Book This book is organized as follows: • Chapters 1 through 4 introduce the Go toolset and the basics of the language • Chapters 5 through 7 describe more complex types and functions • Chapters 8 and 9 describe packages and testing • Chapter 10 introduces concurrency For best results, the book should be read in order, as each chapter builds on the con‐ cepts covered in the preceding chapters. Each chapter ends with a set of exercises, and vii

it’s important to actually complete them—it’s by solving problems like these that you learn to program. In particular, typing out the examples (and not just reading them) can help significantly.

Online Resources You’ll want to check out the following resources: • The Official Go Website • The Go Tour

Conventions Used in This Book The following typographical conventions are used in this book: Italic Indicates new terms, URLs, email addresses, filenames, and file extensions. Constant width

Used for program listings, as well as within paragraphs to refer to program ele‐ ments such as variable or function names, databases, data types, environment variables, statements, and keywords. Constant width bold

Shows commands or other text that should be typed literally by the user. Constant width italic

Shows text that should be replaced with user-supplied values or by values deter‐ mined by context.

Using Code Examples This book is here to help you get your job done. In general, if example code is offered with this book, you may use it in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing a CD-ROM of examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a signifi‐ cant amount of example code from this book into your product’s documentation does require permission.

viii

|

Introduction

We appreciate, but do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Introducing Go by Caleb Doxsey (O’Reilly). Copyright 2016 Caleb Doxsey, 978-1-4919-4195-9.” If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at [email protected].

Safari® Books Online Safari Books Online is an on-demand digital library that deliv‐ ers expert content in both book and video form from the world’s leading authors in technology and business. Technology professionals, software developers, web designers, and business and crea‐ tive professionals use Safari Books Online as their primary resource for research, problem solving, learning, and certification training. Safari Books Online offers a range of plans and pricing for enterprise, government, education, and individuals. Members have access to thousands of books, training videos, and prepublication manuscripts in one fully searchable database from publishers like O’Reilly Media, Prentice Hall Professional, Addison-Wesley Professional, Microsoft Press, Sams, Que, Peachpit Press, Focal Press, Cisco Press, John Wiley & Sons, Syngress, Morgan Kauf‐ mann, IBM Redbooks, Packt, Adobe Press, FT Press, Apress, Manning, New Riders, McGraw-Hill, Jones & Bartlett, Course Technology, and hundreds more. For more information about Safari Books Online, please visit us online.

How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the United States or Canada) 707-829-0515 (international or local) 707-829-0104 (fax) We have a web page for this book, where we list errata, examples, and any additional information. You can access this page at bit.ly/introducing-go. To comment or ask technical questions about this book, send email to bookques‐ [email protected]. Introduction

|

ix

For more information about our books, courses, conferences, and news, see our web‐ site at http://www.oreilly.com. Find us on Facebook: http://facebook.com/oreilly Follow us on Twitter: http://twitter.com/oreillymedia Watch us on YouTube: http://www.youtube.com/oreillymedia

x

| Introduction

CHAPTER 1

Getting Started

Go is a general-purpose programming language with advanced features and a clean syntax. Because of its wide availability on a variety of platforms, its robust welldocumented common library, and its focus on good software engineering principles, Go is a great programming language to learn. This book assumes no prior knowledge of Go, and is intended to serve as an easy introduction to the language. All of the language’s core features will be covered in short, concise chapters that should prepare you to write real Go programs and tackle some of the more advanced resources available on the language (online documenta‐ tion, books, talks, etc.). Although this book is suitable for inexperienced programmers, if you have never pro‐ grammed before you will probably find the material too difficult to follow. You may benefit from consulting a more general programming resource before diving into the material here, but in all honesty, most students need the kind of hands-on, personal support that you might find in a classroom setting or one on one with an experienced developer.

Machine Setup This book contains many code samples and exercises. For best results, you should try to run these examples on your own computer as you work your way through each chapter. But before you can write your first Go program, there are a few things you will need to set up.

1

Text Editors Go is a very readable, succinct language and so any text editor will work for editing files. There are plug-ins that add a few helpful features (like autocomplete and format-on-save) for many popular editors, but those plug-ins are not necessary to learn the language. If you’re not sure what to use, I recommend using GitHub’s Atom —it’s free, cross-platform, and easy to install from the Atom website.

The Terminal Go is a compiled language, and like many languages, it makes heavy use of the com‐ mand line. If you’re coming from a language that does most things through an IDE (such as Java or C#), this may be a bit intimidating, but thankfully, the Go tools are fairly easy to use. As a reminder, here’s how you can get to a terminal: Windows On Windows, the terminal (also known as the command prompt) can be brought up by pressing the Windows key + R (hold down the Windows key, then press R), typing cmd.exe, and hitting Enter. OS X On OS X, the terminal can be reached by navigating to Finder → Applications → Utilities → Terminal.

Environment Environment variables are a mechanism provided by your operating system for alter‐ ing the behavior of a program without having to change it. An environment is a col‐ lection of these variables, each of which has a name and a corresponding value. For example, there is a TEMP environment variable that stores the location of a directory on your computer where temporary files are stored. The Go toolset uses an environment variable called GOPATH to find Go source code. Although you’re welcome to set the GOPATH to anything you want, to make things eas‐ ier we will set it to be the same as your home directory: Windows On Windows, user information is typically stored in C:\Users\, where would be replaced with your username (e.g., C:\Users \alice). Windows comes with a predefined environment variable called USERPRO FILE, which you can use to set your GOPATH. Open a new terminal window and enter the following: setx GOPATH %USERPROFILE%

2

|

Chapter 1: Getting Started

If you’re using a version of Windows prior to Vista, this command may not work, so you can also set environment variables by navigating to Control Panel → Sys‐ tem → Advanced → Environment Variables. OS X On OS X, user information is typically stored in /Users/, where would be replaced with your username (e.g., /Users/alice). On OS X, we will set GOPATH using a special initialization file for the terminal called .bash_profile. Open a terminal and enter the following: echo 'export GOPATH=$HOME\n' >> ~/.bash_profile

Close the terminal, reopen it, and enter the following: env

Among many other environment variables, you should see an entry for GOPATH.

Go Go is both the name of the programming language and the name for the toolset used to build and interact with Go programs. Before you begin working with Go, you’ll need to install the Go toolset. Download and run the installer for your platform from golang.org/dl. To confirm everything is working, open a terminal and type the following: go version

You should see the following (your version number and operating system may be slightly different): go version go1.5 windows/amd64

If you get an error about the command not being recognized, try restarting your computer. The Go toolset is made up of several different commands and subcommands. You can pull up a list of those commands by typing: go help

With Go installed and working, you now have everything you need to write your first Go program.

Machine Setup

|

3

Your First Program Traditionally, the first program you write in any programming language is called a “Hello, World” program—a program that simply outputs Hello, World to your ter‐ minal. Let’s write one using Go. First, create a new folder where you can store our “Hello, World” program. Create a folder named ~/src/golang-book/chapter1. From the terminal, you can do this by entering the following commands: On Windows md src\golang-book\chapter1

On OS X mkdir -p src/golang-book/chapter1

Open your text editor, create a new file, and enter the following: package main import "fmt" // this is a comment func main() { fmt.Println("Hello, World") }

Make sure your file is identical to what is shown here and save it as main.go in the folder we just created. Open up a new terminal and type in the following: cd src/golang-book/chapter1 go run main.go

You should see Hello, World displayed in your terminal. The go run command takes the subsequent files (separated by spaces), compiles them into an executable saved in a temporary directory, and then runs the program. If you didn’t see Hello, World displayed, you may have made a mistake when typing in the program. The Go compiler will give you hints about where the mistake lies. Like most compilers, the Go compiler is extremely pedantic and has no tolerance for mistakes.

4

|

Chapter 1: Getting Started

How to Read a Go Program Let’s look at this program in more detail: package main import "fmt" // this is a comment func main() { fmt.Println("Hello, World") }

Go programs are read top to bottom, left to right (like a book). The first line says this: package main

This is known as a package declaration, and every Go program must start with it. Packages are Go’s way of organizing and reusing code. There are two types of Go pro‐ grams: executables and libraries. Executable applications are the kinds of programs that we can run directly from the terminal (on Windows, they end with .exe). Libra‐ ries are collections of code that we package together so that we can use them in other programs. We will explore libraries in more detail later; for now, just make sure to include this line in any program you write. The next line is blank. Computers represent newlines with a special character (or sev‐ eral characters). Newlines, spaces, and tabs are known as whitespace (because you can’t see them). Go mostly doesn’t care about whitespace—we use it to make pro‐ grams easier to read (you could remove this line and the program would behave in exactly the same way). On the following line, we see this: import "fmt"

The import keyword is how we include code from other packages to use with our program. The fmt package (shorthand for format) implements formatting for input and output. Given what we just learned about packages, what do you think the fmt package’s files would contain at the top of them?1 Notice that fmt is surrounded by double quotes. The use of double quotes like this is known as a string literal, which is a type of expression. In Go, strings represent a sequence of characters (letters, numbers, symbols, etc.) of a definite length. Strings are described in more detail in the next chapter, but for now the important thing to

1 Files in the fmt package start with package fmt.

How to Read a Go Program

|

5

keep in mind is that an opening " character must eventually be followed by a closing " character and anything in between the two is included in the string (the " character itself is not part of the string). The line that starts with // is known as a comment. Comments are ignored by the Go compiler and are there for your own sake (or whoever picks up the source code for your program). Go supports two different styles of comments: // comments in which all the text between the // and the end of the line is part of the comment, and /* */ comments where everything between the asterisks is part of the comment (and may include multiple lines). After this, you see a function declaration: func main() { fmt.Println("Hello, World") }

Functions are the building blocks of a Go program. They have inputs, outputs, and a series of steps called statements that are executed in order. All functions start with the keyword func followed by the name of the function (main, in this case), a list of zero or more parameters surrounded by parentheses, an optional return type, and a body which is surrounded by curly braces. This function has no parameters, doesn’t return anything, and has only one statement. The name main is special because it’s the func‐ tion that gets called when you execute the program. The final piece of our program is this line: fmt.Println("Hello, World")

This statement is made of three components. First, we access another function inside of the fmt package called Println (that’s the fmt.Println piece); Println means “print line.” Then we create a new string that contains Hello, World and invoke (also known as call or execute) that function with the string as the first and only argument. At this point, you’ve already seen a lot of new terminology. Sometimes it’s helpful to deliberately read your program out loud. One reading of the program we just wrote might go like this: Create a new executable program that references the fmt library and contains one function called main. That function takes no arguments and doesn’t return anything. It accesses the Println function contained inside of the fmt package and invokes it using one argument—the string Hello, World.

The Println function does the real work in this program. You can find out more about it by typing the following in your terminal: godoc fmt Println

Among other things, you should see the output shown in Figure 1-1.

6

|

Chapter 1: Getting Started

Figure 1-1. Output of godoc fmt Println Println formats using the default formats for its operands and writes to standard out‐ put. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered. Go is a very well-documented programming language, but this documentation can be difficult to understand unless you are already familiar with programming languages. Nevertheless, the godoc command is extremely useful and a good place to start when‐ ever you have a question. Back to the function at hand, this documentation is telling you that the Println func‐ tion will send whatever you give to it to standard output (i.e., the output of the termi‐ nal you are working in). This function is what causes Hello, World to be displayed. In the next chapter, we will explore how Go stores and represents things like Hello,

World by learning about types.

How to Read a Go Program

|

7

Exercises 1. What is whitespace? 2. What is a comment? What are the two ways of writing a comment? 3. Our program began with package main. What would the files in the fmt package begin with? 4. We used the Println function defined in the fmt package. If you wanted to use the Exit function from the os package, what would you need to do? 5. Modify the program we wrote so that instead of printing Hello, World it prints Hello, my name is followed by your name.

8

|

Chapter 1: Getting Started

CHAPTER 2

Types

In the previous chapter, we used the data type string to store Hello, World. Data types categorize a set of related values, describe the operations that can be done on them, and define the way they are stored. Because types can be a difficult concept to grasp, we will look at them from a couple different perspectives before we see how they are implemented in Go. Philosophers sometimes make a distinction between types and tokens. For example, suppose you have a dog named Max. Max is the token (a particular instance or mem‐ ber) and dog is the type (the general concept). Dog or dogness describes a set of prop‐ erties that all dogs have in common. Although oversimplistic, we might reason like this: all dogs have four legs, Max is a dog, therefore Max has four legs. Types in pro‐ gramming languages work in a similar way: all strings have a length, x is a string, therefore x has a length. In mathematics, we often talk about sets. For example, ℝ (the set of all real numbers) or ℕ (the set of all natural numbers). Each member of these sets shares properties with all the other members of the set. For instance, all natural numbers are associa‐ tive: “for all natural numbers a, b, and c, a + (b + c) = (a + b) + c and a × (b × c) = (a × b) × c.” In this way, sets are similar to types in programming languages, because all the values of a particular type share certain properties. Go is a statically typed programming language. This means that variables always have a specific type and that type cannot change. Static typing may seem cumbersome at first. You’ll spend a large amount of your time just trying to fix your program so that it finally compiles. But types help us reason about what our program is doing and catch a wide variety of common mistakes. Go comes with several built-in data types, which we will now look at in more detail.

9

Numbers Go has several different types to represent numbers. Generally, we split numbers into two different kinds: integers and floating-point numbers.

Integers Integers—like their mathematical counterpart—are numbers without a decimal com‐ ponent. (…, −3, −2, −1, 0, 1, …) Unlike the base-10 decimal system we use to repre‐ sent numbers, computers use a base-2 binary system. Our system is made up of 10 different digits. Once we’ve exhausted our available dig‐ its, we represent larger numbers by using 2 (then 3, 4, 5, etc.) 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, 1,024 bytes = 1 kilobyte, 1,024 kilobytes = 1 megabyte, etc.) and therefore Go’s byte data type is often used in the definition of other types. There are also three machine dependent integer types: uint, int, and uintptr. They are machine dependent because their size depends on the type of architecture you are using. Generally, if you are working with integers, you should just use the int type.

Floating-Point Numbers Floating-point numbers are numbers that contain a decimal component (i.e., real numbers). For example, 1.234, 123.4, 0.00001234, and 12340000 are all floating-point numbers. Their actual representation on a computer is fairly complicated and it’s not really necessary to know the particulars in order to use them. So, for now, you only need to keep the following in mind:

10

| Chapter 2: Types

• Floating-point numbers are inexact. Occasionally it is not possible to represent a number. For example, computing 1.01 – 0.99 using floating-point arithmetic results in 0.020000000000000018—a number extremely close to what we would expect, but not exactly the same. • Like integers, floating-point numbers have a certain size (32 bit or 64 bit). Using a larger-sized floating-point number increases its precision (i.e., how many digits it can represent). • In addition to numbers, there are several other values that can be represented: “not a number” (NaN, for things like 0/0) and positive and negative infinity (+∞ and −∞). Go has two floating-point types: float32 and float64 (also often referred to as single precision and double precision, respectively). It also has two additional types for rep‐ resenting complex numbers (numbers with imaginary parts): complex64 and complex128. Generally, we should stick with float64 when working with floatingpoint numbers.

Example Let’s write an example program using numbers. First, create a folder called chapter2 and make a main.go file containing the following: package main import "fmt" func main() { fmt.Println("1 + 1 =", 1 + 1) }

If you run the program, you should see this: $ go run main.go 1 + 1 = 2

Notice that this program is very similar to the program we wrote in Chapter 1. It con‐ tains the same package line, the same import line, and the same function declaration, and it uses the same Println function. However, instead of printing the string Hello, World, we print the string 1 + 1 = followed by the result of the expression 1 + 1. This expression is made up of three parts: the numeric literal 1 (which is of type int), the + operator (which represents addition), and another numeric literal 1. Let’s try the same thing using floating-point numbers: fmt.Println("1 + 1 =", 1.0 + 1.0)

Notice that we use the .0 to tell Go that this is a floating-point number instead of an integer. Running this program will give you the same result as before.

Numbers

|

11

Go supports the following standard arithmetic operators: +

Addition -

Subtraction *

Multiplication /

Division %

Remainder This small set of basic operators, along with some of the helper functions available in the math package, is sufficient to create surprisingly sophisticated programs. Another type we use in almost every Go program is a string.

Strings As we saw in Chapter 1, a string is a sequence of characters with a definite length used to represent text. Go strings are made up of individual bytes, usually one for each character (characters from some languages, such as Chinese, are represented by more than one byte). String literals can be created using double quotes "Hello, World" or backticks `Hello, World`. The difference between these is that double-quoted strings cannot contain newlines and they allow special escape sequences. For example, \n gets replaced with a newline and \t gets replaced with a tab character. The following are some common operations on strings: len("Hello, World")

Finds the length of a string "Hello, World"[1]

Accesses a particular character in the string (in this case, the second character) "Hello, " + World"

Concatenates two strings together Let’s modify the program we created earlier to test these out: package main

12

|

Chapter 2: Types

import "fmt" func main() { fmt.Println(len("Hello, World")) fmt.Println("Hello, World"[1]) fmt.Println("Hello, " + "World") }

A few things to notice: • A space is also considered a character, so the string’s length is 12, not 11, and the third line has "Hello, " instead of "Hello". • Strings are indexed starting at 0, not 1. [1] gives you the second element, not the first. Also notice that you see 101 instead of e when you run this program. This is because the character is represented by a byte (remember a byte is an integer). One way to think about indexing would be to show it like this instead: "Hello, World"1. You’d read that as “The string Hello, World sub 1,” “The string Hello,

World at 1,” or “The second character of the string Hello, World.”

• Concatenation uses the same symbol as addition. The Go compiler figures out what to do based on the types of the arguments. Because both sides of the + are strings, the compiler assumes you mean concatenation and not addition (addi‐ tion is meaningless for strings). Strings and numbers are both extremely useful. Most programs either represent mathematical formulas or involve the manipulation and transformation of data, which is usually represented as a string (or a byte slice, as we will see later). But there’s a third basic type that is needed by almost any program, and that’s a boolean.

Booleans A boolean value (named after George Boole) is a special 1-bit integer type used to represent true and false (or on and off). Three logical operators are used with boolean values: && and || or !

not

Here is an example program showing how they can be used: func main() { fmt.Println(true fmt.Println(true fmt.Println(true fmt.Println(true

&& && || ||

true) false) true) false)

Booleans

|

13

fmt.Println(!true) }

14

|

Chapter 2: Types

Running this program should give you: $ go run main.go true false true true false

We usually use truth tables to define how these operators work: Expression

Value

true && true

true

true && false

false

false && true

false

false && false false

Expression

Value

true || true

true

true || false

true

false || true

true

false || false false

Expression Value !true

false

!false

true

We often use booleans to make decisions in our program and represent binary dis‐ tinctions (Has a feature been enabled? Is this user an administrator? Have I found the value I was looking for?). As you see more examples and write more code, you will gain a better understanding of how and when to use them. These are the simplest types included with Go, and they form the foundation from which all later types are built. In the next chapter, we will see how these types can be used to store data in your program with variables.

Booleans

|

15

Exercises 1. How are integers stored on a computer? 2. We know that (in base 10) the largest one-digit number is 9 and the largest twodigit number is 99. Given that in binary the largest two-digit number is 11 (3), the largest three-digit number is 111 (7) and the largest four-digit number is 1111 (15), what’s the largest eight-digit number? (Hint: 101−1 = 9 and 102−1 = 99) 3. Although overpowered for the task, you can use Go as a calculator. Write a pro‐ gram that computes 32,132 × 42,452 and prints it to the terminal (use the * oper‐ ator for multiplication). 4. What is a string? How do you find its length? 5. What’s the value of the expression (true && false) || (false && true) || ! (false && false)?

16

|

Chapter 2: Types

CHAPTER 3

Variables

Up until now, we have only seen programs that use literal values (numbers, strings, etc.), but such programs aren’t particularly useful. To make truly useful programs, we need to discuss two new concepts: variables and control flow statements. This chapter will explore variables in more detail (we’ll discuss control flow statements in Chap‐ ter 4). A variable is a storage location, with a specific type and an associated name. Let’s change the program we wrote in Chapter 1 so that it uses a variable: package main import "fmt" func main() { var x string = "Hello, World" fmt.Println(x) }

Notice that the string literal from the original program still appears in this program, but rather than send it directly to the Println function, we assign it to a variable instead. Variables in Go are created by first using the var keyword, then specifying the variable name (x) and the type (string), and finally, assigning a value to the vari‐ able (Hello, World). Assigning a value is optional, so we could use two statements, like this: package main import "fmt" func main() { var x string x = "Hello, World"

17

fmt.Println(x) }

Variables in Go are similar to variables in algebra, but there are some subtle differ‐ ences. First, when we see the = symbol, we have a tendency to read that as “x equals the string Hello, World.” There’s nothing wrong with reading our program that way, but it’s better to read it as “x takes the string Hello, World” or “x is assigned the string Hello, World.” This distinction is important because (as their name would suggest) variables can change their value throughout the lifetime of a program. Try running the following: package main import "fmt" func main() { var x string x = "first" fmt.Println(x) x = "second" fmt.Println(x) }

In fact, you can even do this: var x string x = "first " fmt.Println(x) x = x + "second" fmt.Println(x)

This program would be nonsense if you read it like an algebraic theorem. But it makes sense if you are careful to read the program as a list of commands. When we see x = x + "second", we should read it as “assign the concatenation of the value of the variable x and the string literal second to the variable x.” The right side of the = is done first and the result is then assigned to the left side of the =. The x = x + y form is so common in programming that Go has a special assignment statement: +=. We could have written x = x + "second" as x += "second" and it would have done the same thing (other operators can be used the same way). Another difference between Go and algebra is that we use a different symbol for equality: == (two equals signs next to each other). == is an operator like + and it returns a boolean. For example: var x string = "hello" var y string = "world" fmt.Println(x == y)

18

|

Chapter 3: Variables

This program should print false because hello is not the same as world. On the other hand: var x string = "hello" var y string = "hello" fmt.Println(x == y)

This will print true because the two strings are the same. Because creating a new variable with a starting value is so common, Go also supports a shorter statement: x := "Hello, World"

Notice the : before the = and that no type was specified. The type is not necessary because the Go compiler is able to infer the type based on the literal value you assign the variable (because you are assigning a string literal, x is given the type string). The compiler can also do inference with the var statement: var x = "Hello, World"

The same thing works for other types: x := 5 fmt.Println(x)

Generally, you should use this shorter form whenever possible.

Idiomatic Go Languages often have a set of informal, conventional rules. For example, in English, there are rules that govern the order of adjectives in a sentence. If, instead of “my small black cat,” you were to say, “my black small cat,” it would strike native speakers as very strange. Programming languages often exhibit the same phenomenon. Although there are often many ways to do something, not all of those ways are seen as a natural expres‐ sion of the language. You may write an expression (var x int = 5) which is semanti‐ cally valid in the language (it compiles), but which may look strange to experienced Go programmers. Go programmers (as well as programmers in other communities) often refer to this as the idiomatic usage of the language. Learning idiomatic Go is a worthwhile pursuit, but at this stage, you should probably focus on simply writing correct programs, as that’s challenging enough. Don’t let the best be the enemy of the good.

Variables

|

19

How to Name a Variable Naming a variable properly is an important part of software development. Names must start with a letter and may contain letters, numbers, or the underscore symbol (_). The Go compiler doesn’t care what you name a variable, but you should choose names that clearly describe the variable’s purpose. Suppose we had the following: x := "Max" fmt.Println("My dog's name is", x)

In this case, x is not a very good name for a variable. A better name would be: name := "Max" fmt.Println("My dog's name is", name)

or even: dogsName := "Max" fmt.Println("My dog's name is", dogsName)

In the preceding snippet, we use camelCase, which is a style for writing compound words in which the first letter of each new word or phrase is capitalized. It is also sometimes referred to as mixedCase, BumpyCaps, camelBack, or HumpBack (in some other languages, the first letter is also capitalized).

Scope Let’s take another look at the program we saw at the beginning of the chapter: package main import "fmt" func main() { var x string = "Hello, World" fmt.Println(x) }

Another way of writing this program would be like this: package main import "fmt" var x string = "Hello, World" func main() { fmt.Println(x) }

Notice that we moved the variable outside of the main function. This means that other functions can access this variable: 20

|

Chapter 3: Variables

var x string = "Hello, World" func main() { fmt.Println(x) } func f() { fmt.Println(x) }

The f function now has access to the x variable. Now suppose that we wrote this instead: func main() { var x string = "Hello, World" fmt.Println(x) } func f() { fmt.Println(x) }

If you run this program, you should see an error: .\main.go:11: undefined: x

The compiler is telling you that the x variable inside of the f function doesn’t exist. It only exists inside of the main function. The range of places where you are allowed to use x is called the scope of the variable. According to the language specification, “Go is lexically scoped using blocks.” Basically, this means that the variable exists within the nearest curly braces ({ }), or block, including any nested curly braces (blocks), but not outside of them. Scope can be a little confusing at first; as we see more Go examples, it should become more clear.

Constants Go also has support for constants. Constants are essentially variables whose values cannot be changed later. They are created in the same way you create variables, but instead of using the var keyword we use the const keyword: package main import "fmt" func main() { const x string = "Hello, World" fmt.Println(x) }

Constants

|

21

This: const x string = "Hello, World" x = "Some other string"

Results in a compile-time error: .\main.go:7: cannot assign to x

Constants are a good way to reuse common values in a program without writing them out each time. For example, Pi in the math package is defined as a constant.

Defining Multiple Variables Go also has another shorthand when you need to define multiple variables: var ( a = 5 b = 10 c = 15 )

Use the keyword var (or const) followed by parentheses with each variable on its own line.

An Example Program Here’s an example program that takes in a number entered by the user and doubles it: package main import "fmt" func main() { fmt.Print("Enter a number: ") var input float64 fmt.Scanf("%f", &input) output := input * 2 fmt.Println(output) }

We use another function from the fmt package to read the user input (Scanf). &input will be explained in a later chapter; for now, all you need to know is that Scanf fills input with the number we enter.

22

|

Chapter 3: Variables

Exercises 1. 2. 3. 4. 5.

What are two ways to create a new variable? What is the value of x after running x := 5; x += 1? What is scope? How do you determine the scope of a variable in Go? What is the difference between var and const? Using the example program as a starting point, write a program that converts from Fahrenheit into Celsius (C = (F − 32) * 5/9). 6. Write another program that converts from feet into meters (1 ft = 0.3048 m).

Exercises

|

23

CHAPTER 4

Control Structures

Now that you know how to use variables, it’s time to start writing some useful pro‐ grams. First, let’s write a program that counts to 10, starting from 1, with each num‐ ber on its own line. Using what you’ve learned so far, you could write this: package main import "fmt" func main() { fmt.Println(1) fmt.Println(2) fmt.Println(3) fmt.Println(4) fmt.Println(5) fmt.Println(6) fmt.Println(7) fmt.Println(8) fmt.Println(9) fmt.Println(10) }

Or this: package main import "fmt" func main() { fmt.Println(`1 2 3 4 5 6 7

25

8 9 10`) }

But both of these programs are pretty tedious to write. What we need is a way of doing something multiple times.

The for Statement The for statement allows us to repeat a list of statements (a block) multiple times. Rewriting our previous program using a for statement looks like this: package main import "fmt" func main() { i := 1 for i "aaaaa" }

To replace a smaller string in a bigger string with some other string, use the Replace function. In Go, Replace also takes a number indicating how many times to do the replacement (pass -1 to do it as many times as possible): package main import ( "fmt" "strings" ) func main() { // func Replace(s, old, new string, n int) string fmt.Println(strings.Replace("aaaa", "a", "b", 2)) // => "bbaa" }

To split a string into a list of strings by a separating string (e.g., a comma), use the Split function (Split is the reverse of Join): package main import ( "fmt" "strings" ) func main() { // func Split(s, sep string) []string fmt.Println(strings.Split("a-b-c-d-e", "-"))) // => []string{"a","b","c","d","e"} }

To convert a string to all lowercase letters, use the ToLower function: package main import ( "fmt" "strings"

66

|

Chapter 8: Packages

) func main() { // func ToLower(s string) string // fmt.Println(strings.ToLower("TEST")) // => "test" }

To convert a string to all uppercase letters, use the ToUpper function: package main import ( "fmt" "strings" ) func main() { // func ToUpper(s string) string // fmt.Println(strings.ToUpper("test")) // => "TEST" }

Sometimes we need to work with strings as binary data. To convert a string to a slice of bytes (and vice versa), do this: arr := []byte("test") str := string([]byte{'t','e','s','t'})

Input/Output Before we look at files, we need to understand Go’s io package. The io package con‐ sists of a few functions, but mostly interfaces used in other packages. The two main interfaces are Reader and Writer. Readers support reading via the Read method. Writers support writing via the Write method. Many functions in Go take Readers or Writers as arguments. For example, the io package has a Copy function that copies data from a Reader to a Writer: func Copy(dst Writer, src Reader) (written int64, err error)

To read or write to a []byte or a string, you can use the Buffer struct found in the bytes package: var buf bytes.Buffer buf.Write([]byte("test"))

A Buffer doesn’t have to be initialized, and it supports both the Reader and Writer interfaces. You can convert it into a []byte by calling buf.Bytes(). If you only need to read from a string, you can also use the strings.NewReader function, which is more efficient than using a buffer.

The Core Packages

|

67

Files and Folders To open a file in Go, use the Open function from the os package. Here is an example of how to read the contents of a file and display them on the terminal: package main import ( "fmt" "os" ) func main() { file, err := os.Open("test.txt") if err != nil { // handle the error here return } defer file.Close() // get the file size stat, err := file.Stat() if err != nil { return } // read the file bs := make([]byte, stat.Size()) _, err = file.Read(bs) if err != nil { return } str := string(bs) fmt.Println(str) }

We use defer file.Close() right after opening the file to make sure the file is closed as soon as the function completes. Reading files is very common, so there’s a shorter way to do this: package main import ( "fmt" "io/ioutil" ) func main() { bs, err := ioutil.ReadFile("test.txt") if err != nil { return }

68

| Chapter 8: Packages

str := string(bs) fmt.Println(str) }

To create a file, use the os.Create function. It takes the name of the file, creates it in the current working directory, and returns an os.File and possibly an error (if it was unable to create it for some reason). Here’s an example program: package main import ( "os" ) func main() { file, err := os.Create("test.txt") if err != nil { // handle the error here return } defer file.Close() file.WriteString("test") }

To get the contents of a directory, we use the same os.Open function but give it a directory path instead of a file name. Then we call the Readdir method: package main import ( "fmt" "os" ) func main() { dir, err := os.Open(".") if err != nil { return } defer dir.Close() fileInfos, err := dir.Readdir(-1) if err != nil { return } for _, fi := range fileInfos { fmt.Println(fi.Name()) } }

The Core Packages

|

69

Readdir takes a single argument that limits the number of entries returned. By pass‐ ing in -1, we return all of the entries.

Often, we want to recursively walk a folder (read the folder’s contents, all the subfold‐ ers, all the sub-subfolders, etc.). To make this easier, there’s a Walk function provided in the path/filepath package: package main import ( "fmt" "os" "path/filepath" ) func main() { filepath.Walk(".", func(path string, info os.FileInfo, err error) error { fmt.Println(path) return nil }) }

The function you pass to Walk is called for every file and folder in the root folder (in this case, .). It’s passed three arguments: path, which is the path to the file; info, which is the information for the file (the same information you get from using os.Stat); and err, which is any error that was received while walking the directory. The function returns an error and you can return filepath.SkipDir to stop walking immediately.

Errors Go has a built-in type for errors that we have already seen (the error type). We can create our own errors by using the New function in the errors package: package main import "errors" func main() { err := errors.New("error message") }

Containers and Sort In addition to lists and maps, Go has several more collections available underneath the container package. We’ll take a look at the container/list package as an example.

70

|

Chapter 8: Packages

List The container/list package implements a doubly linked list. A linked list is a type of data structure that looks like Figure 8-1.

Figure 8-1. A linked list Each node of the list contains a value (1, 2, or 3, in this case) and a pointer to the next node. Because this is a doubly linked list, each node will also have pointers to the pre‐ vious node. This list could be created by this program: package main import ("fmt" ; "container/list") func main() { var x list.List x.PushBack(1) x.PushBack(2) x.PushBack(3) for e := x.Front(); e != nil; e=e.Next() { fmt.Println(e.Value.(int)) } }

The zero value for a List is an empty list (a *List can also be created using list.New). Values are appended to the list using PushBack. We loop over each item in the list by getting the first element, and following all the links until we reach nil.

Sort The sort package contains functions for sorting arbitrary data. There are several pre‐ defined sorting functions (for slices of ints and floats) Here’s an example for how to sort your own data: package main import ("fmt" ; "sort") type Person struct { Name string Age int } type ByName []Person

The Core Packages

|

71

func (ps ByName) Len() int { return len(ps) } func (ps ByName) Less(i, j int) bool { return ps[i].Name < ps[j].Name } func (ps ByName) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] } func main() { kids := []Person{ {"Jill",9}, {"Jack",10}, } sort.Sort(ByName(kids)) fmt.Println(kids) }

The Sort function in sort takes a sort.Interface and sorts it. The sort.Interface requires three methods: Len, Less, and Swap. Len should return the length of the thing we are sorting. For a slice, simply return len(ps). Less is used to determine whether the item at position i is strictly less than the item at position j. In this case, we simply compare ps[i].Name to ps[j].Name. Swap swaps the items.

To define our own sort, we create a new type (ByName) and make it equivalent to a slice of what we want to sort. We then define the three methods. Sorting our list of people is then as easy as casting the list into our new type. We could also sort by age by doing this: type ByAge []Person func (this ByAge) Len() int { return len(this) } func (this ByAge) Less(i, j int) bool { return this[i].Age < this[j].Age } func (this ByAge) Swap(i, j int) { this[i], this[j] = this[j], this[i] }

72

|

Chapter 8: Packages

Hashes and Cryptography A hash function takes a set of data and reduces it to a smaller fixed size. Hashes are frequently used in programming for everything from looking up data to easily detect‐ ing changes. Hash functions in Go are broken into two categories: cryptographic and non-cryptographic. The non-cryptographic hash functions can be found underneath the hash package and include adler32, crc32, crc64, and fnv. Here’s an example using crc32: package main import ( "fmt" "hash/crc32" ) func main() { // create a hasher h := crc32.NewIEEE() // write our data to it h.Write([]byte("test")) // calculate the crc32 checksum v := h.Sum32() fmt.Println(v) }

The crc32 hash object implements the Writer interface, so we can write bytes to it like any other Writer. Once we’ve written everything we want, we call Sum32() to return a uint32. A common use for crc32 is to compare two files. If the Sum32 value for both files is the same, it’s highly likely (though not 100% certain) that the files are the same. If the values are different, then the files are definitely not the same: package main import ( "fmt" "hash/crc32" "io/ioutil" ) func getHash(filename string) (uint32, error) { // open the file f, err := os.Open(filename) if err != nil { return 0, err } // remember to always close opened files defer f.Close() // create a hasher

The Core Packages

|

73

h := crc32.NewIEEE() // copy the file into the hasher // - copy takes (dst, src) and returns (bytesWritten, error) _, err := io.Copy(h, f) // we don't care about how many bytes were written, but we do want to // handle the error if err != nil { return 0, err } return h.Sum32(), nil } func main() { h1, err := getHash("test1.txt") if err != nil { return } h2, err := getHash("test2.txt") if err != nil { return } fmt.Println(h1, h2, h1 == h2) }

Cryptographic hash functions are similar to their non-cryptographic counterparts, but they have the added property of being hard to reverse. Given the cryptographic hash of a set of data, it’s extremely difficult to determine what made the hash. These hashes are often used in security applications. One common cryptographic hash function is known as SHA-1. Here’s how it is used: package main import ( "fmt" "crypto/sha1" ) func main() { h := sha1.New() h.Write([]byte("test")) bs := h.Sum([]byte{}) fmt.Println(bs) }

This example is very similar to the crc32 one, because both crc32 and sha1 imple‐ ment the hash.Hash interface. The main difference is that whereas crc32 computes a 32-bit hash, sha1 computes a 160-bit hash. There is no native type to represent a 160bit number, so we use a slice of 20 bytes instead.

74

| Chapter 8: Packages

Servers Writing distributed, networked applications in Go is relatively straightforward. We will briefly take a look at three common approaches to communicating between mul‐ tiple computers: TCP servers, HTTP servers, and RPC.

TCP TCP is the primary protocol used for communication over the Internet. Any time you interact with a web page, play a multiplayer computer game, stream a movie, or video chat, there’s a good chance your computer is communicating with a remote server using TCP. In Go, we can create a TCP server using the net package’s Listen function. Listen takes a network type (in our case, tcp) and an address and port to bind, and returns a net.Listener: type Listener interface { // Accept waits for and returns the next connection to the listener. Accept() (c Conn, err error) // Close closes the listener. // Any blocked Accept operations will be unblocked and return errors. Close() error // Addr returns the listener's network address. Addr() Addr }

Once we have a Listener, we call Accept, which waits for a client to connect and returns a net.Conn. A net.Conn implements the io.Reader and io.Writer interfaces, so we can read from it and write to it just like a file. Here’s a complete example: package main import ( "encoding/gob" "fmt" "net" ) func server() { // listen on a port ln, err := net.Listen("tcp", ":9999") if err != nil { fmt.Println(err) return }

Servers

|

75

for { // accept a connection c, err := ln.Accept() if err != nil { fmt.Println(err) continue } // handle the connection go handleServerConnection(c) } } func handleServerConnection(c net.Conn) { // receive the message var msg string err := gob.NewDecoder(c).Decode(&msg) if err != nil { fmt.Println(err) } else { fmt.Println("Received", msg) } c.Close() } func client() { // connect to the server c, err := net.Dial("tcp", "127.0.0.1:9999") if err != nil { fmt.Println(err) return } // send the message msg := "Hello, World" fmt.Println("Sending", msg) err = gob.NewEncoder(c).Encode(msg) if err != nil { fmt.Println(err) } c.Close() } func main() { go server() go client() var input string fmt.Scanln(&input) }

76

| Chapter 8: Packages

This example uses the encoding/gob package, which makes it easy to encode Go val‐ ues so that other Go programs (or the same Go program, in this case) can read them. Additional encodings are available in packages underneath encoding (like encoding/ json) as well as in third-party packages (e.g., we could use labix.org/v2/mgo/bson for bson support).

HTTP HTTP servers are even easier to set up and use: package main import ("net/http" ; "io") func hello(res http.ResponseWriter, req *http.Request) { res.Header().Set( "Content-Type", "text/html", ) io.WriteString( res, `

Hello, World

Hello, World!

`, ) } func main() { http.HandleFunc("/hello", hello) http.ListenAndServe(":9000", nil) }

HandleFunc handles a URL route (/hello) by calling the given function. We can also handle static files by using FileServer: http.Handle( "/assets/", http.StripPrefix( "/assets/", http.FileServer(http.Dir("assets")), ), )

Servers

|

77

RPC The net/rpc (remote procedure call) and net/rpc/jsonrpc packages provide an easy way to expose methods so they can be invoked over a network (rather than just in the program running them): package main import ( "fmt" "net" "net/rpc" ) type Server struct {} func (this *Server) Negate(i int64, reply *int64) error { *reply = -i return nil } func server() { rpc.Register(new(Server)) ln, err := net.Listen("tcp", ":9999") if err != nil { fmt.Println(err) return } for { c, err := ln.Accept() if err != nil { continue } go rpc.ServeConn(c) } } func client() { c, err := rpc.Dial("tcp", "127.0.0.1:9999") if err != nil { fmt.Println(err) return } var result int64 err = c.Call("Server.Negate", int64(999), &result) if err != nil { fmt.Println(err) } else { fmt.Println("Server.Negate(999) =", result) } } func main() { go server() go client()

78

|

Chapter 8: Packages

var input string fmt.Scanln(&input) }

This program is similar to the TCP example, except now we created an object to hold all the methods we want to expose and we call the Negate method from the client. See the documentation in net/rpc for more details.

Parsing Command-Line Arguments When we invoke a command on the terminal, it’s possible to pass that command arguments. We’ve seen this with the go command: go run myfile.go

run and myfile.go are arguments. We can also pass flags to a command: go run -v myfile.go

The flag package allows us to parse arguments and flags sent to our program. Here’s an example program that generates a number between 0 and 6. We can change the max value by sending a flag (-max=100) to the program: package main import ("fmt";"flag";"math/rand") func main() { // Define flags maxp := flag.Int("max", 6, "the max value") // Parse flag.Parse() // Generate a number between 0 and max fmt.Println(rand.Intn(*maxp)) }

Any additional non-flag arguments can be retrieved with flag.Args(), which returns a []string.

Creating Packages Packages only really make sense in the context of a separate program that uses them. Without this separate program, we have no way of using the package we create. Let’s create an application that will use a package we will write. Create a folder in ~/src/ golang-book called chapter8. Inside that folder, create a file called main.go using the following code:

Creating Packages

|

79

package main import "fmt" import "golang-book/chapter8/math" func main() { xs := []float64{1,2,3,4} avg := math.Average(xs) fmt.Println(avg) }

Now create another folder inside of the chapter8 folder called math. Inside of this folder, create a file called math.go that contains this: package math func Average(xs []float64) float64 { total := float64(0) for _, x := range xs { total += x } return total / float64(len(xs)) }

Using a terminal, change directory into the math folder you just created and run go install. This will compile the math.go program and create a linkable object file: ~/pkg/os_arch/golang-book/chapter8/math.a (where os is something like windows and arch is something like amd64). Now change back into the chapter8 folder and run go run main.go. You should see 2.5. Some things to note: • math is the name of a package that is part of Go’s standard distribution, but because Go packages can be hierarchical, we are safe to use the same name for our package (the real math package is just math, ours is golang-book/chapter8/ math). • When we import our math library, we use its full name (import "golang-book/ chapter8/math"), but inside of the math.go file, we only use the last part of the name (package math). • We also only use the short name math when we reference functions from our library. If we wanted to use both libraries in the same program, Go allows us to use an alias (m is the alias): import m "golang-book/chapter8/math" func main() { xs := []float64{1,2,3,4} avg := m.Average(xs)

80

|

Chapter 8: Packages

fmt.Println(avg) }

• You may have noticed that every function in the packages we’ve seen starts with a capital letter. In Go, if something starts with a capital letter, that means other packages (and programs) are able to see it. If we had named the function average instead of Average, our main program would not have been able to see it. It’s a good practice to only expose the parts of our package that we want other packages using and hide everything else. This allows us to freely change those parts later without having to worry about breaking other programs, and it makes our package easier to use. • Package names match the folders they fall in. There are ways around this, but it’s a lot easier if you stay within this pattern.

Documentation Go has the ability to automatically generate documentation for packages we write in a similar way to the standard package documentation. In a terminal, run this com‐ mand: godoc golang-book/chapter8/math Average

You should see information displayed for the function we just wrote: func Average(xs []float64) float64

We can improve this documentation by adding a comment before the function: // Finds the average of a series of numbers func Average(xs []float64) float64 {

If you rerun the godoc command, you should see our comment below the function definition: func Average(xs []float64) float64 Finds the average of a series of numbers

This documentation is also available in web form by running this command: godoc -http=":6060"

and entering this URL into your browser: http://localhost:6060/pkg/

You should be able to browse through all of the packages installed on your system.

Documentation

|

81

Exercises 1. Why do we use packages? 2. What is the difference between an identifier that starts with a capital letter and one that doesn’t (e.g., Average versus average)? 3. What is a package alias? How do you make one? 4. We copied the average function from Chapter 6 to our new package. Create Min and Max functions that find the minimum and maximum values in a slice of float64s. 5. How would you document the functions you created in #4?

82

| Chapter 8: Packages

CHAPTER 9

Testing

Programming is not easy; even the best programmers are incapable of writing pro‐ grams that work exactly as intended every time. Therefore, an important part of the software development process is testing. Writing tests for our code is a good way to ensure quality and improve reliability. Go includes a special program that makes writing tests easier: go test. To illustrate how go test works, let’s create some tests for the package we made in Chapter 8. In the chapter8/math folder, create a new file called math_test.go. The Go compiler knows to ignore code in any files that end with _test.go, so the code defined in this file is only used by go test (and not go install or go build). The ~/src/golang-book/chapter8/math/math_test.go file should contain the same pack age math we saw before: package math

Then we import the special testing package and define a function that starts with the word Test (case matters) followed by whatever we want to name our test. We’ll be testing the Average function we wrote before, so let’s name it TestAverage: package math import "testing" func TestAverage(t *testing.T) { v := Average([]float64{1,2}) if v != 1.5 { t.Error("Expected 1.5, got ", v) } }

83

For the body of the function, we invoke the Average function on a hardcoded slice of floats (Average([]float64{1,2})). We then take that value and compare it to 1.5 and if they’re not the same, we use the special t.Error function (which is very much like fmt.Println) to signal an error to the go test program. To actually run the test, run the following in the same directory: go test

You should see this: $ go test PASS ok golang-book/chapter8/math

0.032s

The go test command will look for any tests in any of the files in the current folder and run them. Tests are identified by starting a function with the word Test and tak‐ ing one argument of type *testing.T. Once we have set up the testing function, we write tests that use the code we’re test‐ ing. In this case, we know the average of [1,2] should be 1.5 so that’s what we check. It’s probably a good idea to test many different combinations of numbers, so let’s slightly modify our test program: package math import "testing" type testpair struct { values []float64 average float64 } var tests = []testpair{ { []float64{1,2}, 1.5 }, { []float64{1,1,1,1,1,1}, 1 }, { []float64{-1,1}, 0 }, } func TestAverage(t *testing.T) { for _, pair := range tests { v := Average(pair.values) if v != pair.average { t.Error( "For", pair.values, "expected", pair.average, "got", v, ) } } }

84

|

Chapter 9: Testing

This is a very common way to set up tests (abundant examples can be found in the source code for the packages included with Go). We create a struct to represent the inputs and outputs for the function: type testpair struct { values []float64 average float64 }

Then we create a list of these testpairs, loop through each one, and run the func‐ tion: for _, pair := range tests { v := Average(pair.values) if v != pair.average { t.Error( "For", pair.values, "expected", pair.average, "got", v, ) } }

Creating a good set of tests, and in particular, knowing precisely which values to test, takes a bit of practice. For a list of floating-point numbers, it’s a good idea to test a variety of cases: an empty list, several random values, repeated or negative numbers, and so on. But even a small set of basic tests is better than none.

Testing

|

85

Exercises 1. Writing a good suite of tests is not always easy, but the process of writing tests often reveals more about a problem than you may at first realize. For example, with our Average function, what happens if you pass in an empty list ([]float64{})? How could the function be modified to return 0 in this case? 2. Write a series of tests for the Min and Max functions you wrote in the previous chapter.

86

|

Chapter 9: Testing

CHAPTER 10

Concurrency

Large programs are often made up of many smaller subprograms. For example, a web server handles requests made from web browsers and serves up HTML web pages in response. Each request is handled like a small program. It would be ideal for programs like these to be able to run their smaller components at the same time (in the case of the web server, to handle multiple requests). Making progress on more than one task simultaneously is known as concurrency. Go has rich support for concurrency using goroutines and channels.

Goroutines A goroutine is a function that is capable of running concurrently with other func‐ tions. To create a goroutine, we use the keyword go followed by a function invocation: package main import "fmt" func f(n int) { for i := 0; i < 10; i++ { fmt.Println(n, ":", i) } } func main() { go f(0) var input string fmt.Scanln(&input) }

87

This program consists of two goroutines. The first goroutine is implicit and is the main function itself. The second goroutine is created when we call go f(0). Nor‐ mally, when we invoke a function, our program will execute all the statements in a function and then return to the next line following the invocation. With a goroutine, we return immediately to the next line and don’t wait for the function to complete. This is why the call to the Scanln function has been included; without it, the program would exit before being given the opportunity to print all the numbers. Goroutines are lightweight and we can easily create thousands of them. We can mod‐ ify our program to run 10 goroutines by doing this: func main() { for i := 0; i < 10; i++ { go f(i) } var input string fmt.Scanln(&input) }

You may have noticed that when you run this program it seems to run the goroutines in order rather than simultaneously. Let’s add some delay to the function using time.Sleep and rand.Intn: package main import ( "fmt" "time" "math/rand" ) func f(n int) { for i := 0; i < 10; i++ { fmt.Println(n, ":", i) amt := time.Duration(rand.Intn(250)) time.Sleep(time.Millisecond * amt) } } func main() { for i := 0; i < 10; i++ { go f(i) } var input string fmt.Scanln(&input) }

88

|

Chapter 10: Concurrency

When run, this program produces the following: 4 6 5 7 0 8 2 3 1 9 3 9 7 9 8 0 4 5

: : : : : : : : : : : : : : : : : :

0 0 0 0 0 0 0 0 0 0 1 1 1 2 1 1 1 1

f prints out the numbers from 0 to 10, waiting between 0 and 250 ms after each one.

The goroutines should now run simultaneously.

Channels Channels provide a way for two goroutines to communicate with each other and syn‐ chronize their execution. Here is an example program using channels: package main import ( "fmt" "time" ) func pinger(c chan string) { for i := 0; ; i++ { c