Go: Getting Started
-
The First Program
If you're anything like me, then talking about a language is interesting, but you're really here for the code. So without further ado, let's see some Go code. To do that, I'm going to open up a web browser and navigate to play.golang.org Like a lot of newer languages, Go comes with a test area where you can build out small applications to test out concepts or share with other developers to solve problems. If you come here, you can play with Go without dealing with a lot of the challenges that can be associated with getting a development environment up and running. As you can see, we are presented with a small application to start from. Let's go ahead and try this out by pressing the run button up here. It shouldn't be a big surprise that we see a simple Hello, playground message printed in the output section down here. In order to make this our own program, I'm gonna wipe out this code and start from scratch. We'll start with a package declaration, which is required for all Go code files, also called modules. The creation of packages and their naming conventions are beyond the scope of this course, but we do need to talk about one of them. The main package is going to be used by the Go compiler to determine the application's entry point. Inside of the main package, we need a function, denoted by the func keyword, that is also called main and accepts no arguments. This function, the main function in the main package, define the entry point that I just mentioned. Notice that this function is not wrapped in a class like we need to do in Java or C#. Functions are first class citizens in Go with all of the advantages and flexibility that that style brings. Within the main function, we're going to take advantage of the built in println function to write a message to the console. While this built in function is not intended for production use, it is great for simple applications and when you're debugging. If we run our little program, we very quickly get the anticipated message in the output window. So there you have it. Go's focus on simplicity has allowed us to get started with a simple, fully compiled, and statically typed program in three lines of code. The beauty of Go is that as we add more complexity, we don't run into a sharp increase in the difficulty in creating our programs. The complexity of your application's code will follow pretty closely along with the complexity of the problem that you're trying to solve. While we're here, let's play around with some other things that we can do in a module. First up, let's create a block of constants by using the const keyword and adding a matched set of parentheses. Inside of that, I'll create a constant called "message" and set it equal to our greeting. Next, I'll drop into the main function and replace the string literal with the constant. Running the application gives the same results that we had last time, which shouldn't be too surprising. If we try and change the value of the message to another greeting, we see that Go doesn't like that at all. Constants in Go are just that, constant. As a result the Playground presents us with an error message describing where it had the problem so that we can fix it. Let's change this behavior by converting this block from a block of constants to a block of variables. This is easily accomplished by changing the const keyword to var. We'll also tell Go what type of data this is, although this isn't really required. Go would infer the variable type from the fact that we are assigning a string value to it. If we remove this line here, we should get the "Hello Go!" message printed. Let's run the application and, sure enough, everything is working as expected. Now, let's add in the new message value again and see how things respond. OK. We can, in fact, change the value of a variable. Excellent. We can also remove the variable initialization up here and, since we're assigning a value in the main function, we get the same output. What do you think will happen if we remove the assignment line? In many languages you would expect to get a null value of some sort. While Go does have the concept of null, called nil, you're not going to run into it as often as in other languages. Running the application appears to have done nothing. In fact, we did get an output, but it's an empty string. This is due to the fact that Go will initialize variables to the zero value of that type. For a string, the zero value is the empty string. The last way that I want to show you for initializing the message is via another special function that each module can have called init. When present, it will run the first time that the module is referenced by another module. Inside of this function I'm going to assign a value to the message variable. Running the application reveals that the init function was, in fact, executed before the main function. I wonder what will happen if we initialize the variable upon declaration up here? Well, we're here to learn. So let's run this and find out. Interesting. It appears that the init function runs after the variables are initialized to their default values but before any other functions are executed. This can be a useful place to put setup logic that ensures that the module is ready to be accessed. The last part of a module that I want to introduce you to is the import keyword. This is used to tell Go that this module needs to import another package to use it. Let's import the "fmt" package so that we can print the messages to the console in a way that is more advisable in production. Notice the quoted string describing the package name. Now that we have told Go that we need that package, we can call its println function by starting with the package name and changing this "p" from lower to upper case. Once again, running the application gives us the same output. If you need to import a lot of packages into the current module, this form of the import syntax gets a bit noisy. In those cases, we can add mashed parens after the import keyword and then list each package that we wish to import with each one getting its own line. The last thing that I'd like to show you here is what Go does if we don't use an import. To demonstrate that, I'm gonna change the main function back to using the built in println function. Running the program again reveals an error. Notice the message states that we are importing a package that isn't being used. This is one of the rules that's enforced by the compiler about Go code. You aren't allowed to have any import or variables that aren't used. This keeps your production code cleaner and makes it harder to gather unused appendices or dead code paths that tend to build up in many applications over time. The fix to this is pretty simple. All we have to do is remove the import block and everything returns to running normally.
-
A Tour of golang.org
One of the most important things to know as you start to explore the Go language is where to go to get information about the language. Having a name like Go can be tricky to find information in a search engine. The first thing that I'd advise you is to use the word golang, that is "G-O-L-A-N-G" in your queries. You'll get much better search results. By and large, you probably won't need to do that after you know about the Go homepage located at golang.org Navigating there opens up a world of information and tools to help you whether you are new to Go or a veteran with the language. The first place you're going to need to know about is this download page here. This is where you'll gain access to the Go development tools that is available in a wide variety of platforms. As you can see, there are quite a few different options depending on the operating system and architecture that your environment is using. The next section on the site is the Documents section. While there is a wealth of information here, I'd like to point out two areas in particular. First, is the Tour of Go link here. This takes you on a guided tour of the Go language using the same technology as the playground that we used earlier. In this area, the code window is augmented by guided tutorials on the right hand side. This is definitely a place that you should visit if you decide to continue your journey into Go. The second article that I'd like to point out is the Effective Go page here. This extensive page is designed to introduce you to the conventions, styles, and primary use cases of much of the Go language and major packages. Taking the time to work through and understand each section will pay rich dividends in the long run. The Packages section is where you'll find documentation for many of the packages that have been published for Go. It is organized into three major sections. First is the standard library that is installed when you install the Go development tools. These packages are the first class citizens of the Go ecosystem, and are actively maintained by Google. Next, comes a list of Sub-repositories. These packages are not supported as strongly as the standard library, but reflect packages that might become part of the standard library later. This is an excellent place to look if there are new standards coming out, and you want to know if Go is working on adding support for it. Finally, as an open source project, Go works to elevate and raise community contributions as much as possible. One of the ways that it does that is by publishing a list of community created packages here. If we browse to one of the links here, we find some information about the package. It looks like we just found a driver for Postgres. We are also given some instructions on what the package is for and how to get started using it. Let's go back to the home page and drill down into the link called The Project. This is a nice place to visit if you want to see what has changed in the language as it has evolved. While this isn't normally something that you'll be concerned with in the beginning, managing upgrades to new versions of Go can be eased by knowing about this page. Obviously, when you're starting with a new language, you're gonna find times when you need some help. The Help page lists the wide variety of support resources that are available to you. From passive content like the Go wiki to active community tools like the Go Nuts mailing list or the Go IRC channel, a rich set of resources are available to ensure that learning is as easy for you as possible. The final stop that I'd like to make is the Blog page here. The Go language is moving pretty fast right now, and there are many best practices and tools that are documented only in this blog. Eventually, the critical pieces make their way into the main site, but this remains a useful resource to gain practical knowledge about how to use Go in the wild.
-
The Development Environment
For this course, we're gonna use the Playground on golang as much as possible. There are times however, that we need a local development environment to learn a concept. I'm going to be using the excellent Atom text editor from GitHub for this course. It can be obtained from their website at atom.io where you can also learn about this tool. Inside of Atom I've added two packages. The go-plus package integrates Atom with many of Go's command line tools to provide automatic formatting, code completion, linting, and other useful features. I'm also using the terminal-panel package to give me access to the command line from within Atom. While useful on its own, this should help us stay in one tool during the course and reduce the amount of jumping around that I have to do to illustrate a concept. Outside of Atom, I've included one other addition. The Go code plugin, located at github.com/nsf/gocode is the standard package that is used by Go developers to add code completion functionality. It runs as an external process, making it easy for all sorts of editors and IDE's to tie into it. In order to give you a taste of the development experience that we're gonna have, I'm gonna quickly reproduce our "Hello Go!" demo that I showed in the Playground earlier. Of course, we'll start with the package declaration that is required to start each Go module. Next we'll create the main function. Actually, there are two ways to do that in Atom. We could type everything out like I have here, or, if I delete this, I can use the main code snippet in Atom by typing "main" and hitting the Tab key. As you can see, we get to the same place, just with fewer keystrokes. Now, I'll add the code to print our message to the console using the built in println function that we've used before. That should do it, so it's time to run this thing. To do that, I'm going to create a new terminal window by hitting the ctrl key and the back tick. Now, we'll run the program using Go's run command by typing "go" followed by the "run" keyword and providing the location of the main .go file that we've been working with. Hmmm. That didn't go well. Oh. I guess I need to save the file first. Let me jump back to the code window with ctrl and back tick, save the file, and then go back to the terminal again. Once again, using ctrl and back tick to toggle between the panels. Running the application again, gives us the expected result. Notice how fast that was? Of course, this is a little program, but it is fully compiled and runs almost as quickly as if it were interpreted. This makes development iterations extremely pleasant, something especially important to you TDD'ers out there. Another thing that I'd like to show you is that the go-plus package will automatically run Go's code formatter every time you save. This can make diffing source files much easier since you don't have to fight through a ton of insignificant changes due to whitespace differences between two versions of the source file. The last thing I'd like to show you about our development environment is going to require me to import a package. Let's grab the "fmt" package that we used earlier. Now, I'll clear out the main function and start again, this time referencing the "fmt" package. Notice that I've already got a type hint suggesting the package name. When I hit the period a whole list of options comes up, courtesy of the go-plus package backed by the Go code code completion service. If I start typing the name of the function I want, the list is filtered as you might expect. Finally, selecting a function gives me the function signature. I can then start typing right in place to overwrite the first parameter. If more than one parameter were accepted, then the tab key would move me to the next parameter and so on. Once again if we jump to the terminal panel, and run the application, we get the expected result.
-
Summary
In this module, we started our journey to explore the Go language and ecosystem. We got a chance to get a first look at the Go language itself by exploring GitHub's Atom editor and how a couple of packages can go a long way to streamlining the development process. We also used the Go Playground to test out new code ideas without the overhead of setting up a local development environment. Finally, we took a brief tour of the golang.org website to see what resources it makes available to us to ease the learning process. In the next module, we'll dive into the language and start to explore how to work with variables and arithmetic operations. See ya then!
-
Data Types and Operators
Introduction
Hi, my name is Michael Van Sickle. Welcome back to this course where we are investigating the Go language to help you know if it is a language that you'd like to add to your arsenal. In this module, we're going to begin to introduce the elements of the Go language, by talking about various Data Types including Primitives, Constants, and Collections. We'll then introduce Arithmetic Operators and play with them for a bit. Finally, we're gonna wrap up by introducing a real world scenario that will let us apply what we've learned through this module to an application that we'll keep coming back to and enhancing through the rest of this course.
-
Primitive Data Types
The first thing that I'd like to introduce are the Primitive Types that make up the foundation of how to store data in Go. While we're gonna find all of the usual suspects such as Integers and Strings, there are one or two members of this group that might surprise you a bit. I've jumped over to The Go Playground, where we can explore without being distracted by the editor. Don't get me wrong, I'm a huge fan of autocompletion and code snippets, but I want to focus on the code, not the editor for now. Let's begin by creating a simple Integer. We'll start to declare it by entering the "var" keyword to signal that we are declaring a Variable. Next comes the Variable name, followed by the Type. If you've used C-based languages before, such as C++, Java, or C#, then this syntax might seem a bit backward. In those languages, you'd expect the Type to precede the Variable name. As I understand it, this syntax was selected in order to simplify the parsing of the source code since it feeds the data in the order that it is expected. Personally, I've become a fan of this syntax as I think it reads a little bit easier, but your mileage may vary. The next thing we'll do is assign a value to the Variable. To do that, we simply enter the Variable name, add the Assignment Operator that is made by a single "=" sign, and the data that we want to store. 42, in this case. Now, we can admire our handiwork by printing the Variable's value to the console and indeed, we have succeeded in storing the value of 42. However, remember that one of Go's goals is to be simple and fun. This is a lot of boilerplate to write in order to store a simple value. Let's see if we can simplify this a bit. Let's start again with the "var" keyword and create a float32 named myFloat32. Notice, we have to specify the size of floats, 32 bits, but we didn't with an int. There aren't 32s in int64s, but it's generally advised to simply use the int type unless you have a very specific reason for using a specific size. For floats, however, there is no standard and you have to choose between 32- and 64-bit sizes. Anyway, let's take advantage of the fact that we can assign a Variable's value at the same time that we create it by giving the value of 42. I'm gonna finish this with a "." in order to explicitly tell Go that this is a floating point number. While not strictly necessary here, it is a good habit to get into since Go will often refuse to implicitly convert between Types for you. That means that adding an Integer to a float won't work without help. Let's print the value of our Variable once again and see what we have. OK, we have the value, 42, but it's printed in its exponential form. This is a bit better but so far all we have is a declaration syntax that is actually more verbose than many other statically typed languages. As you might imagine, I have one more trick up my sleeve that gives the advantage to Go. This time, let's create a basic String. I'm gonna drop the "var" keyword and just start with the Variable name. Next, I'll add a ":" in front of the "=" sign, to tell Go that we want to use a short Variable declaration syntax. We can then follow that with the String literal that we wish to assign. The Go compiler looks at the assigned value and infers the Type from that value. In this case, the compiler determines that myString must be a String Type and gives it that Type. The last Primitive Type that I want to show you is the Complex Type. Go has first-class support for Complex numbers, a vital feature in many mathematical models. To create a Complex Type, we use the built-in complex function and provide two arguments, the real component and the imaginary component. When printed, we get both values concatenated together with the Imaginary part followed by the traditional "I". We can also retrieve the real and imaginary parts by using the built-in real and imag functions. If I run this version, we see that the components are now available for us to work with.
-
Constants
In many languages, Constants are just that. A value that is assigned at compile time and cannot be changed afterward. While Go's Constants meet that definition, they also have some tricks that make them more powerful and flexible than versions that you might have seen in other languages. Let's start by creating a Constant block. If you'll remember from Module 1, we do that by using the "const" keyword in the module and adding "( )" after it. Within the block, we'll assign three Constants, first, second and third, giving each one a String value. Please note that we can use any Primitive Data Type that we want. I'm just using strings in this example for now. In the main function, we'll simply print out the value of each of the Constants and see what we get. It should be big shock that the values of the Constants are printed in the output window. No big deal, right? Well, let's see what you think of this. Go has a special keyword that works in the context of a const block called iota, spelled i-o-t-a. Let's see what it does. To do that, I'm gonna replace the first Constant's assignment with iota, and remove the second and third Constant assignment. Normally, you would expect the compiler to throw an error since we have declared two Constants with no values, but that's where iota is special. If we run the application, and we find that, in fact, the Constants have been assigned the values of 0, 1, and 2. In short, iota gives us an auto-incrementing number that we can use to determine the values of our Integer. Among other useful features that this provides, we can use this as a basic type of enumeration. The next thing I'd like to try out is if we have two const blocks that are both using iota. What do you think will happen? To find out, I'm gonna close the const block after the second Constant and start another one for the third. If I run the code now, we get the error that we expected last time. The compiler doesn't know what to assign to our third Constant so it blows up. That implies that there must be some kind of relationship between the const block and iota. Let's try assigning iota to the third Constant and see what happens. Hmm, looks like the value of iota was reset to 0 in the second block, so now we have 0, 1, and 0 printed to the output. This is in fact how iota works. If we tried to call out iota again in the same block, it would not reset. The last thing I'd like to show you about Constants is something called a Constant Expression. It turns out that we can do some pretty complicated things to determine the value of a Constant with the only stipulation being that it has to be able to be resolved at compile time, meaning no function calls can be involved. To illustrate, let me rejoin the third Constant back into the original const block and remove its iota value. Next, I'll change the value of the first Constant to equal 1, and then bit shifting that by (10 * iota), which is a way to generate the value of 2 raised to 10 times the iota power. Running this gives us some surprising results. The first value is 1, which is 2 to the zeroth power. Next we see 1024, which is 2 to the 10th power, followed by 1048576, which is 2 to the 100th power. The compiler applied the same Constant Expression to each value of the const block and reevaluated it each time with a new value of iota.
-
Collections
In many languages, when you see the word Collections, you immediately think of going to the languages standard library and getting some kind of list, map or dictionary. These data structures are so common, that the designers at Go decided that it made sense to build these basic structures directly into the language. The first Collection Type that we're gonna look at is the humble Array. An Array is a group of data elements of the same type that are stored together in memory. While they can be very useful, they are constrained to have a fixed size, and therefore not commonly used in Go. However, they underlie a very common Collection Type, so let's explore how to work with them. We create an Array by starting with the Array size in " Notice that, once again, things are a bit backward from what you might have seen in other languages. The last thing we need to do is put these "{ }" here. We'll get back to these in a moment. For now, we can assign values to each element in the Array by typing the Array's name, followed by the index that we want to work with, once again in " Arrays are a bit too complicated for the built-in println function to print, so we're gonna use the "fmt" package to help us out this time. We'll use its println function to print the Array out, and see what we get. As you can see, we have each element of the Array printed nicely for us. In order to reduce some of the redundant code that we have, we can use the Array's initializer to set the values. That's what the "{ }" here are for. We can add the data elements, in order, to the Array, separating each value by ",". One other advantage that we get with this is that we no longer have to explicitly tell Go how big we want the Array to be. As long as all of the values are in the initializer, we can replace the size with "...". Go will then create an Array that is exactly large enough to hold the data. With the initializer in place, we can get rid of these lines and rerun the program. Once again, we get the same output that we had before but with a much more concise and efficient syntax. If you ever need to know the size of an Array, you can use the built-in len function. This function can be used for many data types and collections. As we can see, when we print the result of that function, we get the expected value of 3 printed to the console. As I mentioned before, Arrays are not often used directly in Go. Instead, a more flexible data type, called a Slice, is used. A Slice represents a subset of an Array, a fact that, on its own, is of marginal use. However, the underlying Array can be managed by Go itself, allowing you to add as many elements as you want to the Slice, and Go will take care of re-sizing the underlying Array as needed. For now, let's create a Slice of the current Array. The fastest way to do that is to call out the Array, and then append " Optionally, we could also include an index before the ":" to select an element other than the first one. We could also add an index after the ":" to give the ending the index of the Slice. By just including a ":", we are telling Go that we want to create a Slice of all of the values in the Array. If we print this Slice out, we see that it gives the same result as the Array. In fact, if we changed one of the values of the elements, we'd find that both the Array and the Slice are updated. As I mentioned before, Slices are more flexible than Arrays because they can be re-sized on the fly, adding additional elements as needed. To show that, I'm gonna juggle a bit of code around so that the Array is printed before and after the new element is added, so that we can see what happens to it. Now, we'll assign the Slice to the result of the built-in append function. That function requires the Slice that is being appended to and the data elements still penned. If we run the application again, we see that the Array is exactly the same as before and after the append operation. However, our Slice now contains four elements, with the new one tacked onto the end. When working with Slices, you often don't care about the underlying Array itself. Instead, you can let the Go Runtime manage that for you. To create a Slice, we can use a syntax similar to the one that we used to initialize an Array, however, we don't need to give a size or the "..." inside of the leading " Leading them out means that we have a Slice. Printing that out to the console reveals an error, hmm. That isn't how it's supposed to work. Oh, I missed the ":" here that tells Go that I'm performing a short declaration. OK, now everything's happy. We can also print the length of the Slice using the len function again. As we expect, the value of 3 gets printed to the console. Something important to consider is that the Array that underlies the Slice is exactly the right size. While this works well for us in this scenario, it can be very inefficient if we are going to be adding a lot of elements to the Slice, since Go will have to constantly copy the data to a new, larger Array as more data gets added. To get around this, we can create a Slice with a larger initial Array by using the built-in make function. This takes two or three arguments. When created with two arguments, we create a Slice with the size that we specify. The Slice and its Array are the same length in this case. If, however, we want a small Slice to start with a large capacity, then we can pass a third value into the make function. The second parameter will be used to set the initial size of the Slice, or the third will be used to set the size of the underlying Array. For now, I've created a Slice with a size of 100 elements and set some values for the first three. If we run the program, we see that, as expected, we have 100 elements printed with only the first three having non-0 values. The last Collection Type I'd like to introduce is called a Map. This data type holds multiple values that are accessed by customizable keys. Both the key and the value can be arbitrary data types. To create a Map, we use the make function passing in the signature of the Map. We start to define the Map type by passing in the Map keyword. We'll then give the type of the keys inside of " and finish up by providing the type of the values. In this example, we've created a Map that holds String values organized by Integers. We can then assign an entry to the Map by telling the Map where to store it, and then passing in the value. Let's do this a couple of times and then print out the Map again. As a final test, let's try to print a value of the key that we haven't assigned. Running the application shows that we start with an empty Map as expected. After we assigned the two values, they were returned to us in the printed output. Notice the impact of the third print statement. Once again, we've gotten the 0 value for our Variable, an empty String in this case. Go does not throw an error or print a nil value when we ask for the data that isn't present. It just silently provides a placeholder value for us to work with.
-
Arithmetic Operators
While Primitive Data Types form the basis for how you store data in an application, Arithmetic Operations form the foundation for how data gets manipulated. While this isn't the most complicated topic, I want to take a few minutes to familiarize you with how to work with these Operators. Let's start with the Addition Operator. To use it, we simply need two values of the same type and link them together with a "+". Printing the result yields the expected result for adding two numbers. This Operator also works as the String concatenation Operator when you need to put two strings together. The Subtraction Operator works the same as the Addition Operator, with the obvious exception that it will subtract the second value from the first, returning the result. Also, there is not an implementation of this Operator that works with String. The Remainder Operator, sometimes called the Modulus Operator, will return the remainder of an Integer Division Operation. If you'll think back to your grade school days, you'll remember that 5 รท 2 results in a 2, with a remainder of 1. The remainder operation is going to allow us to get at the 1. If you want the quotient, that is, the 2 part, then you can simply use the Division Operator, represented by a "/". The next Operator that I'd like to introduce is the Increment Operator. This guy works a bit differently than you might be used to. I'll show you that in a bit. To use the Increment Operator, you need "++" after the Variable name. Let's do that a couple of times and run the application to see what happens. As you can see, the Increment Operator adds 1 to the Variable each time it's called. There's another related Operator, called the Decrement Operator, that will subtract 1 every time it's called. To use that, you use "--". If you've used this Operator before, then it was probably treated as an expression, meaning that it could be used in line with other expressions to form a single statement like this. When we run the application this time, however, we get yelled at. That's because the Increment and Decrement Operators in Go are statements, meaning that they have to be used on a line all my themselves. While this means that your code can't be quite as expressive when using these, it was an intentional decision by the Go team in order to eliminate a class of defects where the developer misunderstood how this Operator works. Let's clean our application back up by removing the Increment Operators here and then we can move on to the last operation that I'd like to talk about. There are many times when you have to manipulate a value and then reassign the new value back to the same Variable. While the Increment and Decrement Operators let you move the Variable by 1, what happens if you need to move more than that? Or if you need to use an operation other than addition or subtraction? In those cases, you can use a class of Operators called the Augmented Assignment Operators. If we put a "+" in front of an "=" and then add another Integer, then Go will add that value to the Variable's current value and assign the result back into the Variable. Running the application reveals that the 3 that we ended up with from the Increment Operations is raised to an 8 by the augmented addition. As a final example, you can also do additional operations like multiplication by using an "*" in front of the "=" sign. Now if we run the application, we get, hold on, oh, I think our output has fallen below the page boundary. Yup, there it is, 80, just like we would expect.
-
Putting it Together: Variables and Operations
In order to reinforce the concepts that we are reviewing, I'd like to start building a little application with you. Let's imagine that we work at a utility company called Gopher Power & Light. It is a preeminent supplier of clean, renewable energy and we were lucky to land a job there. For our first assignment, we've been asked to generate some reports that will help keep track of the company's power plants and how much demand the power grid is exerting on those plants. While we don't have enough right now to build anything too sophisticated, this application is going to evolve throughout the rest of this course to end up as a cool little console application that demonstrates a lot of the key concepts that are involved when developing with Go. In order to build this application, we're gonna need a bit more control than we get with The Go Playground, so I'm going to be working in the Atom Editor. For our first assignment, we need to generate a simple report that calculates the current power plant Capacity that is feeding the grid and compare that to the grid's current Load to see how much of that Capacity has been utilized. To start, I'm going to create a Slice of float64s that will hold the Capacity of each of our power plants. I'll start by declaring the Variable, and then initialize it on a separate line by using the Slice's initializer syntax. Let's create a few plants that have 30 MW of Capacity, a couple with 60, and then one big one with 100 MW of generating Capacity. In order to determine the total Capacity, we need to add these values up. While that will be much easier after the next module, we can work with this for now by referencing each index in the Slice and adding it together. We'll do that on the same line that we initialized the Capacity Variable to review that syntax. Next, we need to get the current Load on the grid. We'll set that up by simply assigning a value of 75 MW for now. Now, we have all the information that we need to determine the utilization of the plants. We'll assign that result to a variable called Utilization and use the short declaration syntax to save us some typing. The value is determined by taking the current Load and dividing it by the total Capacity. We're finally ready to generate our initial report. We'll do that by creating a label and adding the appropriate value. Now, we can open up the terminal pane and run the application. To do that, we simply run the Go command, passing in the keyword run to tell Go that we want to compile and run the application without storing it to disk. The last parameter is the relative path to the main.go file that we were working with. Well, everything seems to work, but the formatting leaves something to be desired. Let's take a second to clean that up. To do that, we're gonna import the "fmt" package that we've worked with before. Now that we have that, I'm gonna replace the println function with calls to the Printf function from the "fmt" package. This function takes a formatting String and passes the other parameters into the appropriate locations. For now, I'm just gonna drop in the formatting Strings. For more information about what each of these characters does, feel free to read up on it on golang.org in the documentation for the "fmt" package. Hmm, seems like I forgot a "," comma right here. Let's try that again. OK, now everything seems happy. Let's open up the terminal panel again and rerun the program. Ah, there we go. Not the fanciest report in the world, but I think we're off to a pretty good start.
-
Summary
In this module, we covered quite a few of the fundamentals. We investigated how to declare Variables using the three different styles, including the short declaration syntax that allows Go to infer the type of our Variable. Next, we looked at Constants and how the combination of the iota keyword and Constant Expressions opens up a whole new world of possibilities for how to use Constants in your application. Next, we looked at Arrays, Slices and Maps that are used to store Collections of related data together. Finally, we looked into the Arithmetic Operators that are foundational to working with data and real applications. In the next module, we'll add in the final two critical elements in any program, Branching Logic and Loops. Stay tuned.
-
Branching and Looping
Introduction
Hi, this is Michael Van Sickle for Pluralsight. Welcome back to Go: Getting Started; in the last module we started to get our hands dirty by exploring basic data types and arithmetic operators. Now it's time to add in some of the most critical structures in any programming language, branches and loops. While the basics of these structures in Go, are going to be pretty familiar, we're going to find that Go has some nice enhancements that allow these constructs to be more flexible and powerful.
-
Branches
The first set of constructs that I would like to discuss are the ones that allow you to add branches into your code. This allows your application to respond differently, depending on its state as it goes into the branch. While all the usual suspects are gonna be present, we'll discover a few gems and useful additions along the way. First up is the venerable if statement. It allows us to test for a condition and if it's true, evaluate a block of code. There are a couple of things that I'd like to point out about the syntax here: first, notice that the logical test that is foo = = 1 part, is not surrounded by parentheses. These aren't necessary in Go, like in many other languages. Second, we always have to surround the statements within the if statement, with curly braces. That is intended to eliminate a class of errors that can result when single-line statements are allowed. Finally, notice that I put the opening brace right here, next to the test; this is required in Go, and the application won't compile without it. The reason for this goes back to one of the goals of Go, to be simple and fun to use. The developers of Go made the arbitrary decision to enforce this format and bake it into the compiler. If you try and move the brace to the next line, you'll be greeted with a compiler error. Running the application, prints the value bar into the output window; this is because the variable foo, contains the value one, and so when tested against the literal one, with the equality operator, indicated by the double equal sign, the code inside of the block executes. If we change the value of foo to two. then the println function is skipped, and nothing is sent to the output window. If we do want some kind of behavior to occur in the event that the test fails, we can add an else block after the closing curly brace here. With this structure, we will always evaluate one of the two branches, but never both in the same pass through this code. So far this is pretty normal stuff for an if statement; however, Go does have one very useful enhancement that can make your code more concise and readable. Since many if statements are used to decide what to do after calling some sort of external action, Go allows a statement to be added before the logical test. To demonstrate, I'm gonna move the initialization of foo, into the if statement, right here after the if keyword. I need to add a semi-colon, to tell Go where the initializer stops and the logical test starts. This setup allows us to put the variable initialization right in the if statement that needs it. Now, a word of advice, just because you can do this, doesn't mean you always should. By using this syntax, you are tightly linking the if statement to the initializer. In many cases, this makes a lot of sense and you should do it, however if the results of the initializer are used by more statements later in the code, it might be better to keep the intialization on a separate line. The other branching structure in Go is the switch statement. It comes in two forms: the first one looks like this, with an optional initializer in the variable we want to test. We can then add multiple k statements within the switch's curly braces, that execute if the variable is equal to the k statement. Switch statements in Go also support another scenario. It is possible, to use switches to evaluate cases with more sophisticated logic. These cases can get pretty complicated, so I'm just going to give you a taste for now, and then you can research this more on golang.org if you're interested. To use this form of the switch, we need to get rid of the initializer and the variable in the first line, leaving us just the switch keyword and the opening curly brace. Now the k statements have to take the full responsibility for the tests. For the first case, let's check if the variable is equal to one; in the other case let's test to see if foo is greater than two. Now, let me just add a second equal sign here to make this an equality operation, not an assignment. Okay, now we can run the application and we get the word "one" printed like we expect. If we change foo to five, then we'll execute the other case and "two" is printed to the console.
-
Loops
Branches are critical for allowing your applications to make a decision between two or more code paths that should execute based on its state. Loops, on the other hand, allow a section of code to execute multiple times, an important feature to have especially when working with collections of data. Probably the most basic loop is the loop with three statements. If we start with the keyword "four," add in an initializer, a test and some sort of incrementing logic, we can easily count from zero to four. With this form of the four loop, each of the three statements has a very specific job. The first one will perform any initialization logic that we require. Next, comes a test that is evaluated each time the loop is about to start. If the test evaluates to true, then the loop is complete and execution resumes at the first line after it. Finally, comes a statement that is executed every time we finish the loop, allowing us to increment the counting variable after each pass. Each of these statements is optional, and if you don't need it, you can just leave the statement empty. Just remember to include both of the semi-colons, when using this form. Another form of the four loop, takes on the role of the while loop in many other languages. Let's initialize a variable to zero. Next, we'll create an infinite four loop, by using the four key word and immediately following that with an opening brace. Inside of the loop, we'll increment i by one, each time we pass through the loop and print the value out. If we ran the application like this, it would run for a very long time, since there is nothing to tell it to stop. Since we don't have infinite time to talk about this, let's put in an if statement, that will check if i is greater than five, and if it is we use the break statement to break out of the loop. Now, if we add some closing braces and run the application, we get the numbers one through six printed to the console. While this example is a bit contrived, this form of the four loop is very useful if you can't determine a compile time, when the loop needs to stop. The next thing I'd like to talk about is how to loop over collections, such as a raise, slices or maps. Once again, Go calls upon the four loop to provide that functionality. To demonstrate, let's create a slice of strings and use a four loop to print out the position and value of each member of the slice. To do that, we're going to take advantage of the fact that Go supports multiple assignments to variables. After the four keyword, I'm going to initialize two variables, idx and v, to the result of the range keyword, followed by our slice. The inclusion of the range keyword signals Go that it should expect a collection type. In the case of an array or slice, two results are returned. The first is the current index in the array, and the second is the value at that index. Let's print them both to the console and see what we have. Nice, now we can work with each item by its position in the slice or its value, whichever works best for us. We can also loop over the entries in a map in a similar way, but the values are a bit different. In this case, the two variables that are returned are the key and the value of each map entry; this is actually even more useful than it might have first appeared. The second returned variable, gives us direct access to the value, which is useful in read-only scenarios or when we need to manipulate internal members of the value. However, if you need to reassign the entries in the map to a different value, the second parameter isn't really going to help you. In that case, having the key available, let's you access the position in the map that you need to reassign.
-
Demo: Branching and Looping
We've learned a lot in the last few minutes. We can use that knowledge to really add a lot of functionality to our reporting application for Gopher Power and Light. This time, we're going to convert it to a menu-driven console application, clean up the report that we created in the last module, and add a whole new report that gives a report on each power plant's capacity. We're back in Atom, where we find the application that we built at the end of the last module. The first thing that we're going to do is blow this whole thing away. Don't worry, quite a bit of it will be coming back, but we've got much better tools to accomplish this work, so it'll be easier to rebuild it from the ground up. Let's start by creating the menu for our console application; the first report that we need to generate is a power plant report, that for now, will list each power plant and its capacity. The second report will be the grid-load report that we've already done, but with a twist. This time we're going to have a list of plants that are actually contributing power to the grid. Finally, we'll add a line asking the user to select an option. In order to capture their choice, we need to declare a variable to contain it. Next, we'll use the scanln function, from the fmt package to read their selection from the console and store it in the option variable. Notice that I've added an ampersand in front of the variable; this tells Go to pass the memory address of the variable to the scanln function, so that it can populate the value. If I just pass the option variable itself, then only a copy would be passed, and I couldn't get access to what the user entered. Before we go any further, let's take a look at what this is gonna do. For now, I'm simply going to print out the value of the option variable and exit the application. Now we can go to the command prompt and run the application. Well, everything looks pretty good with the menu; let's try option one. Yep, it seems that we've captured that value. Let's try once more, and yep, I think we're set to continue. Let's jump back to atom and remove this print line. Now, I'd like to add the first report. For this one, we're going to loop over the collection of plants, and print the plants index in the collection in its capacity. To start that, we're gonna need a switch statement that will allow us to execute different code paths based on the report that is selected. Let's start with option one, which corresponds to the plant report. For this case, I'm going to use a four loop to grab the index and capacity of each member capacity of each member of the plant capacity slice, speaking of which, let me jump up here for a second and drop that back in. Okay, within the loop I'm going to drop in the statement that will print a nice message for each entry in the slice, listing the plants index and its capacity. Notice that, once again I'm using the printf function, in order to allow me to format the string nicely for output. Let's jump to the command line again, and verify that everything is working as expected. I'll run the application again and select option one. Excellent, each of the six plants is printed in order, along with its capacity. For the next report, we have a bit more work to do. Instead of just blindly adding up the total number of plants we need to consider that not all of the power plants are going to be online at one time. For now, we'll support that requirement by creating another slice, that holds the indexes of the active plants. To start, I'm going to create a variable called capacity and set it equal to zero. Next, I'm going to loop over a slice called active plants, which will hold the index of each active plant from the plant capacity slice. Let me jump up here once again and drop that slice in. While I'm up here, I'm going to add the current grid load that we'll need in a second. Now, let's drop back down and, after I correct a typo here, calculate the capacity by pulling the value from the plant capacity slice that matches the current plant ID. Now that we have that value calculated, let me drop in the printed output code. This is basically the same as what we had before, with the small change that I'm calculating the utilization in the print statement. The last thing I want to do is handle the situation, in which the user enters a value that we don't recognize. We can do that by adding another case using the special default keyword. This will execute if none of the other cases are matched. If that happens, we'll print out a message telling the user that we didn't understand their request, and then exit. Now, let's save the file and, oh something's wrong. Ah, I see it; Go doesn't support implicitly converting between data types. When I initialized the capacity, I did not add an ending period, so Go's type inference chose to create it as an integer type. Since the plan capacities are float 64's, Go can't add them together. We can fix this pretty easily by adding a period after the zero, and we should be set. Now, let's go to the command line and fire up the application. If we select option two, we get our grid capacity report. It looks like we've got a bit of a capacity problem on the grid. Hopefully somebody knows how to fix that. For now, let's try an invalid option, say three. As expected, we're presented with a message telling us that the option is invalid.
-
Summary
In this module, we introduced the last two structures that are critical for use in your applications. Branches allow us to select between different code paths to execute, based on different conditions. By using initializers, we can streamline our code, and bring the code that generates the test conditions right into the statement. Switch statements allow us to use complex expression to determine which paths to take and can be a very powerful tool. Loops allow us to repeat a section of code multiple times. By using a basic four loop, we can determine the number of times that we iterate, by using a counter-variable or some arbitrary logic combined with the break statement to terminate the loop. When working with collections, we found that adding the range keyword to the four loop, allows us to repeat the loop for each entry in the collection. In the next module, we're going to break out of the main function and learn how to create functions of our own. While not absolutely necessary, the ability to create functions is important for any non-trivial applications that we're going to build. Come back soon and we'll check them out together.
-
Functions
Introduction
Hello, my name is Michael Van Sickle. Welcome back to this course where we are getting started with Go. In the last module, we finished an overview of the tools required in an application to manage the execution path through a function. Now it is time to learn how to work with functions themselves. We are gonna learn how to pass data into functions, how to get data back out of them, and take a brief stop to see how Go's treatment of functions as first class citizens allows us to create anonymous functions.
-
Functions and Parameters
The first things that we need to talk about are how to create a function an how to get data into it. In this section, we'll focus on three ways to pass data to a function. Passing by value allows us to send a copy of a variable's value into the function and protect it from getting changed. On the other hand, passing by reference allows the function to manipulate the data of the parameter directly. Finally, we'll cover a special type of parameter that will turn the function into a variadic function. That is one that can take an arbitrarily long list of arguments. We've actually already seen the creation of a basic function already. In fact, it's right here. The main function that we have been using as our application's entry point. There is in fact, nothing special about the main function other than the convention that, if it is part of the main package, it serves as the application's entry point. We can create our own function by following the pattern of main. We start with the func keyword, add a unique name for the function, some matched parens, and finish off with a pair of matched curly braces. In the function's body, I'm going to add a simple message to be printed to the console. With that defined, we can jump up to the main function and invoke our shiny new sayHello function by entering its name, followed by matched parens. Running the application reveals that we've succeeded in our goal of allowing us to create our own functions so that we don't have to implement everything in the main function as we've been doing so far. This is great and all but we are a bit limited in what our function can do since we can't pass any information along with the invocation yet. To do that, Go allows us to add parameters that will be used by the function to alter its behavior. As a simple example, let's define the message that we'd like to present in the main function up here. Next, we'll pass that into the function by entering the variable name inside of the parens. Finally, we have to tell the function that something is coming and what type of data to expect. We'll do that by entering the name of the parameter inside of the function's parens and follow it with the data type. Now that we have that in place, we can replace our hard-coded message with the value of the incoming variable and we're all set. Running the application verifies that everything is working as planned and our little function is much more reusable since we can alter the message to be printed by passing a new string into it. What do you think will happen, however, if we try to alter the value of the incoming message variable? To find out, I'm going to re-assign the incoming message to contain Hello Go. With that done, I'll print out the value of the message out to the console in the main function after we invoke the sayHello function. As you can see, the value of the message variable in the main function remains unchanged. This is due to the fact that Go has copied the value of the variable into the function's parameter. That means that changing the message variable in the function only changes the copy, not the original. If we want to allow the function to change that value, then we need to change our application a bit. Instead of passing the value of the variable, we're gonna pass in the memory location of the variable. By providing the function a reference for how to get to the variable, it will then be able to manipulate the value directly. To do that, I'm going to add an asterisk in front of the function signature. This small change will tell the function to expect a memory address that is pointing to a string or, in other words, a pointer. Our next step is to pass in that memory address instead of the string itself. We'll do that by adding an ampersand in front of the parameter that we are passing into the function. The final change that we have to make is to add another asterisk right here. This time we are asking Go to put the string to the right of the assignment operator into the memory location referenced by the pointer. Oh, one last bit to change. Right now, this println function here would simply print out the memory address of the message pointer. I'd like to maintain our previous behavior of printing the string so I need to add another asterisk here to get the value that the pointer is pointing to. This is called dereferencing a pointer. Now, if we run the application we can see that we have succeeded in altering the message variable in the main function from within sayHello. The last thing that I would like to show you is that we can define a function to accept any number of parameters, turning it into what is called a variadic function. In order to demonstrate that, I'm going to have to make quite a few changes. First of all, let me clear out the function body of sayHello since we're gonna replace that completely. Next, I'll replace the asterisk here with three periods and make the parameter name plural. The three periods is what signals the creation of a variadic parameter and it must always come last in the list of parameter declarations. When called, Go is gonna take all of the strings that we provided and pass them into the function as a slice of strings that are assigned to the messages variable. From there, we can work with it like any other slice. To demonstrate, I'm gonna loop over the value of each of the members of the messages parameter and print them one at a time to the console. Up in the main function, we simply need to pass in the strings. Let's pass in a nice greeting from Pluralsight and see what happens. Running the application reveals the behavior that we hoped for.
-
Return Values
Well, being able to pass data into functions is very helpful and I can see a lot of ways that we can use that. But there are many cases when we need to use functions to process some data and give us the result. As in most languages, Go lets us return data from our functions. As with many things in Go, however, we have several options to choose from. First, we can do what most languages support and simply return a single value from the function. No big surprises here. However, we can also return multiple values from a function, a technique often used to return a value and a flag indicating if the function was successful or not. Finally, we can provide names for the returned values in the function signature, a technique that when properly used, can make the function more readable. To demonstrate return values, let's create a classic function that simply adds up the numbers provided to it and returns the result. We'll use a variadic parameter to allow any number of values to be provided and indicate that we are going to return an integer by specifying the return type after the parens and before the opening curly brace on the function signature line. Within the function body, I need to initialize a result variable and loop through each term, adding its value into the current total. Finally, we can return the result by using the return keyword followed by the result variable. Let's try this function out by calling it in the main function. I'll capture the value in a result variable and print it to the console. Let's run the application and, sure enough, we get the expected result out of the function. Cool. For many languages, we'd be done with this section since that's all there is to returning data from their functions. However, in Go, we have a couple more tricks that we can explore. The first one is that Go lets us return multiple results from a single function call. To see that in action, let's extend our function to return the number of terms that was given as well as the sum. To start, I'm going to add a paren in front of the return type, and to the second return type, and then place a closing paren after them. Our function signature is now expecting us to return two integer values from this function. To fill that expectation, I'm going to return the value of the terms slice by using the built-in len function. Now we need to update our main function to work with both return values. That is easily accomplished by providing two variables to catch the results of calling the function. Finally, let's update the print function to tell the user how many terms the function received and what their sum is. Now if we run the program, we'll get a lot more information about what the function did. This pattern is useful in many scenarios, but the most common one that you'll see in Go is to return an expected value and an error term that can be inspected to ensure that the function executed normally. The final trick that I'd like to show you is how to use named return values. In some cases, this can improve the readability, and therefore maintainability, of your code. Using named parameters is pretty easy. All we have to do is put the desired names in front of the types in the function signature. These are initialized to their zero values upon entry into the function. With those in place, we can remove this result variable and replace it in the loop with the sum variable. After the loop, I'll assign the numTerms variable the expected value and we are ready to exit the function. In order to do that, we still need to provide a return statement but, this time, we don't need to provide the returned expressions. Running the application again proves that we didn't change any of the behavior but we've increased the readability of the code by being explicit about what each integer is intended to return.
-
Anonymous Functions
We're almost done covering the high points of functions in Go, but there's one thing that I'd like to touch on before we leave. As I mentioned back in module one, Go supports functions as first class citizens of the language. This means that they can be assigned to variables and passed around in your applications without needing to be wrapped in some sort of container like a class. This opens up a lot of flexibility in your application development. Unfortunately, we can't dig too deeply into this right now but I'd like to introduce you to it by showing you how to create an anonymous function. We're back in the example from the last clip where we used named parameters to return values from our add function. I'll start by creating a variable called addFunc. I'll initialize to the add function that we have down here. By simply cutting and pasting the function and eliminating the function name, we now have a function defined as a variable. We can then use that function by invoking the addFunc variable exactly the same way that we called the function earlier. This is just a taste of what can be done with the fact that Go's functions are independent types. I can virtually assure you that as you go deeper into Go, you'll find this aspect of the language to be very useful and a powerful tool to have in your arsenal.
-
Demo: Using Functions to Improve Code Structure
When we left our Gopher Power and Light report in the last module, we had two reports that were working pretty well. However, since our main function contained all of the application's code, its responsibilities were all over the map. Let's use what we've learned about functions to re-factor the code and improve its structure. The first concern that I have with our program is that the logic of the report is right in the switch statements. Let's start to fix that by creating a new function that will generate the plant capacity report. We'll have it accept a variadic parameter of float64s that represents the capacity of the plants. For the implementation, all I need to do is cut the code out of the first case and drop it into the new function like so. I'll then replace that code in the case with a call to the newly created function. Well that wasn't too hard. Let's do the same thing with the second report. Once again, I need to create a new function to generate the grid load report. This report requires a bit more information than the first one. First, we need the slice that represents the active plants. Next, we're gonna need the capacities of the plants themselves. Finally, we need the current load on the grid. Once again, we aren't gonna need to return anything from this function so we can just finish off the signature and drop in the matched curly braces that contain the function body. To fill the body, we just need to grab the code from the second case block and drop it in, just like we did before. Now we can call the new function to generate the second report for us, passing in the required parameters. Okay, let me save the file so that we can check everything. Oops, looks like we have an issue on line 23. Yeah, I forgot that Go needs us to split incoming slices into their individual components when being passed into a variadic function. That is done by adding three periods, sometimes called the spread operator, on the end of the slice here. Okay, let's try saving that again. Looks like everything's in order so let's jump to the command line and try it out. Well, looks like the first report is working as it did previously. Let's try the second one out. Yep, everything seems to be working there. Well, that cleans up the main function quite a bit. I don't like having that menu logic in there though. I'll tell you what, I'll refactor that out to its own function and include that in the source files. If you're following along, try to get there on your own and then check the files when you are done to see how close we are.
-
Summary
Functions are, in many ways, like a factory in your applications. You provide raw materials in the form of parameters to them and then you get a product out in the form of the returned values. In the module, we learned about how to pass parameters into a function. We looked at two ways. Passing by values simply copied the data from the calling function and did not allow us to change it in a way that was visible to the caller. We also passed data by reference by using a pointer to the value and, as a result, were able to manipulate the variable. We also explored how to make the final parameter in a function accept an arbitrary number of parameters and thus, created a variadic function. On the results side of the function, we learned how to return single and multiple values out of the function and how to use named return values, a technique that can significantly improve the readability of the code in certain situations. Finally, we touched on anonymous functions. Although we only saw the tip of the iceberg in what functions as first class citizens can do for us, as you dig further into Go, you'll come back to the concept of passing functions around quite often as a way to modularize and improve the structure and re-usability of your functions. In the next module, we're gonna take a tour of the object-oriented concepts in Go. While not as dominant in Go as in other languages, objects are a vital part of the language when you need to bind functionality and data together. Until then, this is Michael Van Sickle for Pluralsight.
-
Object-oriented Programming
Introduction
Hello, welcome back to Go: Getting Started. In the last module, we talked about functions, and how they can improve the structure of our applications by separating the logic of the application into purpose-built functions. Now it's time to introduce how Go handles use cases in which functionality and data need to work together. While many modern languages use the concept of a class to do this, Go, unsurprisingly, solves this problem in its own way. We'll start our discussion by talking about structs, which are a kind of container for arbitrary types. We'll then move on to discuss several ways that a struct can be created, and when each should be used. After that we'll move on to how to bind functions to run in the context of a struct, in other words, how to create methods. Finally, we'll end the module with a review of to use object composition to simulate traditional inheritance, and discuss why this model was selected.
-
Structs and Fields
To start out our discussion, we need to cover the basics of objects in Go. If you have experience with other languages, you're probably expecting a discussion about classes. Well, Go has a different take on object orientation than those languages do. So it uses the term struct to represent the same basic concept that classes do in other languages. Keep in mind though, structs are not just another name for classes. They are different things, which we'll get further into as we go through this module. To start, let's consider a struct to be a container that holds arbitrary data types called fields. Let's start by seeing how to work with these. The first thing we need to do is create a struct. We start that by using the type keyword to indicate that a custom type is coming. Next comes the name of the type. Let's start with an unimaginative name like my struct. Finally, we have to tell Go what kind of type we are creating, which for now, will be a struct. Our next step is to define what kind of data the struct will contain. We need to wrap that in matched curly braces with one field per line. For now, we'll add a single field by entering the name of the field, and its data type. There are a lot of ways to create an object based on the struct we defined, so let's take them one at a time. The first way that we can go is to create an object on the local memory stack, by simply instantiating a variable to equal the struct's name, followed by matched curly braces. To assign a value to the field, I'll enter the object's name, followed by a dot, and the name of the field. After that, we'll use the assignment operator, and enter the value that we want to store. To prove that everything is working as advertised, let's print the field's value out to the console. Okay, everything seems to be working so far. Now let's play around with some other ways that we can construct an object. The first change that we can make is to use Go's composite literal format. We've already done that a few modules ago with slices by putting the initialization data inside of the curly braces after the type name. For a struct, things are a bit different. We have to add the data in the order of the fields in the structs, like so. By doing that, we no longer need to assign the value of my field on its own line, so we can remove that. And there you have it, a simpler, cleaner way to create an object. The two methods that we've looked at will create an object in the local execution stack. While that isn't as big of a deal in Go as in other languages, it is often better to create large objects on the heap instead. To do that, I'm going to use the built-in new function, and wrap the type's name. Unfortunately, this syntax doesn't allow us to use a composite literal, so I'm going to have to add this statement that sets the field's value back below. As you can see, everything works as it did before. If you've used pointers a lot in the past, this might be blowing your mind a bit. Foo is in fact, simply a memory address, which we can prove by printing its value out to the console. You might have expected us to need to dereference the pointer in order to work with it. It turns out that the creators of Go have taken care of that for us. When the compiler runs into something that looks like it's working with the pointer's reference, it will automatically dereference the pointer for us. This little feature makes it much more pleasant to work with reference data types. To compare that pointer, let's change foo back into a my struct type directly, and then print it out. Oh, it appears that this is a bit much for the built-in print ln function to handle. Let me quickly import the FMT package, and ask its print ln function to take care of this for us. Okay, there we go, as you can see, we get an output that looks like the composite literal that we used a bit earlier. In order to compare apples to apples, let's go back and create foo as a reference type again. Now you can see we have an ampersand in front of the literal, indicating that it's a reference type. There is one more way that I'd like to show you how to create a reference type. Instead of using the new function, we can use an ampersand in front of the type. This has the advantage of allowing us to use the composite literal syntax again. As you can see, the output is exactly the same as what we had with the new function. Once again, this might cause you C++ programmers out there some confusion. In languages like C++, returning a reference to a local variable is a sure way to create a bug in your application since the memory address can be overwritten at any time after the function exits. Once again, Go takes care of us by keeping track of the variable, and what has access to it. If anything is still referencing it, Go will not garbage collect it, even if the function finishes executing.
-
Constructor Functions
In many object-oriented languages, methods are created inside of the class definition. Often, there is a special type of method called a constructor, that allows the object to be set up while it is being created, so that the users of the object don't have to worry about initializing variables, et cetera. Well, Go doesn't use methods like this, and, by extension, doesn't have the concept of a constructor. Instead, plain old functions are tapped to work as object constructors. These so-called constructor functions aren't special in any way. But they are a common pattern in Go, and really the only way to do what a constructor does in other languages. In order to see why constructor functions are useful, let's go through an example. I'm gonna start by creating a simple struct, like we've done before. Once again, I'm going to give it a single field, but this time I'm going to make it a map of strings that are keyed on strings. Now that we have that defined, I'm going to create an instance of the struct in the main function. Next, I'm going to create a key value pair in the struct's map, and then print the whole object of the console. Running the application reveals that we have a bit of a problem. The assignment of the key value pair on line seven failed due to the fact that the map has not been initialized. If you'll remember, maps have to be initialized before they can be used. Of course, we could do that after line six, but that would mean that every consumer of this struct would have to know how to set up objects that it creates from this struct, and would bleed implementation details out to the rest of the application. Instead of doing that, we're gonna create a function whose only purpose is to create a properly-initialized object, and return it to the caller. Functions that have this responsibility are called constructor functions in Go. To create this, let's create a new function. By convention, the name of a constructor function is the word new, followed by the name of the struct that it is initializing, as you see here. The return type could be a struct, or a pointer to a struct. Normally I return a pointer, since that is generally the better way to create objects that are going to have a non-trivial lifespan. Inside of the function, we don't need to do anything fancy. I'll start by creating an instance of the struct. Next, I'll initialize the map, and then return the struct. Notice that I'm using the ampersand, so that I return a reference to it as indicated by the function's return type. Up in the main function, I simply need to change out the direct initialization of the struct, with a call to the constructor function. If I run the application now, everything works as expected. This is just a simple example of when a constructor function is very useful, but this isn't all they can do. Constructor functions are often designed to accept parameters that allow the fields of the created objects to be initialized to values determined by the callers as well.
-
Methods
So far, the only things that we can do with objects are store data in them. While this is useful, and many of your structs will be used just for that purpose. It is often helpful to be able to create methods that execute in the context of an object. Let's take a look at how we can do that in Go. I'm going to begin like we have before, by creating a simple struct with one field. This time, I'm going to call the struct message printer, which, within a couple of minutes, it'll hopefully be able to do. The struct's field will hold the message that we're going to try and print. We can, of course, do what we've already seen and create an instance of the struct, passing in the message using the composite literal syntax. Of course, we can then print the fields using the print ln function. This works, but we are forcing the main function to decide how to go about printing the value out. In a simple program like this, it isn't a big deal. But it would be a major problem if our struct was going to be used throughout a larger application. What I wanna be able to do is something like this. I wanna be able to start with the object, add a dot, and then be able to execute some sort of print message method that would handle the details of what printing a message means. To do that, I'm going to drop below the struct's definition and start to create a new function. After the func keyword, I'm going to introduce something new. Inside of parens, I'm going to enter a variable name, and give it the type of a pointer to the struct. After that, the rest of the function signature will be the same as what we've seen previously. That new element between func and the function name is what determines the context of the function, turning it into a method. Within the method, we can then replicate the printing logic that we've used in the main function above. While this syntax might look a little odd, there are several advantages to it. First of all, the data in the struct is very clearly separated from the methods. Also, new methods can be added to the struct from anywhere within the package. While it's generally gonna be best to do that in the same code file, there might be times in which that functionality can be useful.
-
Object Composition
One of the traditional pillars of object-oriented programming, is the concept of inheritance. Basically, a class is set up to inherit functionality from a parent class. While this is a powerful concept, it's easy to abuse, leading to maintenance headaches as you dig through very deep inheritance trees to find which function is actually being invoked. The designers of Go decided that it should not support this pattern, and decided to use an alternative method of sharing base functionailty, a technique known as composition. Instead of a class, or a struct as we use in Go, inheriting in a parent-child sort of relationship, Go allows source structs to be included inside of the struct that wants to take advantage of the functionality of the first struct. This gives Go most of the benefits of inheritance, but makes it much more explicit about where a certain bit of functionality is coming from. To demonstrate how to use composition in Go, I'd like to start with the last batch of demo code that we worked with. Let's imagine that we wanna create some sort of enhanced message printer that wants to use the field and method of the current message printer. In order to do that in Go, we need to start by defining a new struct. Inside of that struct, I'm going to add an anonymous field with the message printer's type. And that's all there is to it! Since there is no field name, Go is going to treat the fields and methods of the message printer as if they were directly part of the enhanced message printer. Well, that isn't entirely true, as I'll show you now. Let's change the message printer in the main loop to an enhanced message printer, and try and run the application. Uh-oh, looks like we have a problem. The error message is interesting though. Basically what it's saying is that we can't use a string to initialize a message printer struct. Okay, let's put a pin in that for a second and try something else. I'm going to remove the composite literal initialization, and try to assign the message field on a separate line. I'll also quickly update the variable names so I don't get confused. Okay, well the application seems to work now. What's going on? Well, it turns out that the composed struct, that is, the message printer, is not exactly promoted to first class citizen's status, in all cases. This is because there may be situations in which you need to work with the anonymous type directly, such as when composing in two structs that both provide the same method, and you wanna be able to be explicit about which implementation is used. Mostly, these scenarios are hidden within the struct, and the external consumers of the struct never have to know about this stuff. That's what caused the error before. The composite literal needs to initialize the anonymous struct itself, not a field that has been composed into it. To do that, we can simply provide an initialization of the message printer struct, inside of the curly braces of the enhanced message printer. While this works, it is not ideal, since it requires the consumers to understand the internal structure of the enhanced message printer, which is generally a no-no in object-oriented design. Instead, we would be better to have a constructor function create the enhanced message printers for us, so that everything is nicely hidden away in a single, easily-maintained function.
-
Demo: Combining Data and Functions with Structs
I've been waiting a long time to get to this point. While I think we've been able to do a lot with what we know, our Gopher Power & Light project is really begging for structs to allow us to get something that represents the different information that might be related to a power plant. Also, using methods to generate the reports will allow us to encapsulate the method for generating them within the object definitions, and thus, make them more portable and reusable. As you probably remember, we left our project with most of the functionalities split out into sub-functions. However, notice that we still have the data in the main function, since we didn't really have any other place to put that. My goal is to create two primary structs. One to represent a power plant, and another to represent the power grid, including all of the plants that are available to feed it. Before we do that though, I want to introduce you to a little trick that you can do with types. When I think about a power plant, there are three pieces of information that make sense to have associated with it, in the context of our goals. Of course we need the capacity that we've been working with, using the plant capacities slice. It also makes sense to have a plant type, such as hydroelectric, wind, or solar. Finally, some sort of status indicator would be nice to tell us if the plant is currently being used, or if it's unavailable. In many languages, we would use an enumeration to represent the type and status. However, Go doesn't have a unique enumeration type. To simulate that, we can create a simple type alias, and use that to represent the different values that are possible. To create one of these, I'm going to start with the type keyword again, then follow it with the unique name, just like we did with a struct. Finally, I'm going to add a data type that we want to alias, a string in this case. Next I'm going to use the plant type to create a group of constants that are initialized to that type. Let's create three different plant types, one for hydro plants, one for wind plants, and one for solar plants. Next we can repeat that process for the plant statuses. Once again, I think three statuses make sense. If a plant is active, then it is contributing power to the grid. Conversely, if it is inactive, then it isn't adding power to the grid. Finally, I wanna have a status of unavailable, which would indicate that a plant is not able to be activated, perhaps because it's undergoing maintenance. Now let's create the struct to hold the power plant definition. As I said, that will contain three different fields. One for the plant type, one for the capacity, and one for the status. With that in place, let's create another struct to describe the entire power grid. In our scenario, the power grid is described by two things. The load that is currently on the grid, and the power plants that will meet that load. The next thing that I'd like to do is create a method on the grid that will generate the power plant report. Now that we have more data about each plant, we can make the report quite a bit richer than what we had before. I'll start by looping over each of the plants that are in the power grid's plants field. Within the loop, I'm going to create a local variable to hold the title of each of the power plants. Next, I'll print that formatted string out to the console. For the next line, I need to get a little help from another package, so I'm going to jump to the top of the file and import the strings package that provides quite a few tools to manipulate strings. With that available, let's drop back down to the bottom of the file, and start to print a new line. For the contents of this line, I'm going to use the repeat function from the strings package, to repeat a series of dashes that is the same length as the current plant's label. The rest of the report is basically the same as what we've done before, so I'm just gonna drop that in. As the last addition, I'll add an additional line break to separate the plants from each other. With all of that defined, let's change our main function to take advantage of it. To start, I'm going to drop in a slice of power plants. As you can see, we have one big hydro plant, a few small wind turbines, and a couple of solar plants as well. I'd like to point out something about this version of the composite literal syntax. Since I'm using multiple lines, I have to terminate each one with a comma, even the last one. This is due to the fact that Go will falsely identify the last entry as the end of a statement, and generate a compiler error. By adding a trailing comma, it gives the compiler a hint that the statement isn't done, so everything works as expected. Next up is the grid itself, we can get away with a single-line instantiation in this case, providing the grid load and the slice of power plants that we just initialized above. Now it's time to update the switch statement to use the grid object to print the first report. We can do that by calling the generate plant report method on the grid object. We don't have the code to generate the second report yet, so I'm going to drop in a to-do so that we remember to come back to that later. Okay, time to fire this up, to do that, I'm going to jump over to the command line, and run the application. If I run the first report, you see that we have a much richer result than we've had before, due to the ability of a struct, to group date it together, that describes an object. Let's finish this demo up by creating a method to handle the second report. If you'll remember, we changed the way this worked a couple of modules ago, by adding only active plants to the grid. At that point, we had to use a second slice to hold the index of the active plants. Now that we've bound the status into the power plant structs this is going to be a lot easier. Inside of a loop that is iterating to the plants, I'm going to check if the plant's current status field is equal to the active constant that we defined above. If it is, then I'll add its capacity to the running total. After the loop is done, I'm going to set up a label variable, and use it like we did in the first report, to print the label and then underline it with a series of dashes, using the string package's repeat function again. Once again, I'm gonna drop in the rest of the report, and then save everything to allow the editor to check us. Hmm, looks like we have another issue. Yup, I did it again, I've initialized the capacity to a zero, with no trailing decimal. As you'll remember, that is interpreted as an integer, not a float 64 like we've used for the plant capacities. All I need to do is add a period here, and we should be set. Yup, everything is working now. As a final step, let's go back to the main function, and use a new report method in the second case statement. With that in place, we should be set to run the application. Running the application shows that we've achieved our goal of using structs and methods to dramatically improve the organization of the application's code.
-
Summary
In this module, we reviewed quite a few of the key concepts involved with object-oriented programming with Go. While not strictly relegated to object-oriented programming, structs form a flexible data container that serves as a cornerstone for objects. Don't forget though, that they are very useful on their own, even if you don't combine them with methods. Next, we discussed fields, the data elements that store data within a struct. After that, we talked about the problem that we run into when we create fields that need to be initialized, such as a map, and how constructor functions are commonly used to create objects that need some kind of setup in order to be fully-functional. We then moved on to discuss how methods can be bound to a custom type in order to associate functionality that runs in the context of that type. Finally, we touched on composition, and how Go uses that instead of inheritance to allow types to borrow functionality from one another. In the next and final module, I'll give you a brief overview of how to do concurrent programming in Go. While this is really an advanced topic, and would require a full course to do it justice, this is one of the primary strengths of Go, so I didn't wanna leave this tour of the language without at least showing you around the powerful concurrency model built into it.
-
Asynchronous Programming
Introduction
Hello, my name is Michael Van Sickle. Welcome to the bonus round. In the first modules of this course, we learned about the key constructs that are involved in creating the code the resides within a function. We then moved on to learn how functions and objects can improve the structure of our applications. Armed with that knowledge, you are set to start creating world-class applications. However, there's one really cool part of the language that you'd still be missing: how to deal with concurrency. In today's world, applications often need a way to efficiently use hardware that exposes multiple CPUs to the application. Fortunately for us, Go has primitives built into the language that make addressing this challenge much easier to manage. In this module we're gonna touch on how to call functions asynchronously using two tools that Go offers us. Goroutines are the name given to Go's threadlike constructs. Whenever you need to launch a portion of code that runs concurrently, you're gonna turn to goroutines to do that. Channels are the other tool that you're probably going to need. While goroutines are by definition required for asynchronous programming, channels are designed to address one of the most difficult challenges of this type of program, sharing data between threads. They do that by including machinery that handles all of the synchronization of the sender and receiver threads for you. This allows you to write most of your application as if it were not running concurrently, and use channels to bridge the gaps between goroutines.
-
Goroutines
The first thing that I'd like to talk about are goroutines. Before we get into that, however, there's something very important that I'd like to discuss first. Concurrency is not the same thing as parallelism. Let me illustrate it with this example. Both of these boxes illustrate applications that have multiple active functions indicated by the horizontal lines. This is the definition of concurrency. The application has multiple things to do at the same time. For the application on the left, let's assume that we are running with a single CPU. In that case, the Go runtime will schedule each function a certain amount of time to work and then move on to a different function. Eventually, everything gets done, but only one thing is worked on at a time. An application that is running in parallel is a bit different. In that case, the concurrent application has multiple CPUs available. In that case, the scheduler has a pretty easy time. It will assign each task to a CPU and allow them to run to completion. Now that we've covered that, let's look at some code that illustrates both concurrent and parallel programming in Go. Let's start by creating a function that prints the lowercase English alphabet to the console. While this wouldn't be that easy to internationalize, it will serve our purposes nicely. Oh, you might've noticed that I'm doing this demo in Atom. The reason is that I wasn't having much luck getting the Go playground to run on multiple CPUs, so I needed the additional control that a local development environment provides. Anyway, our abcGen function is gonna be pretty simple. We'll start with a byte value that represents the lowercase a, by doing an explicit cast of the character to a byte. We didn't cover explicit casting, but as you can see, it isn't that hard. We simply enter the type that we need to cast to, and pass in the value that needs to be cast inside a parens. Yes, I know, once again. Go is backward from traditional C syntax, but I have fallen in love with this style since the traditional C style always seems to require a lot of parens to get it to work properly. Inside of the loop, we can print the value of the current string by casting the byte, which is an integer type, back into a string via another explicit cast. To begin I'm going to run the application on the main thread by just invoking it, like we've done before. Running it shows that we, indeed, are able to print out the English alphabet in order. Awesome. To make this application call into a goroutine, we simply add the keyword go in front of the invocation, and we're done. This will tell Go to run the abcGen function concurrently while the main function executes. Running the application is a bit disappointing, however. Nothing is printed to the console. What do you think happened? It turns out that the main function in the main package is a bit more important than I've mentioned previously. Before, I told you that it represents the entry point to the application, and so every application must have one. Well, it is also happens to indicate the application's exit point. When the main function is done, the application terminates. In our example, the main function schedules the abcGen function to run, but immediately terminates. That means that the application exits before the function ever gets a chance to run. The simplest way to fix that is to put the main function to sleep for a bit. This will allow Go's scheduler to start running any other pending tasks, such as our goroutine. To do that, I'm going to pull in the time package and then call its sleep function. This function requires a single parameter that defines how long we want the function to pause for. For now, I'll just have it stop for 100 milliseconds, which should be plenty of time to let the abcGen function finish. If we run the application again, we see that everything is working as expected, but the work is being done by our goroutine, not the main function. I know that getting your mind around asynchronous programming can be a little bit tricky, so I'd like to introduce another println call in the main function to further illustrate how this is working. If I run the application and then jump to the top of the output, we'll see that the "This comes first!" message is printed first. This is because Go will finish the current function until it either terminates or pauses for some reason, such as hitting a sleep call. OK, the next thing that I'd like to do is show you that, while we have a concurrent program, we aren't really taking advantage of how goroutines have been designed. Goroutines do not map directly to processor threads of the operating system, which tend to be a bit expensive in terms of memory. Instead, goroutines start out very, very small in order to allow applications to work with thousands of them at a time. This means that we can turn this println call in the abcGen function into a goroutine as well. In total, this should give us 28 goroutines in our application, one per letter of the alphabet, one for the abcGen function invocation, and one that the Go runtime creates for the main function itself. Once again, if we run the application, everything works as before, but we've given Go a lot more opportunities to optimize the application internally. We've also set ourselves up to take advantage of multiple CPUs. To do that, I need to import another package. By default, Go programs only run on a single CPU. The runtime package contains a function called GOMAXPROCS that allows us to alter that default. Let's change it to run on eight CPUs, and see how this affects our program. Oh my. That is a mess. With each goroutine being scheduled across all eight of the machine CPUs, the output is no longer in the order that we expected. As a matter of fact, this guy here indicates that a goroutine was paused right in the middle of a println call, after the letter was printed, but before the new line was added. Welcome to the wonderful world of parallel programming. In this next clip, we'll take a look at channels and how they are going to allow us to keep using multiple CPUs, but also allow our application to retain the expected behavior.
-
Channels
In many languages, I would have to follow that last clip with a fairly lengthy discussion about how to manage multiple threads in an application. Fortunately, we aren't using those languages, so this is going to be quite a bit easier. Channels are a construct in Go that allow messages to be passed between two goroutines in a manner that ensures that the data is safely transferred between them. That means that only one goroutine at a time owns the message's contents and is able to work with it. This makes sharing data pretty simple, since all of the synchronization of threads and determination of ownership is buried within the channel, so you don't have to worry about it. There are two things that I'd like to show you about channels. First, we'll look into the basics of constructing a channel, as well as how to send and receive messages through them. After that, I'd like to show you another usage of the for range loop that is specifically designed to work with channels, that will be receiving messages for an indeterminate period of time. We're back in the editor again with the code that we had at the end of the last demo. If you'll remember, we had a small issue with the code when we allowed multiple CPUs to be scheduled. Without any way to control how the code executed, the output was a mess. Let's create a channel to help us out. With their ability to ensure that messages are delivered safely and in the order that they were generated, they should be just the thing to get things organized again. To create the channel, we have to use the built-in make function. Next comes two parts. The chan keyword tells Go that we are creating a channel, and then we need a data type, since channels in Go are strongly typed, like almost everything else. Next, we'll pass the channel into the abcGen function. Of course, we need to update the function's signature to accept the channel. Next, I'm going to remove the goroutine with the print statement, and replace that with the channel, followed by this operator, called the receive operator, followed by the message that we want to pass, which will be the string that we printed before. Notice how the arrow looks like we are taking something from the right and putting it into the thing on the left. Yeah, I think it's a pretty good choice too. Next, we need to create another function that will receive the message that we put into the channel. That function needs to accept a channel with the same signature as the generator function above. Within the function, I'm going to create an infinite for loop that will contain a println function that accepts the message coming out of the channel. Don't worry too much about the infinite loop. The goroutine will stop executing when it is waiting for a message, so we aren't gonna be spiking CPUs by doing this. Having said that, we'll change this to a better approach in a minute. For now, let's call the printer function as a goroutine up in the main function. Now, let me run the application and see what happens. OK, it looks like everything is back in order. Let's see what we can do about that infinite loop now. To start, I'm gonna go back into the abcGen function and use the built-in close function to close the channel. This makes the channel unavailable to receive new messages, which we'll take advantage of next. In the printer function, we'll change the infinite loop with a for range construct, but instead of looping over a collection type, we'll use the channel instead. This form of the for loop will wait for a message to come in from the channel, and when one arrives, the loop's code will be executed. The loop will exit when the channel is done sending messages, which is determined by detecting if the channel is closed. The final change that I'd like to make is to get rid of the sleep method in the main function. This is a bit hacky, and definitely not something you wanna do with production code. Instead, let's create another channel that will let us know exactly when the goroutines are done. The message type isn't really important when using a channel like this. The fact that a message is passed is what's critical, not what the message is. The next step is to pass the new channel into the printer function, and update its function signature to accept the new parameter. After the for loop exits, we'll pass a message into the done channel. In the main function, I'm going to replace the sleep function with a call to receive the message from the done channel. Since channels stop the function that they're in until a message is received, the main function is gonna stay alive until the printer function sends the message that signals that it is complete. Since we aren't using it any more, we need to remove the time package, and then we should be able to run the application. Well, there we have it. By using channels, we've been able to ensure that our application runs in a predictable manner, while at the same time taking advantage of all of the CPUs that are available.
-
Summary
In this module, we covered the two primary constructs that are used for concurrent programming in Go. Goroutines are the threadlike constructs that are used to kick-off asynchronous tasks in a Go program. These are higher-level primitives than processor threads and, as a result, give more flexibility and make it easy to work with hundreds or thousands of goroutines at the same time, without worrying about the threads sucking up all of the available memory. Channels are used to address the persistent problem of shared memory in concurrent applications. By handling the synchronization and ownership of transmitted messages, they make it easier to safely share data within a massively concurrent application. Well, that just about wraps it up. I hope that over the last couple of hours I've been able to convince you that Go is definitely worth looking into further. Its combination of powerful built-in primitives, fully compiled nature, and simple syntax make Go a nice addition to the pantheon of programming languages. Until next time, this is Michael Van Sickle for Pluralsight.