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, ...