Go (Programming Language)

Random things I'm learning

Reviews of the language

  • 2015.04.21 - Evan Miller - Four Days of Go
  • 2017.08.22 - Michal Konarski - 5 things about programming I learned with Go
    1. HN discussion
    2. It is possible to have both dynamic-like syntax and static safety
      1. Go is not an object-oriented language. But it does have interfaces. And they are pretty much the same as these you can find in Java or C++. They have names and define a set of function signatures.
      2. Then we have Go’s equivalent of classes - structs. Structs are simple things that bundle together attributes.
      3. We can add a function to a struct.
      4. Go uses a concept called “automatic interface implementation”. A struct containing all methods defined in the interface automatically fulfills it. There is no implements keyword. Isn’t that cool? A friend of mine even likes to call it “a statically typed duck typing”. Thanks to that feature and type inference that allows us to omit the type of a variable while defining, we can feel like we’re working in a dynamically typed language. But here we get the safety of a typed system too.
    3. It’s better to compose than inherit
      1. if we want to mitigate the risk of getting lost inside the dark forest of code complexity we need to avoid inheritance and prefer composition instead.
      2. Go doesn’t support inheritance at all.
      3. There is a feature called embedding. Every method in an embedded interface is accessible directly on the struct that the interface is embedded in.
    4. Channels and goroutines are powerful way to solve problems involving concurrency
    5. Don’t communicate by sharing memory, share memory by communicating.
      1. Instead of using locks to control access to a shared resource, Go coders can simply use channels to pass around its pointer. Then only a goroutine that holds the pointer can use it and make modifications to the shared structure.
    6. There is nothing exceptional in exceptions
      1. there is nothing exceptional in exceptions. They are usually just one of possible return values from a function....it’s good to think about exceptions like they were regular return values. Don’t pretend that they just won’t occur.
      2. f, err := os.Open("filename.ext")
  • Quora - Why did Google create the Go language? Isn't Python good enough?
    • "Python is a dynamically typed language, and as such it can present, er, challenges for working on large programs, in large teams. A quick example, is that if you make a function in Python to call, and call it from a few places, you’ll find that if you change the number of parameters, or types of parameters that the function takes, there is no compile time error, only a runtime error. Now that’s no big deal on a small program of only a few hundred or even few thousand lines, but once you go up to hundreds of thousands of lines, or millions of lines, working with hundreds of other people, it’s a major problem.Statically typed language like Go turn that runtime error into a compile time error, and it’ll point out each and every time that function is called with the wrong parameters. This is a huge difference, when working on medium or large scale projects."

    • "Python is very good at making programming simple and easy, but it suffers a performance hit compared to compiled languages. Compiled languages such as C or C++ are very fast, but they're not as simple and easy to use as Python. Go aims to be almost as easy to use as Python while being compiled, and almost as fast as traditional compiled languages. It's also very good at concurrency by design, which is its main strength."

  • Go FAQ - Why are you creating a new language?
    • We wanted the ease of programming of an interpreted, dynamically typed language...
    • ...and the efficiency and safety of a statically typed, compiled language.
    • Finally, working with Go is intended to be fast: it should take at most a few seconds to build a large executable on a single computer.

Cool Go projects

Libraries / frameworks

Learning resources

  • Go vs. Python for Senior Developers
    • This is great.
    • Summary:
      • Similarities to Python:
        • A large standard library.
        • Automatic memory management.
        • A huge ecosystem (libraries, frameworks, IDEs, and other tooling).
      • Differences to Python:
        • Go is a compiled language
        • Better performance
        • Easier deployment and cross-compilation
        • Go is statically-typed
        • Go supports call by reference (via pointers)
          • "Python is call-by-reference for dicts/lists/objects but doesn't support call-by-reference for primitives."  But so what?  I haven't run into a situation where that mattered.
        • Go doesn't have classes or "class implements interface" syntax
        • Go (officially) does not support sub-classing / inheritance
        • Go does not have exceptions
        • Go uses defer instead of try-finally - It's just different syntax.
        • Go has decentralized dependency management
          • import statements in Go typically look like this: import "github.com/some-user-or-org/some-go-module/some-go-package-that-you-need"
          • The equivalent to Python’s requirements.txt is Go’s go.mod file.
        • Go has excellent support for concurrency
          • "Goroutines are a bit like threads, but much more efficient"
          • "Python’s support for concurrency is abysmal. While you can have multiple threads, they won’t make proper use of your CPU cores (due to the Global Interpreter Lock). If you want to use multiple CPU cores, you must fork extra processes."
        • Go comes with built-in, opinionated formatting
  • https://golang-for-python-programmers.readthedocs.io/en/latest/
    • This seems unfinished(?)

Go: The Complete Developers Guide

  • https://www.udemy.com/go-the-complete-developers-guide/learn/v4/content
  • This was the highest-rated intro to Go on Udemy.
  • I liked it, I would recommend it to anyone who wants to learn Go.
  • I think the main thing I found less-than-ideal about it was that I'm already an experienced Python developer and so I found the pace slower than it needed to be for me.  But I can see how this pace would be perfect for someone who isn't as familiar with programming.  So basically it doesn't leave anyone behind.  But I feel like I would've benefited from seeing him digging through some more-complicated programs, like the Axis & Allies simulator, to get a sense of how to work with stuff like that.

Summary of key ideas from the course I want to remember

  1. An executable package (in contrast to a non-executable library) must be named main and have a function named main.
  2. Hello World:

    • package main
      import "fmt"
      func main() {
          fmt.Println("Hi there!")
  3. Use go run <files-to-compile> to run your code.
  4. The := syntax is an abbreviated syntax used when defining a variable for the first time; it has the variable infer its type from its initial value (which must be assigned on the same line).
  5. Instead of using the Python syntax for creating classes, you create types (which have no inherent methods attached to them), and then to create methods you create receiver functions (functions which only accept the new type as one of their arguments).
    1. Example type declaration: type deck []string
    2. Example receiver declaration: func (d deck) print(<args go here>) { <code goes here> }
      1. Note how the receiver part is in front of the function name and separate from the parameter list.
    3. Example receiver usage: cards_variable.print()
  6. A lot of learning Go is learning how the various packages in the standard library work.
  7. Go has arrays (fixed-length) and slices (variable length, similar to Python lists):
    1. Create an empty array: cards := [<size>]string
      1. Create an array with initial values: cards := [<size>]string{<put-initial-contents-here-if-you-want>}
    2. To create a slice just omit the <size>: cards := []string{<put-initial-contents-here-if-you-want>}
      1. Append to a slice: cards = append(cards, newElement)
    3. Selecting a subarray/subslice works just like Python.
    4. The code []byte is read as "slice byte", not "array of bytes".
    5. Arrays are rarely used directly; slices are used 99% of the time for lists of items.
  8. For loop syntax: for i, card := range cards { <do-stuff> }
    1. If you're not going to use the iterator replace its name with an underscore: for _, card
  9. Unlike Python, you don't need to add import statements for stuff that's defined in the same package but in separate files.
  10. To convert between types, write the type you want and put the object to convert in parentheses after it: []byte("Hello World!")
    1. This is similar to Python, where you have str(), int(), class constructors like MyClass(), etc.
  11. When importing multiple packages in a file, use this syntax:

    import (
        "package name one in quotes"
        "package name two in quotes"
  12. Golang uses nil, not null or None.
  13. You can do Println(err) to print an error.
  14. To immediately quit a program we can call the os package function Exit() with an argument of 1 which indicates that something went wrong.
  15. We can use len() to get the length of a slice.
  16. Random number generation is kind of complicated in Go.
  17. Testing:
    1. Make a file that ends in _test.go and then run go test
    2. The first argument to every test function should be t *testing.T
    3. Call t.Errorf("Description of problem") to notify the test handler of a failure.
  18. Structs are like dicts or named tuples or classes-without-methods in Python: a collection of attributes describing a single thing.  The values can be changed afterwards, unlike named tuples.  The fields can't be added to afterwards, unlike a dict.
  19. Pointers:
    1. Go is a "pass-by-value" language by default.  It will copy the object and pass the copy to a function.
    2. To create a pointer: myStructMemoryAddress := &myStruct
    3. To dereference a pointer: myStruct := *myStructMemoryAddress
    4. A potentially very confusing thing: When you see * in a type description, like in a list of function parameters, like *person, it isn't being used as an operator. So the variable name you'll see to the left of the type description is still just a pointer instead of a dereferenced pointer.
    5. If we define a receiver function of type pointer-to-X, Go will allow us to call it either with a pointer or with the referenced type itself. Basically letting us skip defining a pointer variable when calling the function if we want the function to modify the type instance it's called on.
    6. Slices and other "reference types" are tricky because the slice/etc. instance itself is a different thing in memory from the underlying array.  So if you don't want a function to modify the original, you need to manually make a copy before sending that to the function.
  20. Maps:
    1. They're like dicts in Python, except:
    2. The keys and values must all be of the same type within their respective set.
    3. Defining a map: my_map := map[string]string { }
    4. Iterating over a map: for keyVar, valueVar := range myMap {
  21. Interfaces
    1. Interfaces are a way of writing functions that can be used by multiple different types.
    2. Example interface:

      type myInterface interface {
          myRequiredFunction(string) string
    3. Any type that has all of the functions specified in the interface definition (including exactly matching the input types and return types) will thereby be treated by the Go compiler as a member of the interface type as well, and be able to use any functions defined for that interface type.
    4. In Go, interface types can't ever be directly instantiated, whereas "concrete types" are those that can be.
  22. The Reader interface is a common interface in Go; it lets us take input from any of various sources as a slice of bytes.
  23. He goes through the line of reasoning that would lead us to understand that os.Stdout would work as a Writer type: os.Stdout is of type File, and type File has a function of type Write, and since we know that any type that implements that function (including the required parameter types and return types) is of type Writer, we therefore know that os.Stdout would be accepted by io.Copy().  He describes knowing how to do this as "hard-won knowledge".
  24. Go routines & channels
    1. They're like Python threads except they can be assigned to multiple cores like when doing multiprocessing.  So basically they have the ease-of-communication of threads but the speed of multiple cores.  So they're really fast.
    2. To run a function as a Go routine just add the go keyword before the call: go doMyThing()
    3. Channels are a way to communicate between Go routines; think of them as a messaging system.
    4. To create a channel: c := make(chan string)
      1. Channels need to specify the type of the messages they'll be handling.
    5. To push or pop a message to/from a channel, use the <- operator: myNumber <- myChannel
    6. Receiving a message from a channel is a blocking operation (the program will wait until a message arrives).
  25. Function literals are equivalent to anonymous functions in JavaScript or lambda functions in Python and are straightforward to define: func() { myCodeGoesHere() }

Section 1: Getting Started

How to Get Help
  1. No video, just text.
  2. There are three ways to get help:
    • Post in the Q&A discussion
    • Email me: <email address redacted>
    • Message me on Udemy.
Link to Completed Code
  1. No video, just text.
  2. You can find the completed version of all the examples in this course here: https://github.com/StephenGrider/GoCasts
Environmental Setup
  1. We are going to install Go, then we'll install VSCode (an IDE), then we'll configure VSCode, then we'll write some code.
    1. He highly recommends trying VSCode because it has the best integration with Go.
  2. To install Go, go to golang.org/dl
  3. Just keep hitting "Next" for the installer, there's nothing we need to configure.
VSCode Installation
  1. To verify Go was installed correctly, start a terminal / command prompt window and type "go" and hit Enter. You should see a help message show up on the screen.
  2. To install VSCode, go to code.visualstudio.com
Go Support in VSCode
  1. VSCode doesn't start out with any support for Go.
  2. To add this support, go to View → Extensions, and search for "go".
  3. Download the extension with the title "Go", the description "Rich Go language support..." and over 1 million downloads.
  4. Restart VSC.
  5. Make sure you have a code editor window open (do File → New File if you don't).
  6. In the bottom right corner, click "Plaintext" and switch it to "Go", and in the same place where "Plaintext" showed up you'll see an error saying "Analysis tools missing". Click the error message and then click "Install" on the dialogs that pop up.

Section 2: A Simple Start

Boring Ol' Hello World
  1. We're going to start with a simple "Hello World" example in this video but in the next few videos we're going to do a deep dive on how Go is working behind the scenes.
  2. He goes to File → Open and creates a new folder named "helloworld" to house this project.
    • NW: In my editor I didn't have an "Open" option, just "Open File" and "Open Folder", so I chose the latter.
  3. You should see the folder show up in the left sidebar (the "Explorer tab").
  4. He clicks a little "Add file" button next to the name of his project in the Explorer tab and names it "main.go". 
  5. He writes out the code and says we'll discuss each line in-depth in the next few videos:
    • package main
      import "fmt"
      func main() {
          fmt.Println("Hi there!")
    • He says you must use double-quotes, not single quotes, in both the import statement and the Println statement.
Five Important Questions
  1. He studied the Hello World program ahead of time and came up with five basic questions we can ask that will give us a good sense of what the program is doing:
    1. How do we run the code in our project?
    2. What does 'package main' mean?
    3. What does 'import "fmt"' mean?
    4. What's that 'func' thing?
    5. How is the main.go file organized?
  2. He then starts answering question 1.
  3. He switches to a terminal navigated to his project folder.
  4. He runs go run main.go
    • NW: When I tried this I ran into a minor problem in that my code had not been auto-saved like it is in PyCharm, so it didn't work until I figured out that that was happening.
  5. He shows several other Golang CLI commands that we'll be using:
    1. go run is used to compile and immediately execute one or two files, without generating an executable file.
    2. go build compiles, but does not execute, go files. It will generate an executable file.
    3. go fmt formats all of the code in each file in the current directory.
    4. go install compiles and 'installs' a package.
    5. go get downloads the raw source code of someone else's package.
    6. go test runs any tests associated with the current project.
Go Packages
  1. We're going to talk about what "package" means and why we chose the name "main" for the package.
  2. What a package is:
    1. A package can be thought of as a project or workspace.
    2. A package can have many Go source code files in it.
    3. Every file that belongs to a package must have its first line declare the package it is a part of.
  3. As for why we chose the name "main" for the package:
    1. There are two types of packages: executable packages and reusable packages.  Executable packages generate an executable we can run.  Reusable packages are used as helpers / libraries.
    2. The name of the package that you use determines whether you're creating an executable or reusable package.  In particular, the word "main" for a package is what designates a package as executable.  Any other name for a package will create a reusable package.
    3. An executable package must have a function called main.
Import Statements
  1. The import statement is used to give our package access to code contained in another package.
  2. "fmt" is a standard library included with Go. It stands for "format". It's used for printing out information.
  3. We can import both standard packages that are included with Go as well as packages that are not official and have been authored by other individuals.
  4. We can learn more about the standard packages by going to golang.org/pkg
  5. We are going to be looking at these official docs a lot, because a lot of learning Go is learning how these standard packages work.
File Organization
  1. The func in our hello world program is short for "function". Functions in Go work just like functions in other languages.
  2. The way a file is organized always follows the same pattern:
    1. At the very top we have a "package ..." declaration.
    2. Then we'll import other packages we need.
    3. Then we'll declare functions.
How to Access Course Diagrams
  1. No video, just text.
  2. How to use the course diagrams:

    1. Go to https://github.com/StephenGrider/GoCasts/tree/master/diagrams

    2. Open the folder containing the set of diagrams you want to edit

    3. Click on the ‘.xml’ file

    4. Click the ‘raw’ button

    5. Copy the URL

    6. Go to https://www.draw.io/

    7. On the ‘Save Diagrams To…’ window click ‘Decide later’ at the bottom

    8. Click ‘File’ -> ‘Import From’ -> ‘URL’

    9. Paste the link to the XML file

Section 3: Deeper Into Go

Project Overview
  1. We're going to create a Cards package that simulates a deck of playing cards.
  2. We're going to create the following functions:
    1. newDecks - Create a list of playing cards, just an array of strings.
    2. print
    3. shuffle
    4. deal - I'll get one list for the 'hand' and a second one for the remaining cards.
    5. saveToFile
    6. newDeckFromFile
New Project Folder
  1. Create a new folder named cards.
  2. Create a main.go file.
  3. Add some boilerplate code:

    package main
    func main() {
Variable Declarations
  1. We're going to play with Go a bit first to get a sense of the language.
  2. I want to create a variable in our main() function and assign it a string that represents a playing card, and then print out that string.
    1. Add the following code to main():

      var card string = "Ace of Spades"fmt.Println(card)
    2. var is short for "variable" and lets Go know that we're going to create a new variable.
    3. card is the name of the variable.
    4. string is telling the Go compiler that only a value of type string will ever be assigned to this variable.
    5. "Ace of Spades" is the initial value we want to assign to the variable.
  3. Golang is a "statically typed language", like C++ and Java, and in contrast to "dynamically typed languages" like JavaScript, Ruby, and Python.
    1. He opens the Chrome console to show how JavaScript will let you assign a number to a variable and then later assign a string to the same variable.
    2. Some basic Go types: boolstringintfloat64
  4. The := syntax:
    1. He points out that the IDE shows a warning letting you know that you don't need to include string in the variable definition if you're assigning an initial value of that type.
    2. An alternative way to define the variable is card := "Ace of Spades"
    3. We only use the := syntax when assigning a new variable.  If we're assigning a new value to an existing variable, we don't include the colon.
    4. If you accidentally use := a second time, you'll get an error message.
Functions and Return Types
  1. He starts with the following code:

    package main
    import "fmt"
    func main() {
        card := "Ace of Spades"
  2. He wants to modify this code to create a new function that will return the value of the card to be created, instead of just assigning the value directly in main().
  3. To return a value, just use return <value>.
  4. He modifies the code to look like this:

    package main
    import "fmt"
    func main() {
        card := newCard()
    func newCard() {
        return "Five of Diamonds"
  5. He gets an error on the return statement saying too many arguments to return. have (string), want ().
  6. This error is happening because our function definition is saying we don't expect this function to return anything, but we are returning something (a string).
  7. To fix the error we update the function definition to read: func newCard() string {, adding the word string.
  8. He tries changing the string in the function definition to int, and shows how that pops up an error on the return line letting you know you're returning the wrong type of value.
Slices and For Loops
  1. Go has two basic data structures for handling lists of records: Array and Slice.
    1. an array is a fixed-length list of things.
    2. a slice is an array that can grow or shrink, like a Python list.
  2. Both arrays and slices must be defined with a single particular data type; every element must be of the same type.
  3. To declare a slice, we use the following syntax: cards := []string{}
    1. Note he doesn't write strings (plural).
    2. You can place whatever initial records you want to include in the curly braces: {"Ace of Diamonds", newCard()}
  4. To add a new value to a slice: cards = append(cards, newElement)
    1. This returns a new slice.  It doesn't modify the existing slice.
  5. To iterate over a slice use range:

    for i, card := range cards {
        fmt.Println(i, card)}
    1. We use the := syntax for defining the variables (which we earlier saw should only be used the first time you define a variable) because Go "throws away" the variables at the end of every loop.
  6. In the section "Reference vs Value Types" later in the course he writes that arrays are rarely used directly; slices are used "99% of the time for lists of elements".
OO Approach vs. Go Approach
  1. We're now going to start on our Cards project in earnest.
  2. We're going to see how we might create this project in an OO language and then see how we do it in Go.
  3. Go is not an object-oriented programming language.  There's no idea of "classes" in Go.
  4. In an OO language like Python we would define a Deck class and use it to create a Deck instance variable.  The instance would have deck-related methods attached to it.
  5. In Go what we do instead is create a new typetype deck []string, and then to create deck-related functions we create "functions with a receiver" that only work with that new type.  We'll talk more about this later.
  6. We're going to aim to have the following file structure in our package:
    1. main.go - Contains the code used to create and manipulate a deck.
    2. deck.go - Contains the code that describes what a deck is and how it works.
    3. deck_test.go - Contains code that automatically tests the deck.
Custom Type Declarations
  1. In deck.go we'll create the new type: type deck []string
    1. This is telling Go that the identifier deck in our code should be treated as being exactly the same thing as if it saw []string  (a slice of type string).
  2. Note: we don't use the terms "extends" or "subclass" in Go.
  3. We can now replace []string where it appears in our main.go file with deck.
  4. When we run the program now, we need to include deck.go:
    1. go run main.go deck.go
  5. To define a receiver function, use this syntax:

    func (d deck) print() {}
  6. To use the defined receiver function, use it as if it was a method in Python: cards.print()
Receiver Functions
  1. When we define a receiver function, any variable in our package of the specified type will get access to that function.  So basically it works like a Python class method.
  2. The receiver syntax "(d deck)" has two parts: a variable name and a type.  So in our example, "d" is a variable name we'll be able to use in our method, and "deck" is the required type of that variable.
  3. You can think of the receiver variable as being very similar to the word "this" or "self" in JavaScript and Python.
    1. By convention in Go we never use the word "this" or "self"; we always pick a variable name that refers to the actual object.
    2. By convention in Go we always use a one or two letter abbreviation as the receiver variable name.
    3. Personally he's not convinced it's a great idea to use one or two letter variables names as it could be confusing, but he follows the convention.
  4. Officially in Go we never use the terms "class" and "method" even though you can think about this way of defining types with receivers as being similar to those.
Creating a New Deck
  1. We're going to implement the newDeck function.
  2. We need to annotate the function definition with the type that the function returns, we put it after the param list: func newDeck() deck { }
  3. We're not going to add a receiver because we'll want to call this function when we don't already have a deck object to work with.
  4. Each card is going to be a string of the form "<Value> of <Suit>", like "Ace of Spades".
  5. To create the cards we're going to create cardSuits and cardValues slices and then use two for-loops to create all the different combinations.
  6. This code should look very familiar if you've programmed before.  He's seen some people feel intimidated when switching to Go but the language honestly doesn't have a lot of special features.  You can put together good programs without a lot of study of the language.
  7. We don't use the iterator variables in our for loops and so we see a warning in the IDE.  By convention in Go we replace the names of unused variable definitions in for loops with underscores.
Slice Range Syntax
  1. We're now going to implement the deal function.  It's going to return two decks: a "hand" deck and a "remaining" deck.
  2. Selecting a subset from a slice works just like Python:
    1. it's zero indexed
    2. the second index is not included in the result.  So [0:2] is the first two elements.
    3. We can leave off either of the two numbers.
Multiple Return Values
  1. He spends a few minutes repeating information from previous lessons.
  2. To declare that a function returns multiple values, wrap them in parentheses: func deal(d deck, handSize int) (deck, deck) { }
  3. Unlike Python, you don't need to add import statements for stuff that's defined in the same package but in separate files.
  4. Receiving multiple values from a function call works just like in Python: just have comma-separated variable names: hand, remainingDeck := deal(cards, 5)
Byte Slices
  1. Now we're going to implement saveToFile().
  2. We're going to use the WriteFile function in the ioutil package to write to the hard drive.
  3. He uses the word "slice byte" when the code says []byte which makes me wonder how people distinguish between an array and a slice in function definitions.
  4. Whenever you head "slice byte" he wants you to think "string of characters".
Deck to String
  1. We need to convert our card strings to bytes to be able to use the WriteFile function.
  2. To do this conversion we'll do "type conversion".  We just write the type we want, add parentheses, and then put the object we want to convert in the parentheses: []byte("Hello World!")
  3. To convert our deck to a slice of bytes, we're going to take our deck, which is a slice of strings, and join the elements into a single string, and then convert that single string into a slice of bytes.
  4. We're going to make a helper function toString that turns a deck into a string.   We will make it a receiver function.
Joining a Slice of Strings
  1. In our toString function we first convert the deck object to a slice of string: []string(d)
  2. To join the elements of the slice of strings, we're going to use the strings default package, specifically the Join function.
  3. To import multiple packages we use this syntax:

    import (
        "package name one in quotes"
        "package name two in quotes"
Saving Data to the Hard Drive
  1. We're now going to implement a saveToFile() receiver function.
  2. We'll allow the user of our function to specify the filename that WriteFile takes as an argument.
  3. We're going to say that it returns an error since that's something that can be returned from the WriteFile function we're going to use.
  4. For the permission parameter to WriteFile we'll use 0666.
  5. You can see in the import statement that ioutil is a subpackage of the io package.
  6. He runs the code and confirms that it writes a file to the hard drive.
Reading From the Hard Drive
  1. We're now going to implement a newDeckFromFile() function.
  2. Golang uses nil instead of null or None.
  3. A common pattern in Go code after calling a function that can return nil is to add an if statement to add code to handle the case when the value is nil.
  4. He spends a few minutes explaining two different options for handling an error when trying to load a file: 1) return an empty deck, or 2) quit the program.
  5. To immediately quit the program we can call the os package function Exit() with an argument of 1 which indicates that something went wrong.
Error Handling
  1. We'll use the Split() function in the strings package to split up the comma-joined list of cards that we load from the file: s = strings.Split(string(bs), ",")
  2. He converts the list of string to a deck with deck(s)
  3. He tries running newDeckFromFile() and confirms it works.
Shuffling a Deck
  1. Golang doesn't have any built-in function for shuffling a slice.
  2. To implement our own shuffle function, we will iterate over the length of the deck, and for each index we'll generate a random different index in the deck and swap the two cards at those indices.
  3. We can use the Intn() function in the rand subpackage of the math package to generate random numbers.
  4. We'll make the shuffle() function a receiver function that modifies the deck we call it from.
  5. We can use len() to get the length of a slice.
  6. We can do a one-line swap like this: d[i], d[newPosition] = d[newPosition], d[i]
  7. One weird thing about our initial implementation of the shuffle() function is that the last four elements in our deck are always the same.
Random Number Generation
  1. The reason we're getting the same random results every time is that by default, the Golang random number generator always uses the same seed.
  2. It's kind of hard to figure out how to change the seed just by reading the docs.
  3. Let's take a look at the type Rand in the docs.  It's "a source of random numbers".  When we create an object of this type with the New() function, we must specify a source (which is of type Source).
  4. Source is "a source of uniformly-distributed pseudo-random int64 values in the range 0-2^62".
  5. To create a new Source we need to pass in a randomly-chosen int64.
  6. Again, a lot of learning Go is just learning to navigate the documentation around the standard packages.
  7. To generate an int64 that'll be different every time, we'll use the current time with UnixNano() in the time package: time.Now().UnixNano()
Creating a go.mod file
  1. To run tests we need a .mod file.  To create one, run go mod init cards
Testing With Go
  1. Go testing is not like using testing frameworks in other languages.  Go has a very limited number of functions to help us test our code.
  2. To make a test in Go, make a file that ends in _test.go
  3. To run all tests in a package run go test
  4. In your test file remember to start it with package <your-package-name>
  5. VS Code will detect that you've created a test file and will put links at the top of the file to run all the tests in the package or just the tests in that file.
Writing Useful Tests
  1. A common question in testing is: how do we know what to test?  With Go this ends up being straightforward to answer.
  2. Basically you want to try to find some easy assertions that should be true of the output of your functions and that capture attributes of the output you care about.
  3. For the newDeck function there are three things he can think of:
    1. The returned deck should have the expected number of items.
    2. The first card of the deck should be the expected first card.
    3. The last card should be the expected last card.
  4. For each file we want to test we create a _test.go file, and for each function in that file that we want to test, we create a function named Test<YourFunctionName>.
  5. But we don't have to have one test function for each of our functions.  For example, we can test both saving a deck to a file and reading a deck from a file in a single test function: TestSaveToDeckAndNewDeckFromFile
  6. Our tests can often follow a similar pattern:
    1. Get the return value from the function we're testing.
    2. Have various if statements to check the output.
    3. If we find a problem, tell the test handler that something went wrong.
  7. The first (and only?) argument to a test function should be t *testing.T which is our test handler.
  8. To notify our test handler that something went wrong, use t.Errorf("Write a description of the problem here")
  9. To include variables in the error description, use string formatting with the percentage sign: ("Your message: %v", len(d))
Asserting Elements in a Slice
  • He just implements the checks he talked about in the last lesson:
    1. if d[0] != "Ace of Spades" { t.Errorf("...") }
    2. if d[len(d) - 1] != "Four of Clubs" { t.Errorf("...") }
  • Unlike the test frameworks you see in other languages, go test doesn't know how many tests we wrote, so you won't see an output that says something like "Ran 60 tests, 5 failed".
Testing File IO
  1. He's now going to test our functions that save data to a file and load data from a file.
  2. When writing tests with Go we need to make sure we write code that will do any necessary clean-up in all cases.  Go doesn't handle cleanup for us.
  3. In our case, we'll have our code attempt to delete the _decktesting file we'll be using both before running and after running.
  4. So, how do we delete a file?  Let's check the Golang standard library docs.  It's the Remove function in the os package.
  5. The Remove function can return an error, but we can ignore that for our purposes.
  6. Our test function will be named TestSaveToDeckAndNewDeckFromFile
    1. We're using a long name because it will make it easier for us to find it in our code if it causes an error in the future.  (This doesn't make any sense to me if all your tests are in one file but I could see it making sense if you have tests across multiple files and want to avoid using duplicate test names).
  7. He just has a single assertion, that the deck he gets from loading the saved file has the expected number of items.
  8. When writing a test you should make a change to the assertion to make sure that it actually fails when it should.
Project Review
  1. He summarizes the steps he took to get the Deck project done.
  2. There are some weird things about the project he wants to point out.
    1. For the deal function we didn't use a receiver.  The reason is that it might create some ambiguity about what it does.  If we had code that said cards.deal(5) it would look like we're modifying the cards slice by removing 5 cards.
    2. We pass in the t *testing.T argument to each of our tests, but we don't know yet what the * is for.
    3. These two things are actually at the core of the next thing he wants to talk about.

Section 4: Organizing Data With Structs

Structs in Go
  1. When creating our Deck class it would've been awkward to access just the suit or just the number since they're joined into a single string.
  2. A struct is like a collection of properties that are related.
  3. So it's like a JavaScript object or a Python dictionary (or really a named tuple).
  4. He creates a new folder called /structs and a new main.go
Defining Structs
  1. We're going to create a simple project / demo to show how to use a struct.
  2. Whenever you use a struct you need to first define the type and then you can create an object of that type.
  3. He gives an example of a person struct with a firstName field and a lastName field.
  4. Note we don't have any colons or commas in the definition.
Declaring Structs
  1. We can define a new person object with syntax like the following: alex := person("Alex", "Anderson")
  2. By default, if you omit the field names Go will assume the provided arguments match the order of the struct definition.
  3. Personally he can't stand that Go allows this.
  4. The other way to create an instance of a struct is to specify the keys like in Python.
  5. If you Println the struct you'll get a list of the values.
Updating Struct Values
  1. Another way o create an instance is with var alex person
  2. If you don't specify the values, Go will assume what it calls a "zero value": an empty string, 0, or false, depending on the type.
  3. This is different from JavaScript where such fields would have a value of null or undefined
  4. We can use %+v in our Println call to get a printout of both the keys and values of the struct.
  5. You'll sometimes see the var syntax when the coders want the instantiated struct to use the default values of the struct.
  6. To update the value in a struct we can just do alex.firsname = 'new name'
Embedding Structs
  1. You can have attributes in structs that are themselves structs.
    1. Example: a contactInfo struct that is used for a contact attribute of a person struct.
  2. He goes through an example of creating a person struct and having an embedded definition of a contactInfo struct:

    jim := person{    firstName: "Jim",
        lastName: "Party",
        contactInfo: contactInfo{
            email: "jim@gmail.com",
            zipCode: 94000,
  3. Every line within the definition must have a comma at the end.
Structs with Receiver Functions
  1. When writing a struct definition, you can leave off the field name and just specify the value, and the field name will be the same as the name of the type of the value.
    1. Example: instead of contactInfo: contactInfo you can just have contactInfo.
  2. This might seem like a minor thing but it'll be important later when we look at code reuse with Go.
  3. Defining a receiver function works the same way as when doing it for a type: func (p person) print() { }
  4. He creates an updateName(newName string) receiver function and shows that it doesn't actually modify the object that calls the updateName() function.  This is a segue into the next section.
Pass By Value
  1. This discussion is going to revolve around the concept of 'pointers'.
  2. Pointers in Go are relatively straightforward.
  3. We should talk about how RAM on your computer works: it's like a bunch of cubbies, each of which has an address.
  4. When we create a person object, the computer takes that object and puts it at a particular address (cubby) in RAM.
  5. Go is a "pass by value" language by default.  It will copy the entire object when passing it to the function being called.
Structs with Pointers
  1. He reiterates / summarizes what he discussed in the previous section.
  2. He's going to change the code we've been working on to make it pass the value of the person struct to the updateName receiver function.
    1. The first thing is that he adds this line of code: jimPointer := &jim
    2. He then changes the call to updateName to instead be called by the pointer: jimPointer.updateName("jimmy")
    3. He updates the updateName function definition: func (pointerToPerson *person) updateName(newFirstName string) { }
    4. He updates the line of code in updateName that changes the name: (*pointerToPerson).firstName = newFirstName
  3. He's going to explain what is going on with this new code in the next section.
Pointer Operations
  1. The & is an operator.  &variable means "give me the memory address that this variable is pointing at".
  2. * is also an operator.  *variable means "give me the value that this memory address is pointing at".
  3. A very important distinction you need to understand that can be very confusing in Go is the difference between * when seen in a type description vs. * when seen on a 'normal' line of code.
    1. He thinks this is one of the most confusing things about pointers.  When you see a star in front of a type it means something completely different than when you see a star in front of an actual instance of a pointer.
    2. When you see * in a type description, like *person, it isn't being used as an operator.  So the variable name you'll see to the left of the type description is still just a pointer instead of a dereferenced pointer.
    3. When you see * on a line of code like *pointerToPerson.updateName("jimmy") then it is being used as an operator and is actively dereferencing the pointer.
  4. So we're working with two different kinds of variables: variables that produce an address to a particular type of data structure, and variables that produce an actual value of a particular type of data structure.
  5. He spends a few minutes reiterating / summarizing the above ideas.
  6. This is revision 1 of our understanding of pointers.
Pointer Shortcut
  1. With Go, if we define a receiver function of type pointer-to-X, Go will allow us to call it either with a pointer or with the referenced type itself.  Basically letting us skip defining a pointer variable.
  2. So instead of needing to do jimPointer := &jim; jimPointer.updateName("jimmy") we can just keep it as jim.updateName("jimmy") and it will work (it will pass to the receiver function by reference) as long as the receiver is type pointerToPerson *person
Gotchas With Pointers
  1. He creates an example of creating a string slice, passing it to a function, modifying the first element of the slice within that function, and then doing a Println of the slice outside of the function.  Given what we said before about Go being pass-by-value, we would expect that the slice would not have been modified.
Reference vs. Value Types
  1. We rarely use arrays directly; we almost always use slices when we want to have lists of items.
  2. Slices actually store the items in an array behind the scenes.
  3. When we create a slice, Go is actually creating two separate data structures:
    1. the first is what we call a slice.  It has a pointer to the underlying array, a capacity number, and a length number.
    2. The second is the underlying array.
  4. So what's happening when we pass a slice to a function is that Go is still behaving the same: it's still passing by value.  But it's copying the slice and passing the slice by value, but the underlying array is not being copied because it's not what is being passed.
  5. In Go, slices aren't the only data structure that behave this way.  There are "value types" and "reference types".
  6. Value types: int, float, string, bool, structs - You need to use a pointer to modify this value from a function.
  7. Reference types: slices, maps, channels, pointers, functions
    1. He writes "Don't worry about pointers with these".  You don't need to use a pointer to modify the values from a function.
    2. NW: My question: What happens if you grow one of these reference types within the function to such a degree that they need to reference a new array?
      1. A: I asked about this here and apparently the answer is that this is a potential problem for slices, but not for other "reference types" because there's another level of separation between the underlying array and the root type, and actually there are no "reference types" in Go (see links at the top of this wiki page).

Section 5: Maps

What's a Map?
  1. A map is a collection of key-value pairs, like an object in JavaScript or dict in Python.
  2. The keys must all be of the same type.
  3. The values must all be of the same type.
  4. The syntax for defining a map is map[key_type]value_type

    my_map := map[string]string { }
Manipulating Maps
  1. To create an empty map:

    var colors map[string]string
    colors := make(map[string]string)
  2. To add a key-value pair: colors["white"] = "#ffffff"
  3. You can't access keys using dot notation as with structs because the keys don't have to be strings.
  4. To delete an entry: delete(mapName, keyName)
Iterating Over Maps
  1. To iterate over a map:

    for keyVar, valueVar := range myMap {
Differences Between Maps and Structs
  1. Keys in a map don't need to be a string.
  2. All values must be of the same type in a map.
  3. You can't update the keys of a struct; you need to define them all in your code.
  4. There's no built-in way to iterate over the keys of struct like how you can with a map.
  5. A struct is used to represent a single thing that has multiple attributes, whereas a map is used as a collection of different things.
  6. We need to use pointers to update a struct from within a function, but we don't need to do that with maps.  So maps are a "reference type".
  7. In his experience, when writing professional Go code you'll end up using structs a lot more than maps.

Section 6: Interfaces

Purpose of Interfaces
  1. We're going to look at the code we've written through this course and see an issue with it that interfaces help solve.
  2. He gives an example of the shuffle() receiver function we created, and how the logic in it seems fairly generic.  But what if we want to use it with a slice of a different type (other than string)?  Do we need to copy-paste all that code?
    1. This is one of the problems that interfaces helps us solve.
  3. We're going to write a "bad version" of a program without interfaces, then update it to use interfaces.
  4. We're going to create a chatbot program.  It'll have an englishBot struct and a spanishBot struct.
    1. Both bots are going to have a getGreeting receiver function that returns a greeting in the bot's language, and a printGreeting receiver function.
    2. The printGreeting function will probably just do something like Println(bot.getGreeting())
    3. The getGreeting functions' implementations will be very different, but the printGreeting functions will have very similar logic.
Problems Without Interfaces
  1. He creates a new project and creates the englishBot struct (no fields), the spanishBot struct (no fields), and getGreeting receiver functions for each that return different strings.
  2. If your receiver function isn't going to use the received object, you can omit its variable name and just specify the type: func (englishBot) getGreeting() string { }
  3. Go doesn't support overloading: you can't have functions with identical names but different parameters.
Interfaces in Practice
  1. In this video he's going to do the refactor and then discuss what he did.
  2. He defines the interface:

    type bot interface {
        getGreeting() string
  3. He defines the generic form of printGreeting:

    func printGreeting(b bot) {
  4. He has a good "plain English" explanation of what the interface definition is saying: "Hey, every type in this program, pay attention: our program now has a new type called 'bot'.  If you're a type in this program with a receiver function named getGreeting that returns a string, you are now an honorary member of type 'bot', and you now get access to the function printGreeting."
    1. When he showed his refactor I was wondering how the connection was happening between the new interface and the existing types, and this explanation answered my question. 
Rules of Interfaces
  1. In our interface definition we list all of the functions that we expect the matching types to have along with their expected argument types and expected return types.
  2. He mainly makes the point that you can specify multiple parameter types and multiple functions within an interface, all of which need to be matched exactly for a type to qualify as the interface type.
  3. In TypeScript we have the terms "concrete type" and "interface type".  You can directly instantiate a concrete type, but you can't directly instantiate an interface type.
Extra Interface Notes
  1. Interfaces are not "generic types" like you'd see in some other languages.  Go doesn't have support for generic types.
  2. Interfaces are implicit.  You don't need to write code that explicitly connects an interface and the qualifying types.
    1. It's nice because the code is shorter, but it can make it harder to know what interfaces a given type implements.
  3. Interfaces are a "contract to help us manage types".  But they're not some kind of guarantee that your implementation of that interface is going to be correct.  If you feed the interface garbage, you'll get garbage back.
  4. Interfaces are tough to use and understand.  At the beginning, just focus on trying to understand the standard library documentation when it says it expects an interface.  Later, when you're comfortable with them, you can think about writing your own.
The HTTP Package
  1. Next we're going to take a look at a more-realistic example of using interfaces: working with stuff from the standard library.
  2. Our program will make an HTTP request to google.com and print the response to the terminal.
  3. He creates a new http directory to hold the project code, and creates a main.go file.
  4. He navigates to the http package in the official Go docs and sees how to create a GET request, and then navigates to the type Response section of the docs to arrive at the section dedicated to the GET function.
  5. You have to include the protocol (http://) when making a request.
  6. He writes code that will just Println the response object and explains that while this would work in many other languages, it won't work in Go.
  7. He runs the code and seems to get something like a slice of header information, but no actual HTML.
Reading the Docs
  1. He reads the official docs on the Response type and sees that the stuff we were getting in the output last time seem to be the first few fields of the type.
  2. He sees that the Response type has a Body property of type io.ReadCloser
  3. He checks the docs for the ReadCloser type and sees that it's an interface, and that it looks weird, because it doesn't seem to have a list of functions with their required argument types and return types, but instead just Reader and Closer
  4. He reads the docs on the Reader type and sees it's an interface that needs to define a read function that takes a slice of bytes and returns an int and an error.
  5. You can end up going down a rabbit hole when trying to make sense of the docs.
More Interface Syntax
  1. First we're going to talk about why an interface was used as a type inside a struct: what it means is that we can assign any type we want to that field that satisfies that interface.
  2. Next: why did we see that weird syntax in the definition of ReadCloser that just had Reader and Closer fields?  It's a way for us to define an interface in terms of another; what it means is, "in order to satisfy the ReadCloser interface, you need to satisfy the Reader interface and the Closer interface".
Interface Review
  1. Quick review of what interfaces are and why we care about them: (he summarizes the bot example and how interfaces allowed us to re-use the printGreeting function).
The Reader Interface
  1. You're going to see this Reader interface all over Go.
  2. He gives an example of how the Reader interface is useful (and how interfaces are useful): Go programs could receive input from a variety of sources: HTTP requests, text files on the hard drive, image files, users entering information on the command line, or even data from an analog sensor plugged into the computer.  We could imagine that these could all return different data types, and therefore without interfaces we'd need to have separate "print" functions for each of them.
  3. The Reader interface allows us to take the input from all of these different sources and get a slice of bytes as a result.
  4. So you can think of the Reader interface as being like an "adapter".
More on the Reader Interface
  1. Something that implements the Reader interface needs to implement a Read function.
  2. The request body has already implemented this for us.
  3. The way the Read function works is that the code that wants to call ReaderObject.Read() passes it a byte slice, and then the Reader takes its source of data and pushes it to that provided byte slice.
    1. This may seem weird you if you're used to not needing to supply the data structure that the output will be put into.
  4. The integer that gets returned from Read() is the number of bytes that were put into the slice.
Working with the Read Function
  1. Let's try to actually write some code that uses the Read() function.
  2. To create the slice he does this: bs := make([]byte, 99999)
    1. This initializes the slice so that its underlying array can immediately handle the specified number of bytes.
    2. We have to do this because the Read() function isn't set up to automatically resize the slice if it's already full. (NW: Ugh, what a nightmare.)
  3. To load the HTTP response into the byte slice he does this: resp.Body.Read(bs)
  4. To print it out he does fmt.Println(string(bs))
  5. In the next video we'll see a way to simplify this code we wrote.
The Writer Interface
  1. Let's simplify the code from the last section: He replaces the three lines of code he wrote in the previous section with just this: io.Copy(os.Stdout, resp.Body)
  2. How did this work? First, you need to understand that Go has a Writer interface which does the opposite of the Reader interface: it takes a slice of bytes and writes it to some form of output (HTTP request, text file, etc.).
The io.Copy Function
  1. He navigates to the official docs on the Writer interface and sees that to satisfy it, a type needs to implement a Write function.
  2. He then navigates to the docs on the Copy() function and sees that it requires two arguments: first, something that implements the Writer interface, and secondly something that implements the Reader interface.
  3. He then goes through the line of reasoning that would lead us to understand that os.Stdout would work as a Writer type: os.Stdout is of type File, and type File has a function of type Write, and since we know that any type that implements that function (including the required parameter types and return types) is of type Writer, we therefore know that os.Stdout would be accepted by io.Copy().
  4. He describes this as "hard-won knowledge", suggesting there was no easily-available help out there on the Internet that made this stuff clear to him.
    1. "It took me a lot of time when I was learning Go to understand all this stuff."
  5. He says that even though this stuff seems "nasty", it will save you "so much time when you go write your own code".
  6. Next, as a way to get more experience with interfaces, we'll take a look at the implementation of io.Copy() so we can understand how we'd implement the Writer interface in our own type.
The Implementation of io.Copy
  1. He holds down the Command key (OSX, maybe Ctrl on Windows) and hovers over the Copy function in the code, and it shows the source code in a little window.
  2. He then clicks on Copy and a new tab opens to the source code.
  3. He sees that the Copy function immediately passes the arguments it receives to a copyBuffer function, and navigates to that function's source code.
  4. He points out in the source code the line that has the copyBuffer function doing what we did manually in an earlier section: it creates an empty 32kb byte slice to contain information from the input source (whatever is implementing the Reader interface).
  5. He then shows how the function is just looping pulling information from the Reader and piping it to the Writer 32kb at a time until the Reader doesn't have anything left to give.
  6. Next, let's try to create our own type that implements the Writer interface.
A Custom Writer
  1. We're going to try to create our own custom Writer and pass it to io.Copy()
  2. He creates an empty logWriter type with the necessary Write function:

    type logWriter struct{}
    func (logWriter) Write(bs []byte) (int, error) {}
  3. He points out (as he did earlier in the course) that there's nothing about the "interface" feature of Go that guarantees that a given type will actually do the intended action of the interface.  It just guarantees the input and output types.
    1. As an example, he has the Write function just return 1, nil but not do anything else.  He runs the code (using a logWriter object with io.Copy) and shows that it doesn't raise any error.
  4. He then does a working implementation of Write:

    func (logWriter) Write(bs []byte) (int, error) {
        return len(bs), nil
  5. He says interfaces are "some of the really top-end, heavy Go stuff", so it's normal if it feels tough.
Assignment: Interfaces
  1. Create two struct types: triangle and square, both should have a getArea() receiver function.
  2. Add a shape interface that requires a getArea() receiver function and provides a printArea() function.
Assignment: Hard Mode Interfaces
  1. Create a program that reads a file from the hard drive and prints out the contents to the terminal.
  2. The filename should be provided as a command-line argument.
  3. os.Args is a slice of string that has the command line arguments passed to our program.
    1. The first element will be the path to the compiled executable.

Section 7: Channels and Go Routines

Website Status Checker
  1. Channels and Go Routines are both used for writing concurrent programs.
  2. We're going to build a program without using these features and then improve the program by rewriting it to use these features.
  3. The program we'll build will make HTTP requests to a list of sites periodically to check if they're online or not.
  4. Our first approach will be to iterate one-at-a-time through a slice of strings of the URLs we want to check.
  5. He implements the "outer" code of the function that loops through the list of strings.
Printing Site Status
  1. He writes a function checkLink that takes a link and makes an HTTP request.
  2. He runs the program and sees the output coming out one site at a time.
Serial Link Checking
  1. It seems in our first approach there's a bit of a delay between when we receive the output for each site.
  2. This is because we're needing to wait for the HTTP response for each site we make a request for before we can move on to handle the next site.
  3. If we had many, many links, we might having to wait a long time between when we could check a given website.
  4. We could use Go Routines to run these requests in parallel.
Go Routines
  1. First we'll discuss the theory of Go Routines, and then we'll actually implement them.
  2. When we run a Go program, Go creates a Go Routine that runs our code line-by-line.
  3. When it runs the http.Get(link) call, the main Go routine is blocked from continuing until it receives a response.
  4. To fix this, we'll add a go keyword in front of the checkLink function to have it run in parallel in a new Go routine: go checkLink(link)
  5. When the child routine hits a blocking function, control is passed to other Go routines that are unblocked.
  6. In the next section we'll look at some of the edge cases we can run into with Go routines.
Theory of Go Routines
  1. Let's talk about what Go routines are doing on our machine when we run them.
  2. Behind the scenes, there's something called the "Go scheduler".  By default it works with one CPU on our machine, even if you have multiple cores.
  3. What's important to understand is that only one Go routine is ever running at any given time.
  4. The purpose of the Go scheduler is to monitor the status of each Go routine and switch the 'active' one depending on which ones are blocked and unblocked.
  5. If you have multiple CPUs, the scheduler will assign routines to different CPUs to make them run truly in parallel.
  6. In Go there is a saying you'll run into a lot: "concurrency is not parallelism".  Parallelism is when you have code for the same program running at the exact same time on different CPUs.  Concurrency is when you can start new work before having totally finished old work.
  7. Child go routines aren't given 100% exactly the same treatment as the main Go routine.
  1. He updates the checkLink project code we'd written to work with Go routines by just adding the keyword go in front of the call to checkLink(link)
  2. He runs the code and there's no output (we'd expect to see the URLs we're checking).
  3. He explains that in our existing code, when the main routine finishes creating all the child routines, it doesn't see anything else for it to do, so it exits entirely.  It doesn't care that the child routines haven't finished their work.
  4. We're going to fix this by using channels.  Channels are a way to communicate between Go routines, and they're the only way to communicate between Go routines.
  5. In our case, we're going to create one channel that will let our main routine know when the child routines have finished their work.
  6. You can think of the channels as working like text/instant messaging: you can send data into a channel and it'll automatically get sent to all other routines that have access to that channel.
  7. A channel is an actual value in Go that you can pass around between functions like any data structure: int, string, struct.
  8. You must specify a type of data that the channel will be passing around.
Channel Implementation
  1. To create a channel: c := make(chan string)
    1. string can be whatever type you want.
  2. For a function to be able to make use of the channel, you have to pass the channel variable to that function.
  3. When you list a channel in a function's list of arguments, you also need to list the type of data that the channel expects: func checkLink(link string, c chan string) {
  4. To send data into a channel use the <- operator: myChannel <- 5
  5. To receive data from a channel use the same operator: myNumber <- myChannel
    1. The routine will wait for a value to be sent into the channel.
    2. You don't have to assign it to a variable, for example you can do fmt.Println(<- myChannel)
  6. He updates the checkLink project code we've been working on to just send a string message into the channel from each child go routine after the GET request, and then waiting for a single message in the main routine.
  7. He runs the code and notes that we only see one message in the terminal before the program exits, and says we'll discuss why this is happening in the next section.
Blocking Channels
  1. He steps through the code, explaining that the main routine stops at the fmt.Println(<- c) line of code that receives string messages from the child routines, and then when it receives a message it runs that line of code and exits.
    1. He stresses that the main thing to understand is that receiving data from a channel is a blocking operation in the same way that an HTTP request is.
  2. He shows a timeline diagram he created to show how the main routine stops when it has finished creating all the child routines and then restarts when it receives a message in the channel from the child routine that had sent a request to google.com.
  3. He adds another fmt.Println(<- c) line after his first one and runs the code again and sees two messages in the output.
    1. He shows a new timeline diagram showing how the main routine would go to sleep after printing the first string and wake up when receiving the second string through the channel.
  4. He adds three more Println calls, bringing the number of calls up to the same number of links, and runs the code again and sees all of the expected messages in the output.
  5. He adds one more Println call and sees how all of the expected output messages get printed out but then the program doesn't exit because it's waiting for one more that will never come.
  6. In the next section we'll see how to print out all of the messages without copy-pasting Println statements.
Receiving Messages
  1. Instead of copy-pasting the Println calls, we're going to use a for loop: for i := 0; i < len(links); i++ {
Repeating Routines
  1. We're now going to update our program to ping each website repeatedly instead of only once.
  2. To have an infinite loop use this syntax: for {
  3. This is the full code for the repeating part:

    for {
        go checkLink(<- c, c)
  4. This loop runs after our initial loop through the links.  The <-c is receiving links sent into the channel by finished routines and then immediately passing them to a new routine.
  5. He runs the code and we can see how quickly it's querying each site.
  6. In the next section we'll add a gap of time between each request for a given site.
Alternative Loop Syntax
  1. An equivalent syntax for our loop in the previous section is:

    for l := range c {
        checkLink(l, c)}
  2. The range c will wait for a value to arrive on the channel c
  3. This syntax might make it easier for other coders to understand that the for loop is iterating over values coming in from the channel.
Sleeping a Routine
  1. He navigates the official docs to find the 'Sleep' function.
  2. 'Sleep' takes a 'Duration', so he navigates to the docs to see what that is.  He sees that a time.Second is of type Duration
  3. The final code: time.Sleep(5 * time.Second)
  4. He points out that having the sleep statement in the main routine wouldn't achieve the parallel behavior we want.
Function Literals
  1. He initially moves the time.Sleep() call to the checkLink() function but then points out that this kind of "pollutes" the nature of checkLink()
  2. His proposed solution is to instead have the go call initiate a function literal that contains the call to time.Sleep() and the call to checkLink():

    for l := range c {
        go func() {
            time.Sleep(5 * time.Second)
            checkLink(l, c)
Channels Gotcha!
  1. He notes that we're getting a warning underneath the call to checkLink() saying "range variable l captured by func literal".
  2. When he runs the code, we see that after the initial loop through the sites, all of the HTTP calls seem to be going to facebook.com.  So something is wrong.
  3. He explains that what's happening is that the function literal is running within a child routine and is referencing the actual value of l (not a copy), which is also being referenced and updated by the main routine.
  4. The solution is to pass the for loop variable as an argument to the function literal:

    for l := range c {
        go func(link string) {
            time.Sleep(5 * time.Second)
            checkLink(link, c)