Go: Part 2

I will proceed by programmatically explaining constructs in conjunction with code snippets. Treat the code sections below as multiple mini demos combined together and explained with the help of comments. Code sections lower down the post will call functions declared higher up, so you can scroll back up and reference them.

The basics

Programs start execution from within package main, with the main function inside that package being executed first. Packages are self contained, with the only things from the package accessible outside the it are exported names (variables, functions, etc. starting with an uppercase letter). Packages can contain sub-packages, via nested directories.

package main

import (
	"fmt"
	"math"
	"math/cmplx"
	"math/rand" // sub-package is rand, part of math package (sub-directory)
	"time"
)

// Only things accessible outside a package are exported names, starting with uppercase.

// there can only be one main function in a package
func main() {
	rand.Seed(5)
	fmt.Println("The time is", time.Now())
	fmt.Println("My favorite number is", rand.Intn(10))
	fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}

Functions

Unlike most other languages, type declaration in Go occurs after the literal. Similar to how you can declare multiple variables of a type in c, like int x, y;, you can declare multiple variables and mention the type after each variable, or only once at the end. This applies to function parameters too. Functions can also return multiple variables.

func add(x, y int) int { // can also be add(x int, y int)
	return x + y
}

func swap(x, y string) (string, string) {
	return y, x
}

func needInt(x int) int {
	return x*10 + 1
}

If you declare variables as a part of the return type for a function, the variables are accessible within the function, and are returned automatically with a naked return statement.

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return // naked return, returns the named return values (x and y)
}

Variables

Go has many basic data types:


bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte (alias for uint8)

rune (alias for int32, represents a Unicode code point)

float32 float64

complex64 complex128

You can have package level variables, and function level variables. Package level variables can be made accessible outside the package by having the first character of the variable be uppercase. Variables have default values, corresponding to 0.

var c, python, java bool // package level variables

func varTest() {

	var i int                       // function level variable
	fmt.Println(i, c, python, java) // prints 0 false false false
}

Variable types can be manually declared before use, or the := shorthand operator can be used to automatically declare the type of the variable based on the values being assigned to it.

func varTest() {
	var p, q string
	p, q = "hello", "world" // manual declaration of type and assignment

	a, b := swap("hello", "world") // type of a and b is inferred from the values returned by the swap function

	var x, y int = 1, 2   // int can be omitted here since they are being initialised immediately
	u, v := 1, 2          // same as above

	var p, q = 1, "hello" // initialising different variable types is inferred from value

	c, python, java := true, false, "no!" // short variable declaration
	k := 3
}

Outside a function, every statement begins with a keyword (var, func, and so on) and so the := construct is not available.

Factoring

Multiple statements of the same kind can be bunched together to save on verbosity. This is referred to as factoring in Go.

import "math"
import "fmt"
import "math/cmplx"

Can be factored as:

import(
	"math"
	"fmt"
	"math/cmplx"
)

Variable declaration can be factored similar to import statements:

var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

Type conversion and Constants

Go does not support implicit type casting.

func typeTest(){
	// var z is from previous code snippet
	fmt.Printf("Type: %T Value: %v\n", z, z) 
	// prints: Type: cmplx Value: 2 + 3i

	// The expression T(v) converts the value v to the type T.
	f := float64(i)
	u := uint(f)
}

When inferring types from the := shorthand operator, when a constant literal is assigned, the variable type will be set to either int, float64, or complex128 based on the precision of the constant literal.

	i := 42 	// 'i' will be int in this case

Go supports constants, which can hold high precision values. Constants can be character, string, boolean, or numeric values. They cannot be declared using the := syntax.

	const Pi = 3.14

	const ( // can use factored declaration.
		Big   = 1 << 100
		Small = Big >> 99
	)

	// The needInt function accepts an int as argument, and returns an int.
	fmt.Println(needInt(Small)) // const `Small` can fit inside an int, succeeds
	fmt.Println(needInt(Big))   // this will fail as const `Big` overflows int
}

Flow control

Go only has for as a looping construct. All other looping constructs can be emulated with for.

func flowTest() {
	sum := 0

	for i := 0; i < 10; i++ { // init; cond; post
		sum += i
	}

	for sum < 100 { // init and post are optional (becomes a while loop)
		sum++
	}

	for { // removing cond creates an infinite loop
		sum++
	}

	for _, name := range names {
    // replace _ with a variable if you want the index in a slice/array,
    // or to hold the key in case names is a map. Similar to Python's loops.
	}
}

When it comes to condition statements, Go supports if, if/else and if/else if/else constructs.

func sqrt(x float64) string {

	if x < 0 { // if statement
		return sqrt(-x) + "i"
	}

	return fmt.Sprint(math.Sqrt(x))
}

func pow(x, n, lim float64) float64 {

	// if short; cond
	// short is similar to init in for loops.
	if v := math.Pow(x, n); v < lim { 
		return v
	} else {	// Go is strict about where the curly braces are, else must come after the closing curly brace, and not on the next line
		fmt.Println(v) 
		// shorts and inits are only within scope of the block 
		// (including the else blocks) of the flow construct
	}

	// can't use v here
	return lim
}

Go supports switch, however, it is slightly different from other languages. Case conditions need not be constants, they can be an expression / use functions. There is an implicit break between cases.

func days() {
	fmt.Println("When's Saturday?")

	// switch also supports optional short statements
	// switch short; cond
	switch today := time.Now().Weekday(); time.Saturday {
		
		case today + 0:
			fmt.Println("Today.")
		case today + 1:
			fmt.Println("Tomorrow.")
		case today + 2:
			fmt.Println("In two days.")
		default:
			fmt.Println("Too far away.")
	}

	// switch can be conditionless
	// it will select the first case that evaluates to true
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

Flow of execution can be manipulated using defer. The defer keyword before a function call will delay the execution of the function call until the surrounding function returns (finishes executing). However, the arguments to the function are executed and not deferred. For example, if you had an expression as an argument to the function, the expression will be evaluated and not deferred.

func deferExample() {

	defer fmt.Println("world")
	// Function call isn't executed until deferExample() finishes.

	fmt.Println("hello")
} 
// Calling this function first prints "hello", and then "world"

Defers are put into a stack, and called last-in first-out when the surrounding function returns.

func deferStackExample() {

	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}
}
// Prints 9, 8, 7, ...

Continued in Part 3

Here’s all the stuff I made while learning from the Go docs