/learning-go

Hack to learn Golang

Primary LanguageGoMIT LicenseMIT

logo

What is it? | Things I like | Things I'm not a huge fan of | Learning Problems Completed | Take Aways

What is it?

It's a small set of hacks to learn Go. I'm using Golang go1.13.6 darwin/amd64 for this adventure.

Go short for Golang is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and networked machines, while its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language.

Go is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. Go is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency. Go was designed at Google in 2007 to improve programming productivity in an era of multicore, networked machines and large codebases.

Go is common in many Cloud Native solutions such as Kubernetes, Terraform, Prometheus, and many more. It was the tenth most popular language in 2019 on GitHub

Things I like

  • Straight forward in most situations, almost boring which is a good thing
  • Types in Go feel simple 👍
  • Parallel processing is simple with Go Routines and Channels
  • Short hand variables option (foo := "bar" instead of var foo string = "bar")
  • Go's compiler checking import references on save
  • The UX provided by Microsoft's VS Code go plugin is pretty great

Things I'm not a huge fan of

  • It's really opinionated
  • Go's convention of using single letter variables feels bad
  • Mandated formatting choices such as tabs for indents because it's the Go "way". Go reasons these benefits for this choice. This Chrome plugin by the amazing @sindresorhus helps my happiness with readable code.
  • Go's struct allowed construction of using ordered values feels prone to mistakes in the future when the order of the struct changes. I'm glad they offer a named attribute option.

Learning Problems Completed

  • cards - A simple app that builds a deck of playing cards with functions for deal, shuffle, print, etc
  • even-and-odds - A simple app to parse a collection of numbers and print if they are even or odd
  • structs - A simple app to show the use of structs, pointers, and memory addresses
  • maps - A simple app showing of the basics of Go maps
  • interfaces - A simple app showing of basics of Go interfaces
  • http - A simple app to hit google.com and write out the contents of the html to the console
  • shapes - A simple app to demonstrate multiple shapes implementing area differently and the interface they respect
  • print-file - A simple app to read a file specified from the command line and print out the contents on the console
  • [channels] - A simple app to hit popular websites and report if they are up with concurrency via Go Routines and Go Channels

Take Aways

Go CLI

Go has a Command Line Interface (CLI). Some common uses of Go's CLI:

CLI Command Use
go build Compiles a bunch of go source code files
go run Compiles and executes one or two files
go fmt Formats all the code in each file in the current directory
go install Compiles and installs a package
go get Downloads the raw source code of someone else's package
go test Runs any tests associated with the current project

Go Packages

The purpose of a package in Go is to group together code with a similar purpose.

The Go packages are documented here

Package Types

  • Executable - Generates a file that we can run
  • Reusable - Code used as "helpers", good place to put reusable logic

You must use main as the package name in order to make a Executable package type. Any other name besides main will cause the package type to be Reusable.

Anytime you make a Executable package, you must include a function inside of it called main.

Import

In Go you use import statements to give your package access to code written in another package.

Example:

import "fmt"

Variables

You can use the longer or shorthand manner of declaring variables. They achieve the same outcome. The latter relies on the Go compiler to determine the type.

...
	var card string = "Ace of Spaces"
...
	card := "Ace of Spades"

After the declaration the values are updated as such:

...
func main() {
	var card string = "Ace of Spaces"
	card = "Five of Diamonds"
	fmt.Println(card) // Five of Diamonds
}
...
func main() {
	card := "Ace of Spades"
	card = "Five of Diamonds"
	fmt.Println(card) // Five of Diamonds
}

Functions

Use func to declare functions in Go. Go does not support function overloading.

Functions that return must declare the return type.

...
func main() {
	card := newCard()
	fmt.Println(card)
}

func newCard() string {
	return "Five of Diamonds"
}

Receiver Functions vs Passing in arguments to a Function

Receiver Functions execute on the current object in memory where as a normal function would take in the object via arguments. Receiver Functions is usually by reference and passing in arguments to a function is by value.

Multiple return values

...
func main() {
    color1, color2, color3 := colors()

    fmt.Println(color1, color2, color3)
}

func colors() (string, string, string) {
    return "red", "yellow", "blue"
}

Arrays & Slices

  • Array in Go is a fixed length list of things
  • Slice in Go is an array that can grow or shrink
  • Arrays and Slices in Go must define a singular type
  • Go is zero based
  • Slice range sytnax is startIndexIncluding to upToNotIncluding
      fruits := []string{"apple", "banana", "grape", "orange"}
      fruits[0:2] // "apple" "banana"
      fruits[:2] // "apple" "banana"

Struct

A struct is a collection of fields. There are three ways of declaring struts.

  • Value Type
  • All keys must be the same type
  • Values can be of different type
  • Keys don't support indexing
  • You need to know all the different fields at compile time
  • Use to represent a "thing" with a lot of different properties
type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2}) // {1 2}
}

Maps

A map maps keys to values. The zero value of a map is nil. A nil map has no keys, nor can keys be added.

  • Reference Type
  • All keys must be the same type
  • All values must be the same type
  • Keys are indexed, meaning we can iterate over them
  • Use to represent a collection of related properties
  • Don't need to know all the keys at compile time
	colors := map[string]string{
		"red":    "#f44336",
		"pink":   "#e91e63",
		"purple": "#9c27b0",
	}

	fmt.Println(colors) // map[pink:#e91e63 purple:#9c27b0 red:#f44336]

or

	moreColors := make(map[string]string)
	moreColors["blue"] = "#03a9f4"
	moreColors["cyan"] = "#00bcd4"
	moreColors["teal"] = "#009688"

	fmt.Println(moreColors) // map[blue:#03a9f4 cyan:#00bcd4 teal:#009688]

Loops

...
cards := []string{"Ace of Spades", newCard()}

for index, card := range cards {
	fmt.Println(index, card)
}

Type Conversion

For example:

[]byte("Hello world.")
  • []byte - The type we want
  • ("Hello world.") - The type we have

Zero Values

Variables declared without an explicit initial value are given their zero value. The zero value is:

  • 0 for numeric types
  • false for the boolean type
  • "" (the empty string) for strings

Pass by Value Language

Strictly speaking, there is only one way to pass parameters in Go - by value. Every time a variable is passed as parameter, a new copy of the variable is created and passed to called function or method. The copy is allocated at a different memory address. The structs code shows a good example use.

Whenever you pass an integer, float, string, or strut into a function it creates a copy of each argument, and these copies are used inside of the function.

Some syntax associated with this:

  • &foo - Gives the memory address of the foo value this variable is pointing at
  • *foo - Gives the value of this memory address is pointing at

pointers-and-address-info image from Stephen Grider's Go: The Complete Developer's Guide (Golang) course

pointer-use-in-receiver-vs-in-use image from Stephen Grider's Go: The Complete Developer's Guide (Golang) course

Interfaces

Interfaces are named collections of method signatures. You can list out as many functions for an interface as you want. You detail parameters and returns as well in an interface.

  • Interfaces are not generic types
  • Interfaces can be satisfied implicitly, meaning we don't need to write any extra code to say that some type satisfies an interface
  • When a type satisfies an interface it means the type implements all of the functions contained in the interface definition
  • A interface will help with ensuring types but not ensure that the code inside of the interface implements the desired behavior

Example:

package main

import "fmt"

type bot interface {
	getGreeting() string
}

type englishBot struct{}
type spanishBot struct{}

func main() {
	eb := englishBot{}
	sb := spanishBot{}

	printGreeting(eb)
	printGreeting(sb)
}

func printGreeting(b bot) {
	fmt.Println(b.getGreeting())
}

func (englishBot) getGreeting() string {
	return "Hello"
}

func (spanishBot) getGreeting() string {
	return "Hola"
}

Go Routines

A Go routine is a function that is capable of running concurrently with other functions. To create a Go routine we use the keyword go followed by a function invocation. The Go Scheduler handles executing code for a CPU core. The initial running code is referred to as the Main routine and the others (when needed) are referred to as Child Routines.

Channels

Channels provide a way for two Go routines to communicate with one another and synchronize their execution.

Channel info image from Stephen Grider's Go: The Complete Developer's Guide (Golang) course

Gotcha's in Go

some-types-behave-different image from Stephen Grider's Go: The Complete Developer's Guide (Golang) course

  • Everything in Go is pass by value
  • When we create a slice in Go it will automatically create an array and a structure that records the length of the slice, the capacity of the slice, and a reference to the underlying array
  • With "value types" in Go, we have to worry about pointers if we want to pass a value to a function and modify the orginal value inside the function
  • With "reference types" we do not need to worry about pointers if we want to pass a value to a function and modify the orginal valye inside the function
  • A slice is a "reference type"

Testing in Go

Go testing is not RSpec, mocha, jasmine, selenium, etc. To make a test, create a new file ending in _test.go. To run all tests in a package run the go test command.

Useful test command variants:

  • go test -coverprofile=coverage.out - Get a report of run tests
  • go tool cover -html=coverage.out - Get a nice HTML report of run tests
  • go test -v - A more detailed test report of what tests pass and what failed