Go 编程 (Go Programming)

UOHN P. BAUGH Go Programming Go Programming John P. Baugh 1 ISBN: 1453636676 E~-13: 9781453636671 ©John P. Baugh, 2010 2 TABLE OF CONTENTS What is This Book About and Who is It For? ..................... 6 About the Author ................................................................. 8 1.1 Why a New Language? ............................................... 10 1.2 How Does Go Compare with C++? ............................ 10 1.2.1 Functions ............................................................. 11 1.2.2 Is Go Object Oriented like C++? ........................ 12 1.2.3 How do I free memory in Go? ............................ 14 1.2.4 Other stuff to be aware of .................................... 14 1.3 Summary ..................................................................... 16 2.1 What Platforms can I install Go on? ........................... 17 2.2 But what if I'm a Windows user? ............................... 18 2.2.1 Virtual Machine ................................................... 19 2.2.2 Dual Booting with Ubuntu ................................... 20 2.3 Installing Go ................................................................ 22 2.3.1 Environment Variables ........................................ 23 2.3.2 Installing Mercurial.. ............................................ 24 2.3.3 Fetching the Go Repository ................................. 27 2.3.4 Installing Go ......................................................... 27 2.4 Compiling, Linking, and Running .............................. 28 2.4.1 What About gccgo? .............................................. 29 2.5 Summary ..................................................................... 30 3.1 Standard 1/0 ................................................................ 33 3.1.1 The Obligatory "Hello World" ............................ 33 3.1.2 Retrieving Input from the User ............................ 35 3.2 File 1/0 ........................................................................ 38 3.2.1 Output to a File .................................................... 38 3.2.2 Input from a File .................................................. 43 3.2.3 A Short Summary of Permissions ........................ 47 3.3 Constants and Variables .............................................. 50 3.3.1 Identifiers ............................................................. 50 3.3.2 Constants .............................................................. 51 3.3.3 Variables .............................................................. 53 3.3.4 Time Savers : Short Declarations and Distributing .............. , ......................................................................... 55 3 3.4 Some Basic Data Types .............................................. 57 3.4.1 Numeric Data Types ............................................ 57 3.4.2 Character Data Type ............................................ 60 3.4.3 Boolean Data Type .............................................. 62 3.5 The String Data Type .................................................. 63 3.5.1 The strings Package ............................................. 65 Prefixes and Suffixes ................................................. 66 Counting Occurrences of a Substring ........................ 68 Index and Lastindex ................................................... 71 Repeat ........................................................................ 73 ToUpper and ToLower .............................................. 74 3.5 .2 The strconv Package ............................................ 7 6 3.6 Keywords and Operators ............................................. 78 3.6.1 Keywords ............................................................. 78 3.6.2 Operators .............................................................. 79 3.7 Summary ..................................................................... 80 4.1 Conditional Structures ................................................ 81 4.1.1 Basics of Logic .................................................... 82 Equality ...................................................................... 82 AND operator ............................................................. 82 OR operator ................................................................ 83 NOT operator ............................................................. 83 4.1.2 The if Structure .................................................... 83 4.1.3 The switch Structure ............................................ 87 4.2 Iteration ....................................................................... 92 4.3 break, continue, and Labels ........................................ 95 4.3.1 continue ................................................................ 95 4.3.2 break ..................................................................... 97 4.3.3 Labe1s ................................................................... 98 4.4 Functions ..................................................................... 99 4.4.1 Single Return Value ........................................... 1 00 4.4.2 Multiple Return Values .................................... 1 02 Result Parameters ..................................................... 1 05 Empty Return ........................................................... 1 05 4.4.3 The defer Keyword .......................................... 1 06 4.4.4 The Blank Identifier .......................................... 1 08 4 4.4.5 Example : Minimum I Maximum Function ....... 1 09 4.4 Summary ................................................................... 110 5.1 Pointers and References ............................................ 112 5.2 Arrays and Slices ...................................................... 114 5.2.1 Arrays ................................................................. 115 5.2.2 Slices .................................................................. 119 Basic Usage .............................................................. 120 Using make() to Create a Slice ................................ 122 Reslicing .................................................................. 123 5.3 Maps .......................................................................... 125 Testing for Existence of an Element. ....................... 128 Deleting an Element. ................................................ 128 5.4 Using range with for Loops ...................................... 130 5.5 Simulating Enumerated Types with iota ................... 133 5.6 Summary ................................................................... 134 6.1 Structured Types ....................................................... 136 6.1.1 Named Fields and Anonymous Fields ............... 136 6.1.2 Methods .............................................................. 140 6.2 Custom Packages and Visibility ............................... 143 6.2.1 Visibility ............................................................ 144 6.3 Interfaces ................................................................... 14 7 6.4 Summary ................................................................... 151 7.1 Concurrency .............................................................. 153 7 .1.1 Goroutines .......................................................... 153 7.2 Communication Channels ......................................... 156 7.2.1 The Communication Operator, <-...................... 157 7 .2.2 Communicability and Select Statements ........... 161 7.3 A Simple Client and Server ...................................... 163 7.4 Summary ................................................................... 169 5 What is This Book About and Who is It For? This book introduces a new, open source, concurrent, garbage collected, experimental systems programming language called Go. Go was invented by developers at Google and is intended to offer faster development, better support for modem computing technologies, and cleaner code than other systems languages. And perhaps most importantly, Go provides a "fresh start", if you will, for developers. While Go is described as primarily being a systems language, it is, like C or C++, fully capable of supporting the development of front end applications. Go is currently only available for Linux and Mac platforms, but if you are a Windows user- don't fret! In this book, I explain the options you currently have, including usage of a virtual machine, and I will even show you how to easily install Ubuntu(a popular Linux OS) for dual booting with your Windows Machine with very little hassle! However, at the time of this writing, there has been significant effort by several volunteers at creating a port to work with MinGW to enable Windows users to develop in Go. By the time you get this book, there may be something significant and usable, so check the official website at http://www.golang.org first and see if there are any compilers for MinGW available. Because Go is new and open source, it offers a lot of potential for us, the developers. It gives us the ability to 6 help shape it to our needs and desires. Because many other programming languages (such as C, C++, and Java) were invented over a decade ago (some even two decades ago or longer), they weren't invented with modem computing technologies in mind. Go changes the game with built-in, excellent support for many modem technologies like communication channels and concurrency, as well as several coding standards enforced by the compiler to help you keep your code as clean and efficient as possible ... not to mention a built-in type to make unit testing quick and easy! In this book, I assume you have some experience with a high level programming language like C, C++, C#, Java, Objective C, Python, Ruby, or others. I do not expect you to be an expert programmer, but more experienced programmers will most likely gain a deeper understanding of the Go language than less experienced programmers, at least at first. However, this book is perfectly readable and understandable for someone who has a fundamental understanding of a high level language, but not a ton of years experience under his/her belt. I spend some time on the primitives of the language, and throughout the entire book, I teach by tutorial and example, with details where necessary Visit this book's website for errata, updates, and other useful information at: http://www.goprogrammingbook.com 7 About the Author John P. Baugh has been developing software in various forms for over ten years. He holds a Master of Science Degree in Computer Science from the University of Michigan at Dearborn. He is currently preparing to pursue his Ph.D in Information Systems Engineering. He is an Adjunct Lecturer of Computer Science at the University of Michigan at Dearborn, and has taught at Schoolcraft College in Livonia, Michigan as well. He has taught classes ranging from beginning computer science to intermediate computer science with C++, Java programming, as well as a course inC# programming with an emphasis on game design. John also currently works in Ann Arbor, Michigan for Siemens PLM Software as a software engineer in the Licensing and Business Intelligence Group, writing and maintaining licensing software for various products. Prior to working for Siemens PLM Software, John was a Research Assistant in the Vehicular Networking Systems Research Laboratory at the University of Michigan­ Dearborn. For more information about the Go Programming language, other languages, and other information on the author and his availability for book and article writing, or consulting, please visit his personal sites at: http://www.jpbaugh.com http://www.goprogrammingbook.com 8 Chapter 1 Introduction to Go One day in particular, a few months ago, I was in my room at my house doing some development using C++. It was a rather large program, so being the experienced developer that I am, I did what every true-blooded geek would do in this situation... I hit the "Build" button in my IDE of choice, and walked out of my room to go make myself a sandwich and pour myself a soda (whose name shall remain secret, but if you're interested, it is a popular black liquid and doesn't rhyme with "poke".) I was reasonably quick about it, and when I returned to my room, the compilation and linking was just finishing up. Most of the seasoned developers are reading this and thinking, "Umm ... so what's your point?". The point is that I didn't think anything of leaving the room for a few minutes while I waited for this program to compile and link. In fact, I do this quite frequently. I do this at work with my very fast and powerful dual core processor computer, as well as at home on one of my somewhat dinosaurian personal desktop. So, the bottom line is­ development takes a long time with many of the languages and development environments we use. Granted, it's nice to leave the room to get a sandwich and a soda once in a while. But when I'm not hungry or thirsty, I certainly don't like sitting around waiting! Well ... neither did the inventors of the Go language. 9 In the Go example of the same Squaring algorithm, notice a couple key differences from the C/C++ code. Firstly, the func keyword declares that there is a function about to be defined. Then, the identifier, the parameter list, and at the end of the function header, the return type (int). There are many other interesting aspects of functions with Go (including the ability to return and assign multiple values with a single call), but I hope this will give a basic idea of some of the syntax. Later in this book, we'll delve deeper into some of the more interesting aspects of Go functions, and we're definitely going to save Go methods (special functions with a receiver) for later. Go is somewhat like the first cat you've ever had contact with, when the only animals you've ever owned were dogs. You go to pet the cat in all the usual "dog places", and it slices your hand open with its incredibly fast, sharp claws, and hisses at you. Well ... to be honest, Go is a lot nicer than this, but you get the point - you don't know where to "pet" Go yet, so just take it nice and easy. By the way. True story. Be careful around cats. 1.2.2 Is Go Object Oriented like C++? This is a tricky question. In the classical sense of providing full encapsulation, inheritance, and polymorphism, no. But Go does have many object oriented features. Go does not support inheritance at all. We will see later how Go implements interfaces and allows for "pseudo­ inheritance". It is quite different from the way it is done in C++ and will take quite a bit of explanation. Just be 12 patient. Go also allows for types to contain other types in way that allows the types to take on somewhat of a superclass/subclass relationship, but it isn't quite the same as it is in C++. There is a method (not in the programming sense of the word) to the madness, though. Type hierarchies cause a lot of overhead with compilation, and multiple inheritance in languages like C++ is even worse. The inventors of Go found complete object orientation to be quite cumbersome and that it causes compilation to take a long time with generation of symbol tables, the establishment of relationships between objects in type hierarchies and other problems endemic in this type of code. This is why they opted out of a completely object oriented world and found a middle ground where they hope the OOP lovers out there will be content. Many programmers who are staunch about object oriented development (like myself) may be looking at this as a step back. But it's actually not. I can tell you honestly, as a developer who is very fond of strict object orientation in my C++ and Java code, that Go's way of doing things is not a move back to the entirely functional programming paradigm of the Dark Ages, or anything of the sort. In fact, it's quite a relevant and modem approach, which we will see later. In summary, Go provides a clean way that supports an object oriented style of programming without all the confusion and clutter. When you get to the part of this book about interfaces and see how Go actually does things, you'll probably like it a lot. Trust me! 1.2.3 How do I free memory in Go? 13 Memory cleanup in Go is more akin to Java than C++. Go has automatic garbage collection. So, you don't have to (and in fact, can't) explicitly use delete or free() to free memory allocated to various variables. At the time of this writing, the developers working on Go were exploring options to make the garbage collection much more efficient. The current build of Go utilizes a simple mark and sweep method of garbage collection. 1.2.4 Other stuff to be aware of There are many significant differences between Go and C+ +. Some require much more explanation than the brief overview we provide in this section. Much of the nitty gritty of the Go language will be examined as we explore the language throughout this book. Here are just a few things to be aware of as you read through the book: • The syntax of Go may be very different from what you expect. For example, the order of the data type, identifier, and parameter list may not be what you expect. • Go allows multiple return values from functions and methods. This leads to some unusual syntax that you may not recognize or be able to follow until you see some good solid examples later in the book. • Go has interfaces, but they are not what you'd expect if you've dealt with languages like Java. • Go does not allow coercion (implicit type casting). If you try to coerce one type to another, it is a 14 compiler error. You must explicitly tell Go when you're switching between types. • There is no pointer arithmetic. Pointer arithmetic leads to a lot of misbehaving code and erroneous memory access, including fatal crashes of software, so Go removed this capability entirely. • Strings are immutable. You cannot create a string and change one of the characters in it. • Identifiers in Go are in Unicode. So you may see special characters, such as those in Chinese, Hebrew or Arabic as identifiers. However, you can't mix character sets. • Capitalization of identifiers, as we shall see later, has an impact on the visibility of the identifier (capital letters mean public, lower case means private) • There is no operator overloading in Go. • There is no function overloading in Go. This avoids the language fragility caused by functions with the same name and different signatures, and doesn't cause a mess with things like the ugly "name mangling" that happens in C++. 1.3 Summary In this chapter, we briefly explored the Go programming language. We saw why the inventors of Go thought it was necessary to start over with a fresh, new systems 15 programming language instead of just working with older languages like C/C++ or Java. We saw some similarities and differences between C++ and the Go programming language. It is possible to program Go in an object-oriented fashion, but Go does not support strict object hierarchy, full encapsulation, or polymorphism like C++ does. We also took a brief tour of what Go does in terms of garbage collection. Within that subsection, we saw that you don't need to delete or free () allocated memory, and in fact, you cannot explicitly do so. Go takes care of the garbage collection itself. And finally, we looked at several differences and "gotcha" issues that programmers coming from another language may find interesting or unusual. In the next chapter, we'll take a look at how to install Go and any necessary supporting software, including virtual machines, or dual boots for those who have Windows boxes. 16 Chapter 2 Installing Go and Other Support Software Most of the necessary instructions to installing Go onto your Linux or Macintosh box are available at the official Go website (http://www.golang.org). However, in this chapter I will provide some useful information to get you up and running. 2.1 What Platforms can I install Goon? As of the time of this writing, there are two officially supported operating systems (or system groups): • Linux • Macintosh OS X And, there are three processor architectures supported: • x86 _ 64 (also known as the amd64 instruction set. Don't be fooled by the name! This is for both Intel 64, and AMD 64.) • x86_32 (also known as the x86 or 386. These are, as the name suggests, 32 bit processors.) 17 • ARM (These are processors with a 32 bit RISC instruction set. This particular port is not fully complete, and does have some bugs in at as of this writing) In my opinion, the best port is on the x86 _ 64 processor. Because a lot of Linux users are very passionate about open source solutions, it is also likely that they will ensure Go is well tested on Linux. The x86_32 is, according to the official website, not as "well soaked", which indicates it hasn't been as thoroughly tested, but they believe it should be as solid. And, as far as Macintosh users are concerned - you must have OS X running on an Intel 64 machine. Since, to the best of my knowledge, Macintosh OS X has never run on any 32 bit Intel processors, the x86_32 is not even applicable to Macintosh users. 2.2 But what if I'm a Windows user? Whatever anyone feels about Windows, there is no doubt it has a healthy majority of the market as far as personal computer is concerned. In fact, as much as Linux users want to deny the facts, a large portion of companies use Visual Studio on Windows machines to do their primary development. So, while Go has been geared toward the Linux and Macintosh users, there are still very good options available for Windows users! I will describe a couple options for individuals with Windows PCs. And they are both free! And, as always, 18 you do have the option of going through the trouble of partitioning your hard drive and putting a full install of your favorite Linux OS, but this will not be covered here. It is also important to note that as of this writing, there is a project going on to port Go to work with MinGW on Windows. So check the official site often to see if they've developed a port that works for you. 2.2.1 Virtual Machine One option for Windows users who want to have access to a Linux box, for example, but don't want to invest in new hardware and install Linux is a virtual machine. We're not talking about the JVM (Java Virtual Machine), although there are similarities conceptually. Basically, a virtual machine emulates hardware, and runs virtual appliances on this "hardware". For example, if you wanted to install Red Hat, Ubuntu, or Suse Linux on your virtual machine, you'd just download the virtual appliance for that particular operating system, and install in onto your virtual machine as if the virtual machine were an actual piece ofhardware. And while I won't provide a complete step by step installation for this particular technique, I'll give you some basic steps as resources to quickly get you going if you choose this option. In my opinion, one of the best virtual machines available for Windows is VMware Player. So to get started: 1. Go to http://www. vmware.com 2. Go to Support & Downloads 3. Click on VMware Player under Desktop Products, listed on the page 4. Download and install the VMware Player 19 Then, you'll need a Linux virtual machine appliance to run on your VMware Player. Note: If you plan on running a 64 bit virtual machine appliance, the actual hardware on your physical machine (your computer) needs to be 64 bit. 5. Now, go to Virtual Appliances near the top of the page 6. Do a search for your virtual appliance of choice, such as "Linux", "Red Hat", "Suse", or "Ubuntu" 7. Follow the instructions to download and install the virtual appliance of your choice. After you've installed the virtual appliance, you should be able to treat it as if it were an actual Linux box. This means you can install Go and whatever else you might need! 2.2.2 Dual Booting with Ubuntu Personally, this is my favorite option. I have a system that dual boots with Windows 7 and Ubuntu. Ubuntu is one of my favorite Linux operating systems. I've dealt with many different operating systems based on the Linux kernel, and Ubuntu is by far my favorite for several different reasons. I like Linux. Don't get me wrong. In fact, I love Linux. But, a lot ofLinux users (many ofwhom are involved in the development of their particular Linux of choice) hate Windows. And, albeit a rogue element of the Linux community, some even hate Windows so much, that they hate Windows users. 20 Ubuntu isn't like that. Ubuntu is a very Windows friendly community. And in fact, they provide one of the easiest dual boot installers I've ever seen for a Linux OS. This magical little jewel is called Wubi. Now, here are some notes before you being. This installer is not going to partition your hard drive. It actually cleverly creates a special file that Linux recognizes as a hard drive. Then, it modifies some of your boot settings so that when your computer boots, it will let you select either your Windows OS or the Ubuntu installation. Wubi is not itself a Linux port. It is simply an installer of Linux for Windows users. (Windows UBuntu Installer. Clever, eh?) And the version ofUbuntu that installed is a legitimate, complete installation of Ubuntu. It is not a virtual machine. I've used this installer before, and although I do have access to other Linux machines, I decided I wanted Ubuntu on my laptop. I have had very little trouble with it. Windows 7 and Ubuntu play nicely together. Before we beigin, there are a few important notes about installing with Wubi: 1. Hard drive read/writes are a wee bit slower, but shouldn't affect your Go programming significantly unless your computer is a complete dinosaur. 2. Wubi does not support hibernation. The details are available on the official website. 3. The Wubi-installed Ubuntu is significantly more sensitive than an installation on a partition would be, to 21 power offs. If you shut the power off while Ubuntu is doing something, you may completely destroy the Ubuntu installation and have to start from scratch. If it doesn't destroy the installation completely, it may cause strange malfunctions like large sea creatures jumping out of your machine and making a mess of your room (Wubi on Whales? Okay, I had to. Moving on ... ) Regardless, your Windows machine should be safe, as Wubi is not really messing much with your actual hardware or doing partitioning or anything. But, use caution. So here is where you go to get Wubi: http://www. wubi-installer.org It's very simple. Just download it, and follow the installation instructions as you would with any product. Then, you will restart your machine, and be able to select the OS of your choice with the arrow keys. Voila! 2.3 Installing Go Okay, so you've got the appropriate hardware and OS, now how do you actually install Go? In this section, I'm going to focus on installing Go onto Ubuntu, but most of the installation should be similar regardless of what type of Linux (or Mac) you have. As long as it's a Unix-like OS, you can use Unix commands, so you should be in business. The official installation guide is on the official website, located at: 22 http://www.golang.org/doc/instalLhtml I recommend opening up this webpage and using it as your primary guide through installation. However, I will make some important notes about the installation that could throw you off, especially if you're not familiar with Unix­ based systems. 2.3.1 Environment Variables These are crucial to your installation. You should edit the .bashrc file, as prescribed on the official installation page. The .bashrc file is basically a settings file, so that when you open a terminal window, all of the necessary environment variables and prerequisites are set up. If you don't edit this file, you'd need to do manual exports of all the environment variables every time you opened up a terminal window. That is no fun at all. Note also that the .bashrc is not visible when you simple do an ls (list) command. In order to see if it's there or not, you can do 1 s -a (list all) command. So, in the .bashrc, you must write the following: export GOROOT=$HOME/go export GOARCH=amd64 export GOOS=linux export GOBIN=$HOME/bin Make sure to save the .bashrc file with these exports. Then, close the terminal window, and open a new one. Then, do an env I grep '"GO' 23 to ensure that the GO environment variables have been added to your environment. Alternatively, you can do an echo SGOROOT (or $GO) to make sure the variables are there. Now, there is one more thing related to environment variables that you have to do that is very important. The official installation page says that the environment variable GOBIN is optional, but I found that my installation did not work appropriately without it. If you don't export GOBIN and also add it to your PATH, the installation may have problems when it gets to the tests at the end. To add the GOBIN (where your Go compiler and linker reside) to your PATH, do the following: export PATH=$GOBIN:$PATH This will take your new GOBIN environment variable, and append it to the front of your PATH environment variable. As a note, if you have any difficulties or warnings about missing directories, you make have to create a directory (with mkdir, for example) that is missing. 2.3.2 Installing Mercurial Mercurial is a revision control application that allows projects (like Go) to maintain repositories with the latest version of their software, so that you can easily download it and upgrade your installation. In order to test and see if you have mercurial installed, type 24 hg in a terminal window. If it says it doesn't exist, or hasn't been installed yet, first, try using the options for installation that are given. I personally found that the method provided for Ubuntu on the official site did not work. So I went directly to Mercurial's download page: http://mercurial.selenic. comlwiki/Download Then, I went to the Linux (.deb) section for my Ubuntu OS (obviously, you should go to the Linux installation that you have). Then, I selected Karmic (which is the version of Ubuntu I have), and it takes you to the download page. The actual download link will be at the bottom (labeled amd64 or i386). Select the appropriate processor architecture for your platform, and then a mirror site to download it from. I personally opened mine with GDebi Package Installer, which is an incredibly helpful installation tool. Since Mercurial has dependencies (which include things like Python), you may have to install these dependencies first. 25 ' Package Installer - mercurial _ o x file tlelp Package: mercurial EITor: Dependency is not satisfiable: mercurial­ Status: common (= 1.3.1-1) Description Details Included files scala ble distributed version control system Merrurialls a fast. lightweight Source Control Management system designed for efficient handling of very large distributed projects. Its features include: • 0(1) delta-compressed file storage and retrieval scheme • Complete cross·index1ng of files and changesets for elllCient explorabon of project history • Rllbust SHAJ-based Integrity checking ond i!ppend-only storuge model • Decentralized development model with arbitrary merging between trees Figure 2.3.2-1 If you try to install the mercurial package, you will likely get the error in Figure 2.3.2-1. If you get an erTor like this one, just install the dependency that is listed in the error, and it should take care of the problem. I found that all I really needed to install was the dependency of mercurial, listed as mercurial-common (= 1.3.1-1), and nothing else. Then I did a sudo a?t-get install me rcurial That was enough to get the hg command working. Your experience with Linux or Macintosh may be slightly different than my experience, so you may have to play around with what dependencies need downloaded. But the overall installation should be fairly straightforward. 26 2.3.3 Fetching the Go Repository Once you have mercurial installed, you can fetch the Go repository: hg clone -r release https://go.googlecode.com/hg/ $GOROOT You must make sure that SGOROOT doesn't exist yet, or at very least, that it is empty. 2.3.4 Installing Go Once you have all the supporting software installed, it is pretty simple to install Go. Per the official installation page, you must ensure you have the following applications installed: • Bison (a parser generator) • Gee • Libc6-dev • Ed • Gawk • Make To install these, you type: sudo apt-get install bison gee libc6-dev ed gawk make This should take care of the installations of the final support software. Note carefully that the above syntax will work on Debian-based Linux systems like Ubuntu. For most other Linux distributions, RPMs will be used. Now, to build Go, we follow the steps from the official installation site: 27 cd $GOROOT/src ./all.bash When it finishes building, you should get something like: 0 known bugs; 0 unexpected bugs although the number of known bugs could vary. 2.4 Compiling, Linking, and Running By this point, you should have Go installed. This section will hopefully help you to ensure everything's working correctly by taking the compiler and linker for a test drive. Go has some strange names for its official compilers and linkers. These will vary from platform to platform. The following shows you the name of the compiler and linker for your platform: Platform Compiler Linker X86 64 6g 61 X86 32 8g 81 ARM 5g 51 Note that, for the linkers, the second character is a lowercase "L" for "linker", not the number 1. The entire process of creating an executable with Go is as follows: $ 6g filename.go 28 $ 61 fi1ename.6 $ ./6.out Notice, the 6 would be replaced with the appropriate number based on what platform you are on. Also, note the $ signs appear at the beginning of basic prompts in the bash shell, and are not part of the commands themselves. Also note that you can (optionally) change the name of the executable that is output, by using the - o flag: $ 6g fi1ename.go $ 61 -o myExeName fi1ename.6 $ ./myExeName where myExeName is the name you want your executable file to have. The Go compiler is known as gc, for "Go Compiler" (imagine that!). It should not be confused with gee, or gee go. 2.4.1 What About gccgo? If you don't want to use the Go compilers provided by the inventors/developers at Google, you have another option. gee go is a compiler frontend for gee, which as most of the readers probably know, is the very popular GNU compiler. The gccgo typically compiles slower than the (6g, 8g, 5g) compiler, but the generated executable is a little bit faster. For more information about the installation of gee go, please refer to: http://golang.org/doc/gccgo _ install.html 29 It is also important to note that, although much of this is outside the scope of this book, gccgo currently offers some interoperability with C. As with much of the projects related to Go, much of the specifics are subject to change. 2.5 Summary In this chapter, we covered a lot of territory related to the installation of Go and supporting software. Go is officially supported on Linux and Macintosh. However, Windows users still have options, including using a virtual machine, or Wubi, a handy installer of Ubuntu so that you can dual boot with Windows and Ubuntu Linux. These two options are described in a reasonable amount of detail. A third option is to do a partition, and full installation of your Linux (or potentially MacOS) of choice. This option is only mentioned, and is not described in detail. Furthermore, a MinGW port is in the works as of the time of this writing, and it is quite likely you will be able to program in Go even more easily on Windows. After ensuring we have the correct hardware and/or software to install Go, we set up the necessary environment variables. Next, we explored how to install Mercurial, a revision control management system that allows us to install Go. I described some of the pitfalls and issues you may have when you try to install Mercurial, including potential problems with dependencies. Then, we learned how to fetch the code for Go from the Go repository. And finally, we installed Go itself, so that we could compile and link applications with the 6g (or 8g or 30 5g, depending on your platform) compiler, and the 61 (or 81, or 51) linker. Finally, I mentioned gccgo, the frontend for the popular GNU GCC compiler. It is a separate project from the official Go project at Google, but was designed based on the language specification and works almost as well as the Google Go compilers. Also of importance is that it compiles more slowly than the Google Go compilers (6g, 8g, or 5g) but that the executable is often slightly faster. In the next chapter, now that we have a basic history and motivation under our belt and the appropriate software installed, we will begin learning about how to program in Go. 31 Chapter 3 Getting Started with Go Programming Hopefully the previous chapters were not too mind numbing. I tried to give you a basic understanding of why we should even be interested in Go as a language. Also, when I start out with a new programming language, I often find installation to be one of the most irritating aspects of the whole process, so I tried to keep my installation instructions to a bare minimum, but provide enough information so that you can install Go with very little trouble. Anyway, enough of the dark and dirty past -let's get to the fun, shall we? In this chapter, we'll get into the nitty gritty of the basics. We'll learn a lot about the syntax of Go, the different data types that are available, basic I/0, keywords, operators, and a whole boatload of other useful information. I'll provide some good, clear, and concise examples so that you'll gain a solid understanding of what is actually going on. 3.1 Standard 1/0 32 In this section, we' 11 go over some basic mechanisms for printing to the console, and retrieving input from the keyboard. 3.1.1 The Obligatory 11Hello World" In this subsection, we're going to use the fmt package to do simple output to the console, in a fashion similar to what you may be familiar with in C. 1 package main 2 import "fmt" 3 4 func main{) { 5 fmt.Printf{"Hi there!\n "); 6 Notice a few things about the above code segment. First, we declare this to be the main package on line 1. Then, on line 2, we import the fmt package (for formatted 110). On line 4, we begin our main function. Notice a few things about this. Firstly, there is no return type. The func is just a keyword declaring that main is a function. But return types in Go are specified after the identifier of a function. On line 5, we call the Printf (} method of the fmt package. This allows us to print to the console. Finally, on line 6, we close the code block with a closing curly brace. Also, be careful about the opening curly brace, "{". It must be on the same line as the declarative header of the main function (and all functions for that matter). Some people prefer programming with the curly brace on a separate line, thusly: func main {) { 33 fmt.Printf("Hi there!\n "); However, this will cause a compiler error, such as: hello.go:S: syntax error near main This may change in the future, but just to be safe, always keep it on the same line as the function header. In fact, keeping everything clean and compact is in line with the Go philosophy of coding. In case you're curious, the actual reason this causes a compiler error has to do with how and where Go automatically inserts semicolons. So, when you compile the code, the compiler actually reads: func main(); { fmt.Printf("Hi there!\n "); which is a syntax error. Go is most certainly concerned with efficiency and tidiness. In fact, let's see what happens if we include another package, but don't use it: 1 package main 2 import "fmt" 3 import "os" 4 5 func main() { 6 fmt.Printf("Hi there!\n "); 7 or, alternatively, with some "shortcut syntax" provided by Go (notice you can put parentheses around the things you're importing, without retyping import. More on this later.): 34 1 package main 2 import 3 ( 4 "fmt" 5 "os" 6 7 8 func main() { 9 fmt.Printf("Hi there!\n II) i 10 } What happens when we try to compile this code? hello.go:5: imported and not used: os What's this? A compiler error simply because I included a package I'm not using? You betcha! Go wants to you to be lean, clean, programming machines. And part of this is to not include packages you're not using in your code. They just adds fat to your executables (i.e., they increase the executable size) and decrease the readability of your code. 3.1.2 Retrieving Input from the User Retrieving input from the user is a little bit more complicated. Some of the syntax may appear a bit strange, especially if you are brand new to Go. For right now, just try to follow as best you can, and we'll go over more details about packages, variable declarations, functions, and a whole lot more later in the book. Here's the Go code that we will analyze: 1 package main 2 3 import 4 ( 5 "fmt" 6 "bufio" 35 7 "os" 8 9 10 func main() { 11 12 var inputReader *bufio.Reader; //reader for input 13 var input string; //input of type string 14 var err os.Error; 15 16 inputReader = bufio.NewReader(os.Stdin); 17 18 fmt.Printf("Please enter some input: \n"); 19 20 input,err = inputReader.ReadString('\n'); 21 22 if err == nil{ 23 fmt.Printf("The input was : %s\n", input); 24 25 As always, we start at line 1 by naming the package main (there is reasoning behind this, that won't be covered just yet). Then, on lines 3-8, we have our imports. This time, we need a couple more packages than in the input example. We need the os package so we can use Stdin, and we need bu f i o to perform the retrieval of input from the keyboard. In the main function, you see on lines 12-14 declarations of variables. Notice that the format of declaring variables in Go is: var identifier data_type Note that var is a keyword, identifier is the name of the variable, and data_ type is the type of data with which we are dealing. On line 16, we perform the actual creation of the Reader to read input in from the keyboard. 36 Line 18 prompts the user to enter something (this could ask for a name, a color, favorite animal... pretty much anything). Line 20 may be very confusing to the reader. Notice there are not one, but two variables (input of type string, and err of type os. Error) receiving data from the method call inputReader. ReadString ( 1 \n 1 ) • In full, this is: input,err; inputReader.ReadString( 1 \n 1 ); Go supports functions and methods returning multiple values. The ReadString () method is such a method. We will discuss this in more detail later, but for now, just know that ReadString () returns the input string put into the buffer (in this case, from the keyboard, i.e., standard input/Stdin) as its first return value, and returns an error as its second return value. So, two variables are put on the left side of the call to catch both the input from the keyboard, and an error returned by the method. If there are no errors, ReadString () returns nil for its error return value. Also, note that the argument to the ReadString () method is the newline character ('\n '). Finally, in lines 22-24, we check to ensure there were no errors, and then echo the user's input back to them. For those familiar with print£ () function from the C programming language, you may have figured out that % s is a specifier which will match a character string variable (or constant) listed as one of the parameters after the first. 3.2 File 1/0 37 In this section, we' 11 examine a different case of input and output, namely, file input and file output, known collectively as File 110. 3.2.1 Output to a File Below, we have some code for sending output to a file 1 package main 2 import( 3 "os" 4 "bufio" 5 II fmt II 6 7 8 9 10 12 13 func main() { var outputWriter *bufio.Writer; //a writer for output var outputFile *os.File; //an output file var outputError os.Error; //a variable to catch any error var outputString string; //the string to print 14 // Create file for output 15 outputFile,outputError = os.Open("output.dat", os.O_WRON:Yios.O_CREATE,0666); 16 17 if outputError !; nil { 18 fmt.Printf("An error occured with file creation\n"); 19 return; //exit the program 20 21 22 II Create a new Writer, associated with the file we created 23 outputWriter bufio.NewWriter(outputFile); 24 25 outputString "hello!\n"; 26 27 for i:=O; i 10; i++{ 28 outputWr ter.WriteString(outputString); 29 outputWr ter.Flush(); 30 }//end for oop 31 38 As always, we have the initial package declaration at the top, followed by import statements: 1 package main 2 import( 3 "os" 4 "bufio" 5 "fmt" 6 Notice that we have the fmt and os packages imported, just like in the standard output example. However, this time, we will utilize the bu f i o package as well. In lines 8-12, we have the beginning of our main function, and several variable declarations: 8 func main() { 9 var ou~putWri~er *bufio.Wri~er; //a writer for output 10 var outputFile *os.File; //an output file 11 var outputError os.Error; //a variable to catch any error 12 var outputString string; //the string to print Notice the var keyword comes before the name, or identifier, of the variable, and then the data type is at the end of the declaration. Also, for some individuals coming from C++, statements like *bufio.Writer may seem a little peculiar. But, it's actually quite natural, when you say the statements out loud. For example, "I'm declaring a variable named output Writer that is a pointer to a bufio. Writer. It only seems "backwards" if you're accustomed to using a language like C/C++ or Java. It could be argued that the order of the declaration is actually closer to natural language (at least to English) than C++, where you may have something like: bufio. Writer* outputWri ter; //C++ code 39 In C++, we have the data type first, and then the identifier, and no var keyword. If you read it aloud, it becomes, "I'm declaring a bufio. Writer pointer called output Writer". This could be refined slightly, but it really is farther from English than the Go syntax is. Moving on, we create a file to represent the output file we are going to write to: 14 // Create file for output 15 outputFile,outputError = os.Open("output.dat", os.O_WRONLYios.O_CREATE,0666); 1.6 :7 if outputError != ni~ { 18 fmt.Printf("An error occured with file creation\n"); 19 return; //exit the program 20 Notice in line 15, we are using two of the variables declared earlier, namely, outputFile and outputError to catch the return values of os . Open {) . We will explore more on multiple return values later, but for now, just think of os. Open {) as a machine that produces multiple outputs. Let's examine the parameters of os. Open {) carefully: os.Open{"output.dat", os.O_WRONLYI os.O_CREATE,0666); The general signature of the function is as follows: os.Open{filename string, flag int, permissions int); Open {) function takes three parameters, in order: • The filename of type string. This is the name of the file to be used. 40 • Thejlag(s) of type int. This lists the flags, indicating what operation(s) we're performing on the file. For example, 0 _ WRONL Y for "write only" and 0 _CREATE for "create", as in our output example. Notice, we can combine them with the bitwise OR operator, 1. • The permissions of type int. This parameter includes the access permissions to the file in question. For example, if we are creating a file (as above), and we want the permissions to be full read, write, and execute permissions to users, groups, and others, we would set the permissions to 0777. This corresponds to the chmod command used on Unix to change permissions. In our particular example, we set the filename of the output file to "output.dat". For the second parameter, we do a bitwise OR of 0 WRONL Y and 0 CREATE to indicate - -we want to create and/or write to the file, output.dat. Finally, we set the access permissions to 0666, indicating that we want to allow the user, the group, and others to have read and write permissions. As an experiment, try recompiling the program with the permissions set to different numbers, like 0 (indicating no one has the permission to do anything to the file). In lines 22-31, we see the remainder of the program: 22 II Create a new Writer, associated with the file we created 23 outputWriter = bufio.NewWriter(outpu~File); 24 25 outputString = "he:lo!\n"; 26 27 for i:=O; i function. The new value of theint is 370. Then, on line 25 theint is converted back to a string: myNewString = strconv.Itoa(theint); Now, myNewString contains a string representing the Int. Recall that theint is now 370, so the string that is returned by Itoa () is "370". 3.6 Keywords and Operators In this section, I'll show you the available keywords and operators that Go has to offer. I will not go into incredible detail in this section, as this section is only here to make you aware of the available keywords, operators, delimiters and other special tokens available in Go. Much of this will be straightforward, and if you are familiar with another high level programming language, much of this will seem familiar. However, Go does have some keywords (also called reserved words) that are unique to the language. 3.6.1 Keywords The following are the keywords of the Go language: break default interface case defer map 78 chan else go to package switch const fall through if range type continue for import return var We have seen a few of these keywords thus far, and some of these may look familiar to people who program in other programming languages. Note that identifiers cannot have the same name as any of the keywords. The func keyword designates the beginning of a function definition. The var keyword designates the beginning of a variable. The const keyword, similarly, denotes the beginning of a constant definition. We have briefly utilized, but not thoroughly explained (yet) several other keywords, such as if, else, and for. We will more thoroughly explore these structures later in this book. There are also several very unique keywords that may look alien to newcomers to the Go programming language. These include keywords like chan and go. We will also explore these later after we have a stronger foundation in the fundamentals of the language, as they make up some of the more powerful features of Go. 3.6.2 Operators In the Go language, the following are available operators, delimiters, and other special tokens: + & += &= && -- != ( ) - I -= I= I I < <= [ ] * "' *= "'= <- > >= { } I << I= <<= ++ = := ' ; % >> %= >>= -- ! ... : 79 I &" I &"= Many of the operators listed are probably very familiar. The +I - , * , I , ==, ++I -- and others are quite straightforward. The delimiters such as parentheses ( ) , brackets [ l, and braces, { } should also look familiar. Others, such as <-, : =, & " may look very foreign to you. We will look into many of the available operators later in this book. 3.7 Summary In this chapter, we have explored some of the fundamental techniques and building blocks used in Go. We have explored how to retrieve input from the keyboard, as well as from an input file. Also, we learned how to produce output, both to the console, and to an output file. We explored many of the fundamental data types available with Go. We extensively explored strings, and some of the packages used to work with string data. Finally, we briefly introduced the keywords and operators available in Go. 80 Chapter 4 Control Structures and Functions Like most high level languages, Go has control structures to allow programs to perform tasks such as making decisions (conditional structures) and repeating tasks (iterative or looping structures). It is rare when any significant program simply performs a linear sequence of tasks. More often than not, programs are full of decisions and repetitive tasks. Also, in this chapter, we will explore functional decomposition in Go. With functions, we can take a frequently used sequence of instructions and give them a name, to be used over and over again without having to rewrite the same code over and over. 4.1 Conditional Structures With conditional structures, we can control the execution of a program, allowing the program to make decisions based on the current program state, values of variables, and values of constants. Go offers two conditions structures: the if structure and the switch structure. 81 4.1.1 Basics of Logic This subsection serves as a review of logic so that we can more effectively explore the conditional structures of Go. Equality == is the equality operator that takes two operands. It is read as "is equal to". Notice there are two equal signs, one followed by the other. If we have a variable set to the value 6: var someVar int = 6; Give this, sorneVar sorneVar AND operator 2 is a false statement 6 is a true statement The AND operator is a logical binary operator, denoted by & & • Logical AND takes two operands, and the entire statement is true only ifboth operands are true. Given T representing a value that is true, and F representing a value that is false: T && T is true T && F is false F && T is false F && F is false 82 OR operator The OR operator is a logical binary operator, denoted by 1 1. Logical OR takes two operands, and the entire statement is true if any of the operands are true. Given T representing a value that is true, and F representing a value that is false: T I I T is true T I I F is true F I I T is true F 1 1 F is false The only situation in which a statement involving OR is false is if both operands are false. Otherwise, as long as at least one of the operands is true, the statement is true. NOT operator The NOT operator is a logical unary operator, denoted by ! . Logical NOT takes one operand, and makes the truth value of its operand the opposite of what it is: !T is false !F is true 4.1.2 The if Structure For those familiar with other high level languages, the if structure will look very familiar. The syntax, however, maybe slightly different than what you may be accustom to. The if structure takes the basic form: if condition{ 83 //do something Also, there is an optional else or else if as part of the if statement. if condition{ //do something else if condition{ //do something else else{ //default Notice very carefully that with Go, the else if and else statements must be on the same line as the closing curly brace of the previous part of the structure. Also, just like functions (such as the main function that we are so familiar with), the opening curly brace must be on the same line as the if statement header and condition. The following is a syntax error: if condition { //do something Also, notice that with proper syntax, there are no parentheses surrounding the condition. Another form of the if statement involves initialization in the header, as follows: if initialization statement; condition{ I I do something The initialization statement must be separated from the condition with a semicolon. 84 Up until now, we've seen the general forms that if statements can take. One of the best ways to understand concepts is in the contexts they will be used. So, as an example of how to use if -statements, consider the following: 1 package main 2 import( 3 "fmt" 4 5 6 fun c rna in ( ) { 7 var firstint int 10; 8 var cond:nt ~nt; 9 10 if f~rstint <= 0{ 11 fmt.Printf("First int is less than or equal to 0\n"); 12 }else if first:nt >0 && first:nt < 5{ :3 fmt.Printf("?irst intis between 0 and 5\n"); 14 }e:se{ 15 fmt.Printf("?irst intis 5 or greater\n"); 16 17 18 19 ~f condint = 5; cond:nt > 10{ 20 fmt.Printf("condint is greater than 10\n"); 21 }else{ 22 fmt.Printf("condint is not greater than 10\n"); 23 24 The output of the above code is as follows: johnbaugh@ubuntu:-/Des~top/go progra~ing/if elseS ./6.out First int is 5 or greater - - condint is not greater than 10 This is example contains variables that were initialized with literal values in the code itself, and is fairly trivial for the sake of brevity. It is more common that values are input from the user or from a file, and stored in the variables. 85 In the above example, we show the two different element forms available for if statements. On line 7 we declare and initialize the variable firstint. On line 8 we declare the variable condint. On lines 10-16, the firstint variable is tested, and the appropriate branch is taken depending on its value. Since the value 10 is stored into firstint statically, we know which branch the program will take (namely, the final else branch). Notice the usage of<= on line 10. This is the less than or equal to sign. Thus, if firstint was a negative number or 0, then the first branch would be taken. Since this is not the case, the second branch condition is tested: firstint >0 && firstint < s. The && (logical AND) indicates that both of its operands must be true in order for the entire statement to be true. In this case, the branch will be followed if the integer is strictly between 0 and 5. Again, this is not the case. An else without any if attached serves as a sort of catch­ all. In other words, if all of the above statements that were evaluated are false, the e 1 se branch will be followed. On lines 19-23, we see a different form of the if statement. In this case, we perform a value assignment to the variable on line 19, as part of the if statement's header. After initializing condint, we test its value to determine if it is greater than 10. Like with our first example, we also have an else statement that acts as a catch all. As a final note on if statements, you could use parentheses around the condition, but it is not typical in Go programming. It is, however, useful in situations where 86 you may wish (or need) to group logical statements, such as if ! (varl == var2). 4.1.3 The switch Structure Another conditional structure available in Go is the switch structure. These switch structures are somewhat similar to what you may have used in C/C++, but do not require that you use constants or ints as your conditional values. Also, you can use multiple cases, separating them by commas. One major difference between Go and C/C++ switch structures is the cases do not have automatic fall-through. In C/C++ and similar languages, you must use a break; statement to ensure that after one case is determined to be true, the switch statement is completed and other cases (such as default) are not executed. With Go, as soon as a case is determined to be true, the switch is complete, and program control is returned back to the outer structure (such as the main function). You can cause automatic fall-through (thus behaving similarly to how C/C++ switch structures behave without break statements) using the Go keyword fall through. Let's consider a typical example of how the switch structure is used: 1 package main 2 import( 3 "fmt" 4 5 6 func main() { 87 7 var sorneNum int = 5; 8 9 sw:tch someNum{ 10 case 7: fmt.Printf("It's equal to 7\n"); 11 case 5: frnt.Printf("It's equal to 5\n"); 12 default: frnt.Printf("It's not 5 or 7\n"); 13 }//end swi~ch 14 And here is the output from the above program: johnbaugh@ubun~u:-/Desk~op/go_programrning/sw:~chlS ./6.out It's equa:.. to 5 This is a trivial example, in that we know exactly what the value of someNurn is, since we initialized its value to 5 and did not change it before entering the conditional evaluation in the switch structure header. But, as has been typical, I am simply introducing the concept and example. We can use it and build upon our knowledge in practice, and in examples later in the book. In this example, we write our switch keyword, followed by the variable (in this case) to be evaluated. Then, with each of the case statements, our variable sorneNum is compared to the value indicated. For example, on line 10: 10 case 7: fmt.Printf("I~'s equal ~o 7\n"); If sorneNum is equal to 7, we print the string "It's equal to 7", followed by a newline, to the console. In our situation, the test on line 10 evaluates to false, since someNum is not equal to 7. Thus, it tries the next case statement on line 11. Since this evaluates to true, since sorneNum is equal to 5, the output is printed, and the switch structure is exited. In C/C++, we would have needed to put a break; statement. But, as we discussed earlier, this is not what happens with Go. 88 Now, let's consider an example that uses strings: 1 package main 2 import ( 3 "fmt" 4 "os" 5 "bufio" 6 7 8 func main() { 9 var inpu~Reader *bufio.Reader; 10 var input string; 11 var err os.Error; 12 inputReader = bufio.NewReader(os.Stdin); 13 14 fmt.Printf("Please enter your name:\n"); 15 input,err = inputReader.ReadString('\n'); 16 17 if(err !=nil) { 18 fmt.Printf("There were errors reading. Exiting 19 20 21 program\n"); return; 22 switch input{ 23 case "John\n" 24 fmt.Printf("Welcome, John!\n"); 25 case "Silas\n" : 26 fm~.Printf("Welcome, Silas!\n"); 27 default: 28 fmt.Printf("You are not welcome here! Be gone!\n"); 29 }//end switch 30 And here is the interaction output from different test cases: johnbaugh@ubun~u:-/Desktop/go programming/string switch$ ./ 6.out - - Please enter your name: George You are not welcome here! Be gone! johnbaugh@ubuntu:-/Desktop/go_progra~ming/string_switch$ ./ 6.out Please enter your name: John Welcome, John! johnbaugh@ubuntu:-/Desktop/go programming/string switchS ./ 6.out - - Please enter your name: 89 Silas Welcome, Silas! In this example, we have declared the variables required to interact with the user through the command line. In lines 22-29, we see the case statements in our switch structure. Since the ReadString () function returns the string read from the input stream {os. Stdin in our case) up to and including the delimiter, we must take the delimiter into consideration when switching on the string variable input. This is why we have the newline character at the end of the strings we're comparing input against. This program could be seen as an (albeit oversimplified and insecure) authentication system. There are only two usemames that are considered authenticated users, namely "John" and "Silas". Otherwise, (default) the user is not authenticated, and the program tells them, in no uncertain terms, that they are not welcome. We could have written a program similar to the above program using the fall through keyword, by rewriting the switch structure as follows: switch input{ case "John\n" fall through; case "Silas\n" : fmt.Printf("Welcome, %s", input); default: fmt.Printf("You are not welcome here! Be gone!\n"); }//end switch In this case, regardless of whether the user is John or Silas, we simply want to welcome them. This is thanks to the fall through keyword, which causes the case for "John\n" to perform the action(s) in the next case statement. Thus, we can use the % s modifier to dynamically insert the user's input into the output string (either "John" or "Silas"). 90 And, another way to consider multiple case statements is to separate the values by commas. Thus, the above switch statement can be rewritten as: swi-:ch inpu-:{ case "John\n","Silas\n" : fmt.Printf("Welcome, %s", input); defaul-:: f~t.Printf("You are not we:co~e here! Be gone!\n"); :!lend switch Another form of the switch statement can be used to very accurately simulate if-else chains. In this form, there is no expression after the switch keyword, so the cases switch on true. This means that if the logical expression following the case statement is true, the body of the case will be executed. The following code is an example of this type of expressionless switch statement: pacJ 0 && someNu~er < 10: :3 fr..t.Prin-:f ("Sorr.e nur..ber is be-:ween 0 and :O\n"); 14 default: 15 fmt.Printf("Sor..e nu!l'.ber is :o or greater\n"); 16 17 And the output of the above code is: 91 johnbaugh@ubuntu:-/Desktop/go_prograrnming/noexp_switch$ ./6 .out Some number is between 0 and 10 In the above code, we now see that the switch structure has no expression, and to the astute reader, looks very similar to an if-else structure. Often, switch statements are helpful, especially with a large number of comparisons. The syntax is often more readable, and even more writable than if-else statements. 4.2 Iteration It is common for a program to repeat a particular task multiple times. In this section, we will explore how to perform iteration with the Go programming language. Go has only one iterative construct, the for loop. This may seem peculiar to readers coming from other languages, like C/C++ or Java, which have iterative constructs like while loops and do-while loops. In these programming languages, the for loop is used most typically (and some would argue, most correctly) in a manner known as count-controlled iteration, that is, when a particular number of iterations is required, and known before the loop is entered. Iterative constructs such as while and do-while in these languages serve as event­ controlled loops. This means that they are ideally suited for when a particular event must occur in order to exit the loop. However, the programmer doesn't have to use them in the prescribed manner. In Go, the for loop allows for a little more flexibility, and can be easily used for both event and count controlled scenarios. 92 The following code will be an example of the fairly typical usage of the for loop: 1 package main 2 import ( 3 II fr..t." 4 5 6 func main () { 7 8 for i:=O; i < 5; i--i 9 fmt.Print.f("!'~ in :he %d i:erat.ion\n", i); :o !//end for :oop This is a very typical and simple example of using the for loop in the manner that is most familiar to readers coming from many other high level languages. In this form, the for loop has an initialization, a conditional check, and a modification of the variable, i. Notice that there are no parentheses surrounding the header of the for loop. Also notice that I use the short declaration format for the counter variable. This could be rewritten with the counter variable declared outside and before the for loop, as follows: var i in:; for i=O; i < 5; i--~ fmt..Prin:f(":'n in :he %d it.era:ion\n", i); }//end for loop And the output of the above program is as follows: johnbaugh@ubu tu:-/Deskt.op/go prograrrming/for loopS ./6.out I'm in the 0 teration - - I'm in the 1 :eration I'm in the 2 tera:ion I'm in :he 3 tera:ion I'm in the 4 t.era:ion 93 Another way to use the for loop is by treating it like a while loop, leaving out the header and replacing it with semicolons (or nothing, as well shall see). To break out of the loop, the break statement can be used. 1 package main; 2 import( 3 "fmt" 4 5 6 func main() { 7 8 var i int ; 5; 9 10 for ; { 11 i ; i -1; 12 fmt. Print£ ("The variable i is now %d\n", i); 13 if i < 0 { 14 break; 15 } 16 } I lend for 17 And the output is as follows: johnbaugh@ubuntu:-IDesktoplgo_programminglfor_event$ t The variable ~ is now 4 The variable i is now 3 The variable i is now 2 The variable i is now 1 The variable i is now 0 The variable i is now -1 .16.ou In this code, the magic happens on line 10, the for loop header. Notice there is no condition check (or anything else for that matter). When there is a condition check missing in Go for loops, it defaults to true. This can also be written without a header at all. Instead of: for ; ; { 94 You can write: for { The only difference is that we remove the semicolons completely. These types of for loops would be infmite loops without the break statement. 4.3 break, continue, and Labels In this section, we will explore some of the more interesting aspects of the break and continue keywords in Go, including the use of labels. We have already seen that the break statement allows us to break out of a particular block of code, such as a for loop. For now, let's look at the continue statement. 4.3.1 continue The continue statement causes the current iteration to cease, causing the next iteration of the loop to occur. Let's consider one scenario in which we could print out only the odd numbers in a sequence: - package main 2 impor-:( 3 II fr.,-: II 4 5 6 func main() { 7 8 for i:=O; i statement) and go to the next iteration. 96 .. At its highest level, this code says we should skip all the even numbers, and print out all the odd numbers. And, as expected, this is what we see in the output of the program. 4.3.2 break The break statement causes the flow of execution to break out of the innermost construct (such as a for or switch statement). We've seen examples of how break works, earlier in this chapter. Consider the code we saw earlier: pacj{age r:-.a.:..n; 2 import( 3 "frnt" 4 5 6 fun c rna in ( ) { 7 8 var i int = 5; 9 10 for ; ; { :1 i = i -1; 12 fnt. Printf (":he var.:..ab:.e i is now %d\n", i); :3 .:..f :.. < 0 { 14 break; 15 } :6 :!lend for :7 Here, line 14 causes us to break out of the innermost structure (in this case, a for loop). Note that the for loop would be infinite if we did not provide the break to allow a way out. 4.3.3 Labels Labels are an interesting feature that you should at least be aware of, even though, like goto statements (which we will 97 not cover in this book) can lead to poor program design if not used very carefully. We could consider an example in which we have embedded for loops: 1 package main 2 import( 3 "fmt" 4 5 6 func main() { 7 8 myLabel: for i:=O; i < 5; i++{ 9 for j :=0; j < 5; j++{ 10 if j == 4{ 11 continue myLabel; 12 13 fmt.Printf("i is : %d, and j is %d\n", i, j); 14 } 15 }//outer for 16 And the output: johnbaugh@ubuntu:-/Desktop/go_programming/labels$ ./6.out i is 0, and j is 0 i is 0, and j is 1 i is 0, and j is 2 i is 0, and j is 3 i is 1, and j is 0 i is 1, and j is 1 i is 1, and j is 2 i is 1, and j is 3 i is 2, and j is 0 i is 2, and j is 1 i is 2, and j is 2 i is 2, and j is 3 i is 3, and j is 0 i is 3, and j is 1 i is 3, and j is 2 i is 3, and j is 3 i is 4, and j is 0 i is 4, and j is 1 i is 4, and j is 2 i is 4, and j is 3 98 As we can see from the output, even though j does equal 4 at different points in the code (five times to be exact), "j is : 4" is never printed out. This is due to lines 10-12. Notice that if j is ever equal to 4, we continue to the label myLabel, which points to the header of the outer for loop, which starts i at its next value, causing the j in the inner for loop to reset to 0 (at its initialization). Note that you can also use a break statement with a label, which essentially does the same thing as continue with a label. Both are a means by which we jump to a different spot in the code, breaking the current flow of execution. 4.4 Functions One of the most powerful constructs in Go, and arguably most other high level languages, is the function. Functions allow us to break a large problem into smaller tasks. These functions are called, or invoked and perform the tasks specified by a program. If you are familiar with functions already, most of this section will be straightforward. However, just like with many other constructs in Go, the syntax is often very different from what you might be accustomed to. Additionally, Go has a very interesting (and incredibly helpful) approach to allowing us to retrieve multiple return values from the function. The way functions are treated in Go is one of the most interesting treatments of familiar programming constructs that this modem programming language has to offer. 99 4.4.1 Single Return Value Some functions only return a single value. In this case, the format of the function is as follows: func Identifier(parameter_list) return_type{ The keyword func introduces the header to a function, just like var introduces a variable. The parameter_list will consist of parameters, separated by commas, each in the form: paramidentifier dataType The return type will be the data type of the value that the function returns. As usual, it is most helpful to look at a concrete example in order to get a better grasp of this concept. 1 package main 2 import( 3 "fmt" 4 5 6 func main() { 7 8 fmt.Printf("Multiple 2 * 5 * 6 = %d\n", Xultiply3Nums(2,5,6)); 9 } 10 11 func Multiply3Nums(a int, b int, c int) int{ 12 return a * b * c; 13 And the output of the above program is: 100 johnbaugh@ubun~u:-/Des~~op/go prograrrning/func:s ./6.ou~ v.u:tip:e 2 * 5 * 6 = 60 - For those unfamiliar with functions, the idea is that we can take a task, such as multiplying three numbers, and give this task (or set of tasks) a name. That way, if we wanted to, we could call this function several times, and not have to rewrite the code in each scenario. The task of multiplying three numbers is fairly trivial, and the function Mul tiply3Nums () serves to simply educate us on how functions work. On line 11, we have the function header, and on lines 11-13, we have the body of the function. The header of the function starts with the keyword func, then the identifier of the function, Mul tiply3Nums and then the parameter list in parentheses, (a int, b int, c in t) . Finally, the last thing in the header (before the open curly brace, starting the body of the function) is the return type ofthe function, which in this case, is int. In the body of the function, we have the value of the three parameters multiplied by one another being returned. Noticed, another way to do this would have been to declare a separate variable, named say, total. Then, we would set that variable to the value of the three parameters multiplied by one another, and then return total, thusly: total = a * b * c; return total; However, this takes an extra variable, adds unnecessary code, and doesn't really add to the readability of the program. 101 In line 8 of our program is the actual function invocation (also called the function call). While we defined our function on lines 11-13, the function doesn't do anything until we call it from another function, which in this case is the main () function. On line 8, we call the function from within the Printf () statement. For readability or stylistic reasons, you could choose to separate the function call from the printing of the value by creating a separate variable, such as the following: var myValue int = Multiply3Nums(2,5,6)); fmt.Printf("Multiple 2 * 5 * 6 = %d\n", myValue); But again, I chose not to use a separate variable to store this value, since we are using it immediately, in the Printf () function. We will see more practical and complicated examples of functions as we explore the rest of this section. 4.4.2 Multiple Return Values One of the more interesting features of functions in Go is that they can return multiple values. A function that returns multiple values is of the form: func functionName(param list) (return_value_list) { II function body here The func keyword begins a function definition, as before. Then, the function identifier functionName is after the func keyword. Then, as usual, the parameter list pararn_list is inside parentheses. Finally, the primary difference with the multiple return value functions is with the return_value_list inside a set of parentheses. With single return value functions, the parentheses are not 102 present and only one return type is specified. In this case, multiple return value types can be specified. Let's consider a program that contains a function to return multiple values: paci function are made. In this case, notice that the list of return values has identifiers for the return values. And, inside the body of the function, these values are set as if they were local variables, and returned by name. Empty Return Another useful form of the multiple return value function is a function with an empty return. func getTwiceAndThrice(input int) (twoTimes int,threeTimes in~) { ~woTimes = inpu~ * 2; ~hreeTimes = inpu~ * 3; return; Notice that the return statement is by itself. This will return the value of the result parameters when the return statement is made. In other words, whatever the value of the result parameters when the return statement is made will be the values returned by the function. 105 4.4.3 The defer Keyword A helpful keyword that Go makes available to us is defer. This keyword, as the name suggests, allows us to defer the execution of a function until the end of the enclosing function. Like many programming concepts, this is most easily explained using an example: 1 package main 2 import ( 3 "fmt" 4 5 6 func main() { 7 8 SomeFunction(); 9 10 11 func DeferredFunc(){ 12 fmt. Printf ("I was deferred until the end of my calling function\n"); 13 } 14 15 func SomeFunction() { 16 fmt.Printf("I'm in SomeFunction() at the top\n"); 17 defer DeferredFunc(); 18 fmt.Printf("I'm now at the bottom of SorneFunction()\n"); 19 } And the output of the program is: johnbaugh@ubuntu:-/Desktop/go programming/deferS ./6.out I'm in SomeFunction() at the top I'm now at the bottom of SomeFunction() I was deferred until the end of my calling function In the main function, on line 8, we call SomeFunction (). Since SomeFunction () has no return values, we do not have to capture any values. It is a special type of function, just like main<), that performs a set of tasks and simply 106 exits when it completes. This is analogous to void functions in languages like C/C++. The first function we define after main () is DeferedFunc () , which, like SomeFunction () does not return a value. On line 12, all it does is print some information before exiting. Lines 15-19 make up the body of SomeFunction (). On line 16, we print a statement indicating that we're at the top of Some Function ( >. Then, on line 17, we call defer on our DeferedFunc () function, and finally on line 18, we print another statement, indicating we're at the bottom of SorneFunction (}. Pay careful attention to the output of the program. Notice that the print statement in our deferred function is not performed until after the two print statements that are present in Some Function ( >, even though it was called in between these two print statements. Remove the defer statement, and the order will be different. The defer keyword allows us to ensure that certain tasks are performed before we return from a function. It can be helpful to keep the code itself clean, as well as to ensure certain tasks such as closing a file stream. 4.4.4 The Blank Identifier In this subsection, we learn about the blank identifier, which is denoted by an underscore, . The blank identifier is helpful when we have a multiple return value function, but do not want to store all of the values it returns. 107 In Go, it is a syntax error to not capture all of the return values of a function. If we capture a value, we must then use it, and this gets clumsy and makes the code messy with variables we don't really intend on using. So, the blank identifier comes to the rescue. Consider the following: 1 package main 2 impor~( 3 "fmt" 4 5 6 func main () { 7 var myinteger int; 8 var myFloat float; 9 10 myinteger,_, myFloat = ThreeValues(); 11 12 fmt.Printf("My int : %d, my float : %f\n", myinteger, myFloat); 13 } 14 15 func ThreeValues () (int, int, float) { 16 return 5, 6, 7.5; 17 And the output is: johnbaugh@ubuntu:-/Desktop/go_programming/blankidentifierS ./6.out My int : 5, my float : 7.500000 In this program, we have defined a trivial function named ThreeValues () in order to demonstrate how the blank identifier works. The function returns the values 5, 6, and 7.5. In our main () function, we are simply wishing to capture the first in t and the f 1 oat value, ignoring the integer return value in the middle (in our case, 6). So, on line 10, we use our declared myinteger to capture the first integer return value, then we use the blank 108 identifier to capture and drop the second integer return value, and then we capture the third return value, which is a float, in our myFloat variable. 4.4.5 Example : Minimum I Maximum Function To further enhance our understanding of functions, I will give a simple example of a function that returns the minimum and maximum of two integers. The code: 1 paci function is quite simple. It just assigns the minimum and maximum value and returns them in the order of maximum, then minimum. 4.4 Summary In this chapter, we've explored control structures and functions. We learned about conditional (also known as branching) control structures, such as if-else and switch structures. We also learned about the only iterative (also known as looping) control structure, the for structure. Furthermore, we learned about functions, and how to use them to break a larger problem into smaller, more manageable sub-problems. Functions allow us to perform a task or set of tasks over and over again (if we wish), without having to write the same code over and over again. In Go, we can have functions that return no value, one value, or multiple values. 110 Chapter 5 More Data Types In this chapter, we will explore some more of the data types that Go has to offer. We will look at several composite structures available in Go, including arrays, slices, and maps. We will begin with arrays, commonly available structures that are used in many programming languages for storing homogenous groups of data. However, we will see that arrays in Go are significantly different from arrays in many other high level languages. Additionally, we will explore the slice structure, which is a reference to a section of an array, and more accurately approximates what readers from other programming languages expect from an array. Also, we will explore maps, which are associate arrays. Inherent to the discussion of these data types is understanding pointers, which we will investigate first. 5.1 Pointers and References Computers store values in memory, and each memory block has an address. A pointer is a special data type in 111 which the memory address of another value is stored. Thus, a pointer points to the location of another value. Related directly to pointers is the address-of operator, denoted by the ampersand,&, which is used to determine the address of a particular piece of data. Let's consider the following example: 1 package main 2 import ( 3 "fmt" 4 5 6 func main() { 7 var someint int = 5; 8 9 fmt.Printf("My integer: %d. Its location in memory: %p\n", someint, &someint); 10 } And the output is: johnbaugh@ubuntu:-/Desktop/go_programming/chap5/pointers$ . /6.out My integer: 5. Its location in memory: Ox7F6927B25010 On line 7, we simply declare an integer variable, and set its value to 5. Line 9 is where we get to see the address-of operator in action. As we see from the output, the first piece of data we print out is 5, which is contained in our variable, some Int. Next, we print out the value Ox7F6927B25010, which is the address returned by &some Int. Note that this value is hexadecimal (base-16) and that the actual address value of the memory location is after the Ox part. Ox denotes that the number following is in base-16. Also note that this value will change when you run the program more than once. This is because the memory 112 location assigned to the variable changes with distinct executions of the program. What if we wanted to store the address of a piece of data into a variable, rather than just use it directly like we did in the code above? No problem. That's where pointers come in. To declare a pointer, we put an asterisk, *,in front of the data type we are pointing to. Let's consider the following example, which is a modification of the above code: pac: returns the length of the slice itself (how many elements are in the slice), whereas the cap ( > function returns the sum of the length of the slice and the number of elements from the end of the slice, to the end of the underlying array. Basic Usage Let's consider a basic scenario where a slice is used: 1 package main 2 import( 3 "fmt" 4 5 6 func main() { 7 var myArray [6)int; 8 var mySlice [)int = myArray[2:5]; 9 10 //load the array 11 for i:=O; i function takes two parameters. The first, [ 1 int in our case, is the data type that is to be created. The second parameter, the number 10 in our example, indicates that number of items to be in the slice. This code creates an array, and then automatically creates a slice referencing the array. The remainder of the program 122 is fairly self-explanatory. Lines 9-11 are responsible for filling the slice (or the underlying array, to be exact) with values. And then, on lines 13-15, the values are printed. Reslicing Another useful form of the make () function involves not two, but three parameters. The basic format of this version of make () is: rnake(data_type, starting_length, capacity) The parameter data_type is (not surprisingly) the data type of the array in question (such as [ 1 int). The starting_ length is the beginning length of the slice, and the capacity is the entire length of the underlying array. Since slices have the ability to grow within the limits of the underlying array, it may be useful to create a slice that is smaller than the underlying array, but can also grow as needed when elements are added. In the following example, we can see how resizing of the slice is done: 1 package rr.ain 2 import( 3 "frr.t" 4 5 6 func rr.ain () { 7 var slice: :Jint = make([:int, 0, 10); 8 var numElements int = 0; 9 10 for i:=O; i < cap(slice1); i++{ 11 slice1 = slice1[0:numElements+1]; //reslice 12 slice1:i: = i; 13 numElements = nurr.E:ements + 1; 14 fmt.Printf("Length of slice is : %d\n", len(s:ice:)); 123 15 } I /end for i 16 17 for j:=O; j < len(slicel); j++{ 18 fmt. Printf ("Slice at : %d is %d\n", j, slicel [j]); 19 } I I end for j 20 21 Here is the output: johnbaugh@ubuntu:-/Desktop/go_programming/chap5/resize_slic e$ ./6.out Length of slice is 1 Length of slice is 2 Length of slice is 3 Length of slice is 4 Length of slice is 5 Length of slice is 6 Length of slice is 7 Length of slice is 8 Length of slice is 9 Length of slice is 10 Slice at 0 is 0 Slice at 1 is 1 Slice at 2 is 2 Slice at 3 is 3 Slice at 4 is 4 Slice at 5 is 5 Slice at 6 is 6 Slice at 7 is 7 Slice at 8 is 8 Slice at 9 is 9 On line 7 of the code, we make an integer slice starting with zero length, but with an underlying array that has a capacity of 10. On line 8, we declare numElements, which will serve as the counter of the number of elements in the slice (note that we could creatively use the variable i for this purpose, but I want to make it more clear what is going on in the code). Lines 1 0-15 contain the loop that fills the slice with data. Line 11 is of particular interest, however, because we "reslice" the slice, adding one to its length. The loop itself prevents us from going out of bounds on the underlying capacity, so we don't need to check for this. Note in the 124 output, that the length of the slice keeps increasing with each iteration of the loop. On lines 1 7-19, we have the loop that allows us to print the contents of the slice. Note for this loop, we can use the len () function for the loop condition. As a final note about slices, the growing ability of a slice would be more useful, not in a strictly iterating structure like a loop, but if it were for the purpose of retrieving input from say, the user. It would be a typical case that we would have no knowledge of exactly how many integers the user was going to enter. As long as it was within the capacity, the user could enter 0 or more data elements. 5.3 Maps Maps in Go are essentially associative arrays. They are similar to *map in C++ or the diet (dictionary) type in Python. With maps, you can associate (hence why they're called associative arrays) a key with a value, called the data or just the value. Maps are an alternative to strict arrays or slices, which use integers as indices. Instead, you can use a value of any data type for the index for which == and ! = are defined to find a particular piece of data. For example, the data type string can be used as the key type since == and ! =are defined for strings. Let's consider an example: 2 3 4 package main import( "frr.t" 125 5 6 func main () { 7 var myMapLiteral map[s~ring] int; 8 var myMapCreated map[string] float; 9 var myMapAssigned map[string] int; 10 11 myMapLiteral ~ map[string] int { "one":1, "two":2 }; 12 myMapCreated ~ make(map[string]float); 13 myMapAssigned = myMapLiteral; 14 15 myMapCreated["chicken"J = 4.5; 16 myMapCreated["pi"J = 3.14159; 17 18 fmt.Printf("Map Literal at \"one\" is %d\n", myMapLiteral["one"J); 19 20 fmt.Printf("Map Created at \"chicken\" is %f\n", myMapCreated["chicken"]); 21 22 fmt.Printf("Map Assigned at \"two\" is %d\n", 23 24 25 myMapAssigned["two"]); And the output is: johnbaugh@ubuntu:-/Desktop/go_programming/chap5/maps$ ./6.o ut Map Literal at "one" is : 1 Map Created at "chicken" is 4.500000 Map Assigned at "two" is : 2 This example shows three different ways of creating an instance of a map type. On line 7, we declare a map, myMapLi teral that is indexed with keys of type string, and contains ints. This map is populated with literals on line 11, using key-value pairs inside curly braces. On line 8, we declare a map called myMapCrea ted which has keys of type string and contains float values. We create and populate this map in a slightly different way. We actually create a map on line 12 with the make () function. 126 It is interesting to note that maps automatically grow to accommodate any key-values that are added. You can, however, optionally make () the map with a starting capacity: make(map[string] float, 50) would create a map that uses strings as the key and floats as the value type, with an initial capacity of 50. As mentioned, this will not limit the size of the map. When the number of values reaches 50, the next key-value added will cause the map to increase its size by 1 automatically. Line 9 creates a map named myMapAssigned, which is assigned to refer to myMapLiteral on line 13. Since myMapAssigned now refers to the same map as myMapLi teral, we can use it as if it were myMapLi teral, by using the same indices. For the map that was created, we assign it values on lines 15 and 16. We can see how maps work from the output, which is performed on lines 18-22. As mentioned above, pay close attention to line 22. We can use myMapAssigned ["two"] because myMapAssigned refers to the same map as myMapLi teral, which has an index named "two". Testing for Existence of an Element Sometimes, we want to know if an item exists in a map. In order to do this, we use the following so-called comma ok form to test for the existence of an element: value, isPresent = someMap[key] 127 The variable value will contain the value at the key in question if it exists. If the key is not present in the map, then the value returned will be the zero-value for that particular data type (0 forint, 0.0 for float, etc.) The variable is Present will always be of type boolean and is true if the key is present in the map, or false if it is not present. Deleting an Element It is a common task to delete an element from a map. Essentially, we use the comma ok form in reverse: someMap[key] = value, delete In the above expression, value can be essentially anything, and delete is a boolean value that you should set to false if you wish to delete a value of a particular key. As an example of deleting elements, and testing for the existence of particular key-value pairs, let's consider the following code: 1 package main 2 import( 3 "fmt" 4 5 6 func main () { 7 var myMap map[string]int; 8 var value int; 9 var isPresent bool; 10 11 myMap = make(map[string]int); 12 13 myMap["horses"] =55; 14 myMap["cows"] = 20; 15 myMap ["pigs"] = 25; 16 128 17 va:ue, isPresen: = my~ap~"horses";; 18 frnt.Printf(":s \"horses\" in rnyMap? %t\n",isPresent); 19 frn:.Printf("Value is : %d\n", value); 20 21 value, isPresent = myMap["chicken"]; 22 frnt.Printf("Is \"chicken\" in rnyMap? %t\n", isPresen:); 23 frnt.Prin:f("Value is : %d\n", value); 24 25 //delete an ite~ 26 rnyMap["horses"] = O,fa:se; 27 value, isPresent = myMap["horses":; 28 fr..t..Prin:f(":s \"horses\" in r..yV.ap? %:\n", isPresen:); 29 And the output is: johnbaugh@ubun:u:-/Des~:op/go programning/chap5/exist map$ ./6.out - - :s "horses" in myMap? : true Value is : 55 Is "chicken" in myMap? : false Value is : 0 Is "horses" in rnyMap? : fa!se Lines 7 - 15 contain the creation (using rna ke ( ) ) and the initialization of our map, myMap. Line 17 tests for the existence of the key "horses". Since this is in myMap, we see that isPresent is assigned the value true, and value is assigned the value 55. These are printed on lines 18-19. Later, on lines 21-23, we test the existence of the key "chicken". Since "chicken" does not exist as a key, isPresent is assigned the value false, and value is assigned the value 0 (since 0 is the zero-value for integers). Finally, we delete the value at key "horses" on line 26, by using the comma ok form with any value as the first value and false as the second. On line 27, we test for the existence of the key "horses", which is now false (since we deleted it). As you can see, the output reflects this. 129 5.4 Using range with for Loops So far in this book, we have explored various composite types such as arrays, slices and maps. We also have done some work with strings. Up to this point, we have only seen basic for loops, but there is another form of the for loop that is perfectly suited for strings and composite types (and channels, but we haven't explored these yet in this book). This form of the for loop is based on the range of elements in the composite type (or characters in the string). Let's consider an example using maps: 1 package main 2 import( 3 "fmt" 4 5 6 func main() { 7 var myMap map[int] float; 8 var key int; 9 var value float; 10 11 myMap = make(map[int]float); 12 myMap[1] 1.0; 13 myMap[2] 2.0; 14 myMap[3] = 3.0; 15 myMap[4] = 4.0; 16 17 for key,value = range myMap { 18 fmt.Printf("key is: %d\n", key); 19 fmt.Printf("value is : %f\n\n", value); 20 }//end for 21 130 And the output is: johnbaugh@ubuntu:-/Desktop/go_programming/chap5/for_r ange$ ./6.out key is : 4 value is 4.000000 key is : 1 value is 1.000000 key is : 2 value is 2.000000 key is : 3 value is 3.000000 In this program, we declare a map on line 7 called myMap. The keys in this map are ints, and the values are floats. We create the map using make() on line 11. Then, we populate the indices 1 through 4 with values on lines 12- 15. On lines 17 - 20, we use a for loop with the range form to iterate over items in the map. With this form, you must have at least the key, and optionally you may capture the value (which we have in this example). However, the output is interesting. Notice that the key at index 4 is printed out first. With maps, this order could be anything, because by definition, maps are unordered. The situation is quite different if we were to use an ordered composite type, such as a slice. Let's see what happens when we iterate over a slice using the range form of the for loop: 1 package main 2 import( 131 3 "frnt" 4 5 6 func main() { 7 var rnySlice []int = rnake([]int, 4); 8 9 rnySlice[O] = 15; 10 rnySlice[1] = 20; 11 rnySlice[2] = 25; 12 rnySlice[3] = 30; 13 14 for key,value := range rnySlice 15 frnt.Printf("Slice at %d is %d\n", key, value); 16 } 17 And the output is: johnbaugh@ubuntu:-/Desktop/go programming/chapS/for r ange$ ./6.out - - Slice at 0 is 15 Slice at 1 is 20 Slice at 2 is 25 Slice at 3 is 30 Here, we use essentially the same syntax as with maps. But, as you can see from the output, the data in the slice is in the order we added it to the slice. This is because arrays and slices are ordered composite types. Note also on line 14, our usage of the idiomatic (quick and easy) : = without having declared key or value previously. 5.5 Simulating Enumerated Types with iota Many readers may be wondering if Go supports anything like the enum type of languages like C++. Go does not technically have an enumerated type, but this can be simulated by the iota identifier. The iota identifier is used in const blocks. It resets to 0 any time the keyword 132 cons t is encountered, and increments any time a semicolon is encountered. It therefore represents integer constants in succession. An example of iota in practice is: 1 package main 2 import( 3 nfmtn 4 5 6 func main() { 7 const( 8 first = iota; 9 second = iota; 10 third = iota; 11 12 13 fmt.Printf(nfirst : %d\nn, first); 14 fmt.Printf(nsecond: %d\nn, second); 15 fmt.Printf(nthird: %d\nn, third); 16 The above code has the output: johnbaugh@ubuntu:~/Desktop/go_programming/chap5/iota$ . /6 .out first : 0 second: 1 third: 2 In the code, notice the cons t block on lines 7-11. Each successive usage of iota will increment by 1. This is why the output starts at 0 (since iota is set to 0 at the beginning of the cons t block), and ends at 2. We do not have to explicitly include the semicolon at the end of each statement, as Go automatically inserts one. In fact, many Go programmer prefer this way: 7 const( 8 first iota 133 9 second 10 third 11 With this syntax, we leave out the semicolons. Additionally, notice we do not have to keep using= iota after every canst identifier. Go will automatically use the last assignment if none is explicitly written. Note that if we wanted to start our enumeration at say, 50, we could use iota+SO with the initialization ofthe elements. 5.6 Summary In this chapter, we enhanced our knowledge of Go by exploring more of the data types that the language has to offer. We began the chapter discussing reference types and pointers, and learned how they represent memory addresses of other types. We also focused on the composite types, including arrays, slices and maps. We learned that slices exist on top of an underlying array. Slices grow as needed, but we can also use make (} to give them an initial capacity. The map type represents associative arrays, in which a key-value pair is used. As long as the == and ! = operators are defined for a particular type, we can use that type as the key. We explored how we can use range with for loops to iterate over the composite types learned about in this chapter. Finally, we briefly learned how to simulate enumerated types with the iota identifier. 134 Chapter 6 Structured Types, Packages and Interfaces In this chapter, we will look at structured types, interface types, interfaces, the methods to implement interfaces, and the values that are possible for these special types. Go has a somewhat unusual and novel approach to object awareness, and the unique concepts involving interfaces, interface types, and interface values. 6.1 Structured Types In this section, we'll explore the struct keyword, and how we can create specialized groups of information, called struct types, or structured types. It is often helpful to group pieces of data together, and to be able to access that data as if it were part of a single entity. Structured types help us do that. 6. 1. 1 Named Fields and Anonymous Fields 135 Structured types contain.fie/ds, which are the component pieces of data that constitute the structured type. Let's consider an example: 1 package main 2 import( 3 "fmt" 4 5 6 type rnyStruct struct{ 7 sorneinteger int; 8 sorneFloat float; 9 sorneString string; 10 :1 12 func main () { 13 var rns *rnyStruct = new(rnyStruct); 14 15 rns.someinteger = 10; 16 rns.someFloat = 15.5; 17 rns.someString = "John"; 18 19 fmt.Printf("My intis : %d\n", ms.someinteger); 20 fmt.Printf("My float is : %f\n", rns.someFloat); 21 fmt.Printf("My string is : %s\n", rns.someString); 22 23 And the output is: johnbaugh@ubuntu:-/Desktop/go_prograrnrning/chap6S ./6.out My int is : 10 My float is : 15.500000 My string is : John On lines 6-9, we declare and define our structured type, rnyStruct. This structure has three fields, some Integer, someFloat, and sorneString. This means that whenever we create an instance of this structured type, the instance will have three constituent components with those names. On line 13, we declare a variable, rns, that is a pointer to an instance of mystruct. This is why we must put the symbol 136 *in front of the data type of the structure. Notice that we then allocate the memory for the structure with the new (} function. At this point, the three fields contain the zero values for their respective types. In other words, the int contains a 0, the float, a 0.0, and the string, an empty string,"". We can now populate the fields using the name of the variable (ms) and the dot operator (. ), followed by the name of the field we wish to assign a value to. For those coming from the C/C++ world, this may seem peculiar. In C/C++, you must use the class member access operator (->)to access the members of an object being pointed to. Thus, in C/C++, objName->fieldName is equivalent to (*objName}. fieldName. There is no such class member access operator in Go. The indirection is performed automatically with the dot operator. Thus, we are able to populate ms on lines 15-17. On lines 19-21, we print the data stored in ms to the console. Another useful feature of structures in Go is that they can have anonymous fields. The fields in the above example are specifically called named fields, because, well, the fields have names! Fields that don't have names may seem a bit peculiar at first. These unnamed fields can even be structs themselves. Although Go does not directly support inheritance in the same way that a language like C++ or Java does, anonymous fields allow for the embedding of the members of the inner struct into the outer struct. 137 Let's consider some code: 1 package main 2 import ( 3 "fmt" 4 5 6 //inner struct 7 type innerStruct struct{ 8 innerint int; 9 innerint2 int; 10 11 12 //outer s~ruct 13 type outerStruct struc~{ 14 b int; 15 c float; 16 int; //anonymous field 17 innerS~ruct; //anonymous field 18 19 20 func main () { 21 22 23 var outer *outerStruct 24 outer.innerint = 5; 25 outer.innerint2 = 10; 26 outer.b = 6; 27 outer.c ~ 7.5; 28 outer.int = 60; 29 new(outerStruct); 30 fmt. Printf ("outer. innerint = %d\n", outer. innerint); 31 fmt.Printf("outer.innerint2 = %d\n", outer.innerint2); 32 fmt.Printf("outer.b = %d\n", outer.b); 33 fmt.Printf("outer.c = %f\n", outer.c); 34 fmt.Printf("outer.int ~ %d\n", outer.int); 35 And here is the output: johnbaugh@ubuntu:~/Desktop/go_programming/chap6/anonymous_f ieldsS ./6.out outer.innerint = 5 outer.innerint2 = 10 outer.b = 6 outer.c = 7.500000 outer.int = 60 138 Here, on lines 7-10, we declare and define a structured type called innerStruct. Note that it has two integer fields, innerint and innerint2. Later on in the program, we declare and define a second structure on lines 13-18, called outerStruct. This structure has named fields, an integer called b and a floating point type called c. After these, we then see a couple peculiar things that we haven't dealt with before now. On line 16, we have just a data type listed, int. This is an anonymous field. There is no identifier. In fact, when we want to store data in this field or access the data, we simply use the name of the data type. Notice also there would only be the ability to have one anonymous field of each data type. In other words, for example, we could not have two anonymous fields named in t. This would be a naming conflict. On line 1 7, notice that we use another anonymous field. This time, it is the name of a type we've defined elsewhere in the file. Namely, we've embedded innerStruct into outerStruct. What this does is simply causes any fields of innerStruct to be directly accessible from instances of outerStruct. The majority of the rest of the program is fairly self explanatory, with a couple notes. On line 22 we create the instance of outerStruct called outer. On lines 24-28, we populate the members of outerStruct. Notice the first two, innerint and innerint2 that come from the innerStruct. They are accessed directly from outer rather than having to go through another layer of hierarchy, as would likely be the case in another language. In other words: outer.innerStruct.innerint 139 would be incorrect. Instead, we need to simply use: outer.innerint to access or assign values. Lines 30-34 print the values that we stored in the structured type instance, which we can observe from the output. 6.1.2 Methods Methods in Go have much the same syntax as regular functions, except that they have a receiver. The general signature for a method is: func (receiver) FuncName(params) returnType{ II .. body } The receiver is specified in parentheses before the name of the method. A receiver is the type that the method acts upon. The best way to understand this is to see an example of how methods can act upon a structured type. 1 package main 2 import( 3 "frnt" 4 5 6 type TwoNums struct{ 7 a int; 8 b int; 9 10 11 func main () { 12 var myTn *TwoNums; //ptr ~o instance 13 myTn a new(TwoNurns); 14 rnyTn.a = 12; 15 rnyTn.b = 10; 140 :6 :7 fm~.Prin~f("!he sum is : %d\n", my!n.Add!hen()); :e fm~.Prin~f("Add ~hem ~o ~he param : %d\n", 19 20 rny!n.AddToParam(20)); 2: func (~n *TwoKums) Add!hem() in~{ 22 re~urn ~n.a - ~n.b; 23 24 25 func (~n *!woKums) Add!oParam(param in~) in~{ 26 re~urn ~n.a ~ ~n.b + param; 27 28 And the output is : johnbaugh@ubun~u:-/Desk~op/go_prograr.~ing/chap6/me~hodsS ./ 6.ou~ The sum is : 22 Add them to ~he param : 42 Here, we declare a structured type named TwoNurns. This structure contains two integer fields, a and b. On lines 12- 15, we declare and assign values to the fields of our struct instance, rnyTn. Now, if you look a little farther down in the file, you will notice two different methods defined on lines 21-27. The first method, called Add Them () states its receiver as a TwoNurns struct. Although we could have just used TwoNurns directly, it is more efficient to use pointers, which is why the data type of tn is *TwoNums. TwoNurns is said to be the receiver base type, or just base type. It is important to note that the receiver base type must be declared within the same package and cannot be a pointer or interface type. The method Add Them () takes the fields of a TwoNurns structure, adds them, and returns them. Notice how they 141 are accessed using the name of the identifier of the receiver. Go does not have an implicit this pointer available that languages like Java have. Therefore, you must give the receiver an explicit name. In our case, it is called tn. The second method, AddToParam not only adds the fields of the structures, but also adds an additional parameter and returns the sum of all three. Now, we need to jump up to lines 17 and 18 to see how the methods are invoked. We take our instance of the structure that we named myTn and simply use the dot operator to invoke the methods, just as if they were fields. This is different from what you might be familiar with if you're coming from a language like C++ or Java. Go does not have classes, and the methods (also called member functions in C++) in Go are not inside of the structure. The association between method and type is established by the receiver. 6.2 Custom Packages and Visibility In this book, we've used some packages that are available as part of Go's libraries. But you can create your own custom packages, too. For a package that Go provides, you have simply used code such as: import{ "fmt" 142 But, if we make a custom package, we can explicitly describe where the package is located, such as: import( "./packl/packl" In the above case, the packl package is available in a directory inside the same directory in which the importing source file resides. In other words, if we have one file called packageTest. go that imports packl, we would have a packl directory inside the same directory as packageTest. go. And, as another note, we must resolve the name using the dot operator, just like with the built-in Go libraries. For example, we use fmt. Printf () to resolve the Printf () function available from the fmt package. Similarly, we would use packl. FunctionName () to import a function from packl. 6.2.1 Visibility The frrst topic we must consider when dealing with packages is visibility. Visibility refers to the ability of a function, method, or data to be accessed from outside a package. This is similar to the concept of public and private data members and member functions/methods in languages like C++ and Java. When an identifier is available outside of a package, it is said to be exported. Go has a very unique approach to indicate whether data or functions are exported or not. Instead of a particular keyword, Go uses the case of the identifier. 143 If an identifier in a package is uppercase, then the identifier is exported, and therefore available outside of the package. If the identifier is lowercase, it is not exported. Let's look at an example. Keep in mind that this time we have two different source files to consider. One will be the package we are creating, and the other is the driver program, which means the program that utilizes the package. Firstly, we must create a package. While not entirely necessary, it is good to make a directory to put the package source file in (which will be compiled into an object file). In my case, I call the directory the same name as the package, namely, packl. Here is the code for the package. Note the location given is not part of the code: (Location : packl/packl.qo) 1 package packl 2 3 var MyPacklint int = 15; 4 5 func ReturnNum() int{ 6 return 5; 7 The package code is fairly self-explanatory. The only thing that is really different is on line 1. Notice the package name is not main. Instead, we named this package packl. This is the name that will be imported in the driver program. Now, here is the code for the driver program: (Location : packageTest.go) 144 1 2 package rna in 3 4 inport ( 5 "frnt" 6 ". /packl/packl" 7 8 9 func r..a:.n () { 10 var ~es~: :.n~; 12 testl = pack1.Re:urnNurn(); 13 frnt.Printf("Hi there\n"); 14 frnt.Printf("Nurn : %d\n", ~estl); 15 16 fn:.Printf("Nun 2 : %d\n", pack1.MyPacklint); :7 Here is the output: johnbaugh@ubuntu:-/Desktop/go_programrr.ing/chap6/packages$ /6.out Hi there Num : 5 Nurn 2 : 15 Again, once you understand what's going on here, we can see that the code is not that difficult. Notice how the package is imported on line 6. We specify the directory and package name as we saw earlier. When we want to call the function ReturnNum (),we qualify it with the name of the package, packl and use the dot operator to qualify it as we can see on line 12. Again, we can access data from our custom package just as well as we accessed our function, just as long as the name begins with an uppercase letter. So, we can access MyPacklint on line 16. 145 Before we move on, it's important to note that you must compile the packl. go source file before you compile the packageTest. go driver source file. The driver is actually looking for packl. 6, the compiled object code. That is why it must be available, or the driver will not compile. Since we learned about structs previously, it would be important that we note you can create and utilize exported structures as well. Let's consider some more code: (Location : structPack.go) package structPack 2 3 type ExportedStruct struct{ 4 Member1 int; 5 Member2 float; 6 (Location : main.go) 1 package main 2 import( 3 "fmt" 4 "./structPack" 5 6 7 func main () { 8 var myStruct *structPack.ExportedStruct; 9 myStruct = new(structPack.ExportedStruct); 10 11 myStruct.Member1 = 10; 12 myStruct.Member2 = 16.0; 13 14 fmt. Printf ( "Member1 = %d\n", myStruct .Member1); 15 fmt.Printf("Member2 = %f\n", myStruct.Member2); 16 And the output is: johnbaugh@ubuntu:-/Desktop/go_prograrnrning/chap6/package2S . /6.out Member1 "" 10 Member2 = 16.000000 146 This example is also fairly clear if you understood the previous example. In the above example, take note that the name of the structure itself, ExportedStruct is capitalized. Also, the members that we access are also capitalized, namely, Mernberl and Member2. If there was a member that began with a lowercase character, we could not access it from outside of the package. Additionally, if we had a struct type whose name began with a lowercase letter, we could not declare an instance of the struct type itself. 6.3 Interfaces An interface defines a set of methods, called the method set. Interfaces are pure and abstract. This means that they don't have implementations or data fields. Another concept is that of interface types. Interface types are any type that implements the interface. This means that the type has methods that are a subset (proper or not) of the interface. As long as the type has methods that are at least the methods in the method set of the interface, then this qualifies that type as implementing the interface. Finally, an interface value is an actual value with its type being the interface type. It will be more clear once we examine some examples, but for now, we must establish that multiple types could implement the same interface. An interface type can point to an instance of any of the types that implements the interface. This allows for great flexibility. 147 As with most topics in this book, examples are the best way to give us a stronger grasp on the concepts of an interface. Let's see some code; 1 package main 2 import( 3 "fmt" 4 5 6 type Square struct{ 7 sideLength int; 8 9 10 type Triangle struct{ 11 base int; 12 height int; 13 14 15 type Areainterface interface{ 16 Area() float; 17 18 19 func main () { 20 var mySquare *Square; 21 var myTriangle *Triangle; 22 var areaint Areainterface; 23 24 mySquare = new(Square); 25 myTriangle = new(Triangle); 26 27 mySquare.sideLength = 5; 28 myTriangle.base = 3; 29 myTriangle.height = 5; 30 31 areaint = mySquare; 32 fmt.Printf("The square has area areaint.Area()); 33 34 areaint = myTriangle; 35 fmt.Printf("The triangle has area 36 37 38 areaint.Area()); %f\n", %f\n", 39 II Square implements the Areainterface interface 40 func (sq *Square) Area() float{ 41 return float(sq.sideLength * sq.sideLength); 42 43 148 44 II Triangle implements the Areain:erface interface 45 func (tr *Triangle) Area() float{ 46 return 0.5 * f:oat(tr.base * tr.heigh:); 47 And here's the output: johnbaugh@ubuntu:-IDesktoplgo programminglchap6linterfacel$ .16.out - The square has area : 25.000000 The triangle has area : 7.500000 This program has a lot going on. It combines a lot of the things we've learned throughout the book, as well as interface types, which we just learned in this section. In fact, it is one of the largest programs we've looked at in this book thus far. On lines 6 - 13, we declare two different structure types, Square and Triangle. Square has a sideLength field, which is the measurement of a square's side. In Triangle, there are two fields, base and height, indicating the length of the base and the height of the triangle. In other words, we have the data that is required to determine the area of either a Square or a Triangle. On lines 15 - 17, we declare our interface type, Area Interface. The interface has only one method, Area () that returns a float. In other for a type to implement this interface, it must only implement the Area() method. On lines 20- 22, we declare our Square and Triangle structure types, as well as our Areainterface type. On lines 24 and 25, we actually create our Square and Triangle, mySquare and rnyTriangle, respectively using new (). And then, on lines 27-29, we set the values of the side Length of our Square and the base and height of our Triangle. 149 On lines 31 and 32 are where the really interesting and new stuff happens. On line 31, we assign the Square variable to our interface variable, area Int. This interface variable now contains a reference to the Square variable. We can then call areaint .Area(). This actually calls the Area() method of Square, which is defined on 40-42. Later, on lines 34 and 35, we store our Triangle variable into area Int. And, although we call the exact same method, Area<>, what is actually called is the Area<> method of Triangle, defined on lines 44-47. This is all possible because an interface variable can contain a reference of any type that implements its method set. Since both Square and Triangle implement the Area<> method (the only method in the method set of Areainterface), an Areainterface variable can contain either a Square or a Triangle. This is the closest thing to polymorphism that Go has. And, while areaint does contain a reference to either the Square or Triangle variable (pointer variable, to be exact), the areaint is not a pointer itself. It is a multiword data structure, containing the value of the receiver (which could be a pointer, or a variable of the type itself) and a method table pointer that points to the appropriate method. For example, when areaint refers to a Square, it maintains both the field data and value of the Square variable, as well as the appropriate Area ( > method, namely, the Square's Area() method. 6.4 Summary 150 In the first part of this chapter, we learned about structured types. We learned about named and anonymous fields of these structured types. Also, we learned about how to implement special functions, called methods, that act upon these structured types. We also learned about how to create our own custom packages. Along with packages, we learned about visibility and how the case of the first character of identifiers within a package determines whether or not the identifier can be accessed outside the package. Finally, we discussed interfaces. We learned that interfaces allow us to describe a set of methods that define the interface, and how a type must implement each of these methods in order to qualify as implementing that particular interface. We also learned that an interface type allows us to store a variable or reference to any of its implementing types. This is similar to the concept of polymorphism present in all fully object oriented languages, although not exactly the same. 151 Chapter 7 Concurrency and Communication Channels In this chapter we will discuss slightly more complicated features of Go. Go comes with built-in types and functionality to help with concurrency and communication. This chapter reveals much of the interesting characteristics of Go that make it a superb language for modem computing paradigms. 7.1 Concurrency In this section, we will discuss how Go handles concurrency. Concurrency allows programs, processes, threads, and in the case of Go, go routines (more on these later) to operate simultaneously. Sometimes these entities share resources as well, so this sharing must be coordinated properly. Go offers a lot of support for concurrency and makes resource sharing and coordination between such simultaneously executing entities. In order to understand exactly how does this, we must explore goroutines. 152 7.1.1 Goroutines Typically, when talking about concurrency (also called parallelization ), you will almost certainly hear two terms: process and thread. While these terms are often confused, and there are some individuals who split hairs over details, basically a process is an independently executing entity that runs in its own address space. Composing a process may be one or more threads, which are simultaneously executing entities that share address space. With Go, the designers wanted to avoid confusion or preconceptions (and especially, the hair-splitting over details), so they named their parallelization entities goroutines. A goroutine is essentially a thread, which shares address space with other goroutines within the same process. A goroutine is implemented as a function, and invoked (called) with the go keyword. A code sample follows: ~ package main; 2 3 irr.port ( 4 "fn-:" 5 "-:.ime .. 6 7 8 func na:.n () { 9 10 frnt.Pri..ntln("In main()"); 11 go longWai..t(); :2 go shortWai..:(); 13 frr:c.Prin-:ln ("About -:o sleep :.n nain () "); 14 ti..rne.Sleep(lO * le9); 15 fmt.Println("A: the end of main()"); 16 17 18 func longWait(){ 153 19 fmt.Println("Beginning longWait()"); 20 time.Sleep(5 * le9); //sleep for 5 seconds 5 * l,OOO,OOO,OOOns 21 fmt.Println("End of longWait()"); 22 23 24 func shortWait(){ 25 fmt.Println("Beginning shortWait()"); 26 time.Sleep(2 * le9); 27 fmt.Println("End of shortWait()"); 28 And the output is: johnbaugh@ubuntu:-/Desktop/go_prograrnrning/chap7/goroutine$ ./6.out In main () About to sleep in main() Beginning longWait() Beginning shortWait() End of shortWait() End of longWait() At the end of main() Before we discuss the goroutines themselves, notice we have included the time package. We need this in order to use the Sleep() method. Sleep() has the following signature: func Sleep(ns int64) os.Error{ //function body here } Note that we could capture an error (the return type) if we wish. The parameter is in nanoseconds, which means billionths of a second. The Sleep () method allows us to pause the current goroutine for at least a certain number of nanoseconds. In our code, notice that in both longWai t () and shortWait (),we have a multiplication by le9. What does this mean? This means 1 times 10 to the power 9, in other 154 words, a 1 followed by 9 zeroes. We do this multiplication to get seconds. Since a nanosecond is one billionth of a second, we need to multiply a nanosecond by a billion (1 ,000,000,000) to get one second. So any number we have multiplied by 1 e 9 will give us the number of seconds. On lines 11 and 12, we use the go keyword to start the goroutines. When you execute the code, you will notice that each of these functions will start independently and not end in the same order that you would expect if they were typical functions. The function shortWai t () sleeps for 2 seconds, while longWai t () sleeps for 5. I have the main function sleep for 10 to ensure that it doesn't exit before the two goroutines do. Much of the magic of this program will be evident when you run the program. I cannot, on this statically printed paper (or digitally typed data file, more accurately) accurately express the dynamics of this code. You will have to run it and see that it does in fact wait after the two "beginning" statements are printed. Note that the "End of shortWait()" string is printed before the "End of longWait()" string. This was a very basic and trivial example just so you can see what can be done with goroutines. We will explore more of their power later in this chapter. 7.2 Communication Channels Related to goroutines are communication channels, a built­ in reference type that provide mechanisms to perform 155 cross-goroutine communication. Goroutines must be able to communicate in order to send and receive information and coordinate their efforts. The keyword for a channel is chan. Since channels are reference types, you must use the rna ke ( ) function to allocate memory for them. Channels are specified with the data type that transmit. Thus, the generic form: chan data_type; declares a channel that allows for sending and receiving of data_ type data. Specifically, if I want to transmit, say, integer data, I could create a channel thusly: chan int; 7.2.1 <- The Communication Operator, We use the communication operator, <- to designate that we are transmitting data. The data is transmitted in the direction of the arrow. For example, myint = <- ch; would indicate that myint is receiving the data from a channel, ch. The data is flowing.from the channel, to the integer variable. However, ch <- someint; 156 would indicate that someint is being sent over the channel, ch. Let's look at a fairly simple, "quick and dirty" example of how to use channels. Note that this is not an excellent implementation, and that we will explore better examples later. But for now, it will get the point across without unnecessary difficulty: 1 package ~ain; 2 impor~( 3 "fm~" 4 "time" 5 6 7 func main () { 8 var ch chan string; 9 10 ch = make (chan s-cring); 11 12 go sendData (ch); 13 go getData(ch); 14 15 time.Sleep(3 * le9); 16 17 18 func sendData(ch chan string){ 19 ch <- "John"; 20 ch <- "Bob"; 21 ch <- "Sam"; 22 ch <- "Sally"; 23 ch <- "Julie"; 24 25 26 func getData(ch chan s~ring){ 27 var inpu~ string; 28 29 for { 30 input = <- ch; 31 fmt.Printf("%s\n", input); 32 33 And the output is: 157 johnbaugh@ubuntu:-/Desktop/go_programrning/chap7/namepump$ . /6.out John Bob Sam Sally Julie In this code, we declare and initialize our channel of strings, ch, on lines 8-10. We then call two functions (in this case, they are goroutines), sendData () and getData (>,passing ch to them in order to provide a mechanism for communication between them. If we skip down a little bit in the code, and look at lines 18- 24, we see the body of sendDa ta () . Since our channel is a channel that utilizes strings, we are able to send various strings across it. In this case, we've used some names. On the other side of the channel, we have our receiver goroutine on lines 26-32, called getData ( >. Notice that we have an infinite for loop that has within its body, a string variable input being set to whatever comes out of the channel, and then we print the data. The reason it will eventually break is that when the channel is closed (when the channel goes out of scope in the sendDa ta ( > function), the for loop will exit. Let's hop back up into the main function for a bit, on line 15. Note that right now, we are using a little quick and dirty trick to keep the program from terminating before the goroutines have a chance to execute. Specifically, we are sleeping for 3 seconds. This gives us enough time for the goroutines to send and receive data, respectively. If you run the code, you will notice that even after the data is done being sent and received (and in our case, printed), the program doesn't terminate until three seconds have elapsed. 158 Our above approach is a little naive and clunky. How can we make it better? Well, we must first consider what causes us to exit the rna in ( ) function in the first place. Since we call go on both of our functions (lines 12 and 13), the main () method has nothing to do except exit, unless we put the Sleep () function at the end to keep it from finishing. Why don't we just call the second function, getData () instead of causing it to execute in its own goroutine? Well, if you simply do this, you will end up with a runtime error. The system will detect a potential deadlock. Is there any way we could signal when we are done with the channel on the sendDa ta () side, and is there any way to detect that the channel is closed on the get Data () side? In fact, there are. The method close(ch); closes a channel, ch. The method closed(ch); returns true if the channel is closed, and false if it isn't. Let's look at the code in total with some of our old code commented out, and new code added. The output will be the same, except we don't have to wait at the end of main (),because there is no Sleep () method. 2 3 4 pacl




需要 8 金币 [ 分享pdf获得金币 ] 2 人已下载





下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!