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.The language is often referred to as Golang because of its domain name, golang.org, but the proper name is Go.
It is good for:
Here is the list of resources which can be used on daily basis.
Installing is done via package manger or by getting source code and following instructions from it.
Following environment variables must be after go installation.
GOPATH - go workspace,any name can be usedGOROOT - holds golang source codeUnder GOPATH structure should look like:
▶ tree -L 1 $(printenv GOPATH)
/home/mjv/go
├── bin
├── pkg
└── src
where folders are:
go env - print go envgo version - print go versiongo help [topic] - print helpgo doc <cmd> - print doc for cmdgo get [-d] package - download and [install] packagego fmt - formating the codego run filaname - running the *.go code, it needs a filenamego build - build executable file in current foldergo install - compile and install package and dependencies and place it inside GOPATH/bin// Every go file begins with a package name. The name of the package must be the same as the folder name except for package main. Package main is the entry point for your program.
package main
// import fmt package
import (
"fmt"
)
func main(){
fmt.Println("Hello World")
}
hello.go
package go
func Hello() string {
return "Hello, world."
}
hello_test.go
package hello
import "testing"
func TestHello(t *testing.T) {
want := "Hello, world."
if got := Hello(); got != want {
t.Errorf("Hello(): %q, want %q", got ,want)
}
}
go mod init REPO - initialize module in godirect dependency - what you import in *.goindirect dependency - what was downloaded based on dependencies from directly imported modulesgo list -m [-versions] all - list all dependencies (with versions as well)go.mod - contains golang version used for module,require statement for imported modules and include/exclude statement for including/excluding files from the current modulego.sum - contains expected cryptographic hashes of the content of specific module versionsgo get rsc.io/[email protected] - get module rsc.io/sampler with version v.1.3.1Every program must have package main and func main(){} which is also an entry point of the program.When program leaves func main(){} your program is done.
func test(a ...interface{} -> here ...interface{} can be thought as i allows for an unknown number of arguments and any typeinterface{} - empty interface_ - throw away returns,this char allows you to tell to the compiler that you are not going to use this value returned by a functionfmt.Println = package.IdentifierPackage name must match folder name (except package main) and must have func main(){}.Statenment of func main(){} can have other functions.Here are some words from go spec which are used for talking about the language:
identifiers(variable name) - first char must be letterkeywords - reserverd words by go which cannot be used as identifierstype - every variable needs to have a declared type so go language can process the code based on its value:= - short declaration operator, declare and assign,latter for reassinging use =statement - represents a line in go which are usually made of expressions ( e.g. x := 5 is a statement)expression - y := 24 + 5 here 24 + 5 is an expression, it consists of an operator (+) and operands (24,5)group1 - general printing to standard out
fmt.Printfmt.Printlnfmt.Printfgroup2 - printing to a string which you can assignt to a variable
fmt.Sprintfmt.Sprintlnfmt.Sprintfgroup3 - printing to a file or a web server response
fmt.Fprintfmt.Fprintlnfmt.Fprintfgroup4 - scan for input and store it to variables
fmt.Scanfmt.Scanlnfmt.ScanfConversions are expressions of the form T(x) when T is a TYPE and x is an expression that can be converted to TYPE T.
Variables are created with the same pattern as:
// KEYWORD IDENTIFIER TYPE
type person struct // user-defiend type
var x int // int variable
const y string // string constant
var keyword = var x string - defining variables outside the function, use for package scope or zero value
// DECLARE there is a VARIABLE with IDENTIFIER "z"
// and that the VARIABLE with IDENTIFIER "z" is of TYPE int
// ASSIGNS the ZERO VALUE of TYPE int to "z"
var z int
// DECLARE a VARIABLE is of a certain TYPE it can only hold VALUES of that TYPE
var x string
false for booleans0 for integers0.0 for floats"" for stringsnil for pointers,functions,interfaces,slices,channels,mapsx := 5 - here := is called short declaration operator. it is used for defining variables inside functions, their scope is limited to the related function
x := "hello" - stringi := 10 - intb := true - boola := [5]int{1,2,3,4,5} - arrays := []string{"mile","kitic"} - slicem := map[string]string{ "name": "mile" } - mapf := func(){ fmt.Println("this is func") } - funcst := struct{ first string last: string }{ first:"Mile", last:"Kitic" } - structraw_literal - s := `hello` - can include space,\n and other special chars,everything expect back quotestring_literal - s := "hello" - any char can may appear except newline and unescaped double quoterun_literal - s := 'h' - one or more characters enclosed in single quotes, as in ‘x’ or ‘\n’byte = unit8rune = int32String value is a sequnece of bytes -> string = []byte
Unicode - Every platonic letter in every alphabet is assigned a magic number by the Unicode consortion which is written like U+0639 and it is called code point.Code point in UTF8 is a character, it is called rune in GO.Code points are represented in hex(base 16).UTF-8 - storing your string of Unicode code points,in memory using 8 bits.In UTF-8,every code point from 0-127 is stored in a single byte.Only code points >128 are stored using 2,3,…6 bytes.If you have a string,in memory,in a file, or in a email message,you have to know what encoding it is in or you cannot interpret it or display it to the users correctly.When you stora a character value in a string,we store its byte-at-a-time representation.Source code in Go is defined to be UTF-8 text, no other representation is allowed.That implies that whem, in the source code, we write the text
⌘
the text editor used to create the program places the UTF-8 encoding of the symbol ⌘ into the source text.When we print out the hex bytes,we’re just dumping the data the editor placed in the file.
In short,GO source code is UTF-8,so the source code for the string literal is UTF-8 text.If that string literal contains no escape sequences, which is raw string cannot, the constructed string will hold exactly the source text between the quotes.Thus by definition and by the construction the raw string will always contain a valid UTF-8 respresentation of its contents.Similarly, unless it contains UTF-8-breaking escapes like them from the previous section, a regular string literal will also alywas contain valid UTF-8.
To summarize,strings can contain arbitrary bytes,but when constructed from string literals,those bytes are (almost always) UTF-8 sequences.Those sequences represent Unicode code points, called runes.
package main
import "fmt"
func main() {
s := "hello"
fmt.Printf("value:%v\ntype:%T\nhex:% x\nlength:%v\nquoted:%+q\n", s, s, s, len(s), s)
bs := []byte(s)
fmt.Printf("dec_value:%v\ntype:%T\nunicode:%#U\nquoted:%+q\n", bs, bs, bs, bs)
fmt.Println()
s1 := `⌘`
fmt.Printf("value:%v\ntype:%T\nhex:% x\nlength:%v\nquoted:%+q\n", s1, s1, s1, len(s1), s1)
bs1 := []byte(s1)
fmt.Printf("dec_value:%v\ntype:%T\nunicode:%#U\nquoted:%+q\n", bs1, bs1, bs1, bs1)
}
will print
value:hello
type:string
hex:68 65 6c 6c 6f
length:5
quoted:"hello"
dec_value:[104 101 108 108 111]
type:[]uint8
unicode:[U+0068 'h' U+0065 'e' U+006C 'l' U+006C 'l' U+006F 'o']
quoted:"hello"
value:⌘
type:string
hex:e2 8c 98
length:3
quoted:"\u2318"
dec_value:[226 140 152]
type:[]uint8
unicode:[U+00E2 'â' U+008C U+0098]
quoted:"\u2318"
A for range loop on a string decodes one UTF-8 encoded rune on each iteration.Each time around the loop, the index of the loop is the starting position of the current rune,measured in bytes,and the code point is its value.
String are build from bytes so indexing them yields bytes,not charactes.A strint might not even hold characters.
const nihongo = "日本語"
for index, runeValue := range nihongo {
fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
}
will print
U+65E5 '日' starts at byte position 0
U+672C '本' starts at byte position 3
U+8A9E '語' starts at byte position 6
%#U - utf%#b - binary%#o - octal%#d - decimal%T - type%#x - hex% x - hex with space between chars%q - will escape any non-printable byte sequence’s in a stringascii - 128chars (0-127) - print the table with man 7 asciiutf-8 - useng 1-4 bytes for representing the chars,first 128chars are mapped 1to1 to asciiuntyped - The default type of an untyped constant is bool, rune, int, float64, complex128 or string respectively, depending on whether it is a boolean, rune, integer, floating-point, complex, or string constant.const a = 42
typed - means that we specified TYPE of the valueconst a int = 42
iota - predefined identifier representing the untyped int
const (
a = iota // a = 0
b // b = 1
c = iota // c = 2
)
func main() {
x := 10
fmt.Printf("x_value:%d\t\tx_binary:%b\n",x,x)
y := x << 1
fmt.Printf("y_value:%d\t\ty_binary:%b\n",y,y)
}
will output
x_value:10 x_binary:1010
y_value:20 y_binary:10100 // one 0 is added to the end of the x_binary to set y_binary
var x := 5
var test := true
Cannot be changed.
const x string = "Hello World"
if-else.go
if i % 2 == 0 {
// even
} else {
// odd
}
if-else-if.go
if i % 2 == 0 {
// action
} else if i % 3 == 0 {
// action
}
if-condition.go
// if var declaration;condition
if x := 100;x == 1000 { do something }
switch.go
switch i {
case 0:
fmt.Println("Zero")
case 1:
fmt.Println("One")
case 2:
fmt.Println("Two")
case 3:
fmt.Println("Three")
case 4:
fmt.Println("Four")
case 5:
fmt.Println("Five")
default:
fmt.Println("Unknown Number")
}
Go has only for loop.While,do and until can be created via for loop.
for.go
// for init statement(optional);evaluation;post statement (optional)
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
reflect.TypeOf(<some_type>).Kind() - can be used to print the underlaying type(usefull for slice,arrays,maps,struct)
An array is a numbered sequence of elements of a single type with a fixed length.It is zero based index record.
declaring-array.go
// x array needs an initiliazation also (e.g. x[i] = some_int)
var x [5]int
// array format arrayName := [arrayLength]arrayType{arrayElements}
x := []int{} // -> []
x := []int{0} // -> [0]
y := [5]float64{ 98, 93, 77, 82, 83 } // -> [98,93,77,82,83]
// or
y := [5]float64{
98,
93,
77,
82,
83,
}
for-range.go
// x is array, i is the index of the array and v is the value on the related index in the array
for i, v := range x {
// some action
}
WARNING: Go compiler doesn’t allow to declare a variable which wont’be used(i variable in above case).In that case one can use _ as special variable which tells compiler that we don’t need it.
A slice is a segment of array but it is not an array.Like arrays slices are indexible and have length.Unlike arrays this length is allowed to change.Slice is 3 word data type which consists of:
pointer to underlaying arraylength - length of the slicecapacity - allocated space for slice dataTo create a slice one has several options:
var x []int - with varx := make([]int,5) - with make builtin function where one should specify length and optionaly capacity.It will allocate and initialize the memory for the slice with zeroed values of related TYPExptr = new(int) - with new builtin function which will allocate memory and return pointslice.go
// one option for creating slice
var x []float64 // -> []
// preferred way for creating slice
x := make([]float64,5)
// composite literal,creates new instance each time it is evaluated
// x := type{value}
x := []int{1,2,3,4} // -> [1 2 3 4]
// slicing - slice can be created with operator [low:high] as well. low is starting position,high is until position
x := []int{0,0,0,0,0}
y := x[0:3] // -> [0 0 0 0]
// make can use 3rd param for capacity which represents the capacityof underlying array.
// in case slice is extening its capacity Go will double capacity on the fly
x := make([]float64, 5, 10)
WARNING: slice is always associated with some array,and although they can never be longer than array, they can be smaller.
multidimensionalslice.go or slice of slices
x := []int{1,2,3} // -> [1 2 3]
y := []int{4,5,6} // -> [4 5 6]
xy := [][]int{x,y} // -> [ [1 2 3] [4 5 6]]
append.go
// append will add a new item to the end of the slice.
slice1 := []int{1,2,3} // -> [1,2,3]
slice2 := append(slice1, 4, 5) // -> [1,2,3,4,5]
slice1 = append(slice1, 4) // -> [1,2,3,4]
// slice2... is needed as append is expecting variadic number of elements and not a slice
slice1 = append(slice1, slice2...) // -> [1 2 3 1 2 3 4 5]
copy.go
slice1 := []int{1,2,3} // -> [1,2,3]
slice2 := make([]int, 2) // -> [0,0]
// content of slice1 will be copied to slice2 based on length of slice2
copy(slice2, slice1) // -> slice2 -> [1,2]
delete.go - with slicing and append
slice := []int{1,2,3,4,5,6} // -> [1 2 3 4 5 6]
slice := append(slice[:2], slice[4:]...) // -> [1 2 5 6]
A map is an unordered collection of key-value pairs.
map.go
// creating a map with composite literal style - map[KEY_TYPE]VALUE_TYPE
m := map[string]int{
"Mile": 10,
"Kitic": 21,
}
fmt.Println(m) // -> m = {"Mile":10,"Kitic":21}
// creating a map with make(map(KEY_TYPE)VALUE_TYPE)
m := make(map[string]int)
m["Mile"] = 10
m["Kitic"] = 21
fmt.Println(m["Mile"]) // -> 10
// accessing with m[KEY]
fmt.Println(m["Mile") // -> 10
// checking if key exists in the map via comma ok method, `ok` value is boolean type which will be evaluated as true or false depending on existance of the related key in the map
if v, ok := map["Mile"]; ok {
fmt.Println(v)
}
// adding a new item to the map
m["Mira"] = 20
fmt.Println(m) // -> m = {"Mile":10,"Kitic":21,"Mira":20}
// deleting an item from the map,it is recommended to check if key exists
if v,ok := m["Mira"]; ok { // if key "Mira" exists ok will be evaluated as true
fmt.Println(v)
delete(m, "Mira") // delete the key "Mira"
}
Range iterates over elements in a variety of data structures
range.go
for i,v := range string // iterating over strings,i is starting position of the rune and v is its code point
for i, num := range array // iterating over array,i is index and num is value
for _,num := range slice/array // iterating over slice/array
for k,v := range map // iterating over map, k is key, v is value.If any of them is not needed it can be replaced with `_`.
A struct is a composite data type which contains named fields, each of which has a name and a type.It allows us to compose together values of different types.A field declared with a type but no explicit filed name is called an embedded field or anonynmous field.An embedded field must be specified as a type name T or as a pointer to a non-interface type name *T.
named struct - define a named struct type// creating a new struct type
type person struct{
first string // name and the type
last string
age int
gender string
}
func main(){
// use composite literal person{}
p1 := person{ first:"James",last:"Bond",age:42,gender:"male",}
// do something...
}
embedded struct - creating a type struct which defined type is using another user-defined struct// user defined struct
type person struct {
first string // name and the type
last string
age int
gender string
}
// another user defined struct which is using above struct person
type secretAgent struct {
person // embedded or annonymous type,specifying only the type not and not the name from another struct
// license to kill
ltk bool
}
func main() {
sa1 := secretAgent{
person: person{
first: "James",
last: "Bond",
age: 42,
gender: "male",
},
ltk: true,
}
// do someting
}
anonymous struct - struct which doeasn’t have a name and it is not defined with type NAME struct {}func main() {
// defining a struct during initialization instead of `type NAME struct {}`
p1 := struct {
first string
last string
age int
gender string
}{
first: "James",
age: 10,
gender: "male",
last: "Bond",
}
fmt.Println(p1)
}
Go Playground anonymous struct
Is go an object oriented languate.Yes and no.Go has OOP aspects.But it’s all about TYPE.We create TYPES in Go; user-defined TYPES.We can then have VALUES of that type.We can assign VALUES of a user-defined TYPE to VARIABLES.
Functions are all about modules or small codes.When we define functions we use parameters but when we call functions we are using arguments.Functions may have return statement which is terminating the execution of current function,and optionally provides one or more result values.
func (
We can use function in package scope as well like
package main
import "fmt"
var f func() = func(){
fmt.Println("aaa")
}
func main(){
f() // -> "aaa"
fmt.Printf("type:%T\n",f) // -> type: func()
}
func main() {
// basic func
foo()
// func with arguments
bar("Mile")
// func with return
s1 := woo("Kitic")
fmt.Println(s1)
// func with multiple returns
x, y := foobar("Mira", "Skoric")
fmt.Println(x)
fmt.Println(y)
}
// func format
// func (r receiver) identifier(parameters(s)) (return(s)) { ... }
// foo is identifier,there are no parameters and no returns
func foo() {
fmt.Println("Hello from foo")
}
// scope of s string is limiter to the function bar
// bar is identifier,(s string) is parameter,there are no returns
func bar(s string) {
fmt.Println("Hello from bar,", s)
}
// scope of s string is limited to function woo
// woo is identifier, (s string) is parameter,string is return
func woo(s string) string {
return fmt.Sprint("Hello from woo, ", s)
}
// scope of the fn,ln,a and b variables are limited to foobar func
// in case we return multiple values we have to put them between `()`
// foobar is identifier,fn and ln are paramters, string and bool are return values
func foobar(fn string, ln string) (string, bool) {
a := fmt.Sprint(fn, " ", ln, `, says "Hello"`)
b := false
return a, b
}
variadic.go
func main(){
foo(1,2,3,4,5)
}
// ...int means it can receive multiple int parameters
func foo(x ...int) {
fmt.Println(x)
fmt.Printf("type:%T\n",x)
}
unfurling.go
func main() {
xi := []int{1, 2, 3, 4, 5}
// passing elemnts of xi via xi...
x := sum(xi...)
fmt.Println(x)
}
// func sum is expecting 0 or more number of int as parameters
func sum(x ...int) int {
fmt.Println(x)
fmt.Printf("type:%T\n", x)
sum := 0
for _, v := range x {
sum += v
}
return sum
}
Anonymous functions can accept parameters,return data, and do pretty mych anything else a normal function can do.You can even assign a regular function to a variable just like you do with an anonymous function.
anonymous.go
func main() {
// anonymoyus func without arguments
func() {
fmt.Println("anonymous func")
}()
// anonymous func with argument
func(x int) {
fmt.Println("the meaning of life:", x)
}(32) // we need to pass an argument to the anonymous function
// inititalizaion and anonymous func with arguments - func expression - or assigning a function to a variable
add := func(x, y int) int { // type of add is func
return x + y
}
fmt.Println(add(3, 5))
}
returning_string.go
func main(){
s1 := foo()
fmt.Println(s1)
}
func foo() string {
s := "Hello world"
return s
}
Go Playground returing a string
returning_func.go
func main() {
s1 := foo()
fmt.Println(s1)
x := bar()
fmt.Printf("type of x:%T\n", x) // -> func() int
fmt.Printf("type of x():%T\n", x()) // -> int
}
func foo() string {
s := "Hello world"
return s
}
// returning a type "func() int" as value
func bar() func() int {
return func() int { // retrurning "func() int" to bar() and expecting int as returns
return 42 // returning int to above anonymous func
}
}
Go Playground returning a func
Passing a function as an argument to another function is callback.
callback.go
func main() {
ii := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s := sum(ii...) // providing []int as argument
fmt.Println(s)
s1 := even(sum, ii...) // providing func sum as an argument
fmt.Println(s1)
s2 := odd(sum, ii...) // providing func sum as an argument
fmt.Println(s2)
}
// func sum is expecting []int as paramter
func sum(xi ...int) int {
total := 0
for _, v := range xi {
total += v
}
return total
}
// func even is expecting func and []int as parameters
func even(f func(xi ...int) int, vi ...int) int {
var yi []int
for _, v := range vi {
if v%2 == 0 {
yi = append(yi, v)
}
}
return f(yi...)
}
// func odd is expecting func and []int as parameters
func odd(f func(xi ...int) int, vi ...int) int {
var yi []int
for _, v := range vi {
if v%2 == 1 {
yi = append(yi, v)
}
}
return f(yi...)
}
callback2.go
func main() {
g := func(xi []int) int {
if len(xi) == 0 {
return 0
}
if len(xi) == 1 {
return 1
}
return xi[0] + xi[len(xi)-1]
}
// passing a g and slice of int ([]int) as argumetns to func foo()
f := foo(g, []int{1, 2, 3, 4, 5, 6, 7})
fmt.Println("f:",f)
}
// func foo() accepts parameters as `func(xf []int) int` which match definition of g (`func(xi []int) int`) and slice of int ([]int)
func foo(f func(xf []int) int, ii []int) int {
n := f(ii)
n++
return n
}
A clousure is a special type of anonymous function that references variables declared outside of the function itself.That means that we will be creating a function dynamically, but in this case we will be using variables that weren’t passed into the function as a parameter, but instead were available when the function was declared.
closure.go
func main() {
n := 0
counter := func() int {
n += 1
return n
}
fmt.Println(counter())
fmt.Println(counter())
}
Notice how our anonymous function has access to the n variable, but this was never passed in as a parameter when counter() was called. This is what makes it a closure!
Another interesting fact about the clousure functions is that they can still reference variables that they had access to during creation even if those variables are no longer referenced elsewhere.
closure-2.go
func main() {
counter := newCounter()
fmt.Println(counter()) // -> 1
fmt.Println(counter()) // -> 2
fmt.Println(counter()) // -> 3
counter2 := newCounter()
fmt.Printf("%T\n",counter2) // -> func() int
fmt.Printf("%T\n",counter2()) // -> int
fmt.Println(counter2()) // -> 1
fmt.Println(counter2()) // -> 2
fmt.Println(counter2()) // -> 3
}
func newCounter() func() int {
n := 0
return func() int {
n += 1
return n
}
}
In this example our closure references the variable n even after the newCounter() function has finished running. This means that our closure has access to a variable that keeps track of how many times it has been called, but no other code outside of the newCounter() function has access to this variable. This is one of the many benefits of a closure - we can persist data between function calls while also isolating the data from other code.
recursion.go
func main(){
f := factorial(4)
fmt.Println(f) // -> 24 (4*3*2*1)
}
func factorial(x uint) uint {
if x == 0 {
return 1
}
return x * factorial(x-1)
}
Go has a special statement called defer which schedules a function call to be run after the function completes.
defer.go
func first() {
fmt.Println("1st")
}
func second() {
fmt.Println("2nd")
}
func main() {
defer second() // defer moves function second() to the end of the `main` function
first()
}
Defer is ofter used when resources need to be freed in some way ( e.g. closing an opened file).Also, defer functions are run even if a run-time panic occurs.
panic_and_recover.go
func main() {
defer func() {
str := recover() // handling a run-time error
fmt.Println(str)
}()
panic("PANIC") // causing run-time error
}
Shortly, the pointer is a variable which stores an address of another variable, where some data is stored.
// set value for i to 10
// set memory location where value for variable i is stored to addri (&i)
// *&i - give me the value which is stored on memory location of &i
i := 10
x := new(int)
x = &i
// value of x is set to to addri (&i)
// &x - `&` is address operator and it is referencing to a memory address where x value is stored
// *x - `*` is dereferencing operator and it is used to access the value stored in x. as value stored in x is set to addri,*x is using to access to the value stored on memory location addri (or value of i)
// x has a TYPE of *int which is saying that this is a pointer to address where int value is stored
pointer.go
func main() {
x := 100
fmt.Printf("xtype: %T xvalue: %v xaddress: %p\n", x, x, &x) // -> xtype: int xvalue: 100 xaddress: 0xc00002c008
fmt.Println()
y := new(int)
fmt.Printf("ytype: %T yvalue: %v yaddress: %p %v\n", y, y, &y, *y) // -> ytype: *int yvalue: 0xc00002c048 yaddress: 0xc00000e030 0
fmt.Println()
y = &x
fmt.Printf("ytype: %T yvalue: %v yaddress: %p %v\n", y, y, &y, *y) // -> ytype: *int yvalue: 0xc00002c008 yaddress: 0xc00000e030 100
}

Variable x of TYPE int has VALUE of 100 of the same type.Its value ADDRESS in memory is stored at 0x0201.
After initialization and assignment via statement
var y *int = &x
variable y is getting a TYPE *int and its value ADDRESS in memory is stored ad 0x0206.Also, y variable value (*y) has VALUE equal to memory location of ADDRX.Thus, with *y we can access to the value stored at memory location ADDRX, which is now 100.
The * operator is a dereference operator.
The dereference here means that we are getting a value not of a pointer (which stores an address) but from the memory address where this pointer is… Well — pointed to :-).In previous example *y is getting a value from the memory address of variable y which was set to ADDRX.
The & is an address operator which returns a variable’s memory address.
Pointers are rarely used with Go’s built-in types, but they are extremely useful when paired with structs.
When to use pointers:
Methods provide a way to add behaviour to user-defined types(i.e struct).Methods are really functions that contain an extra parameter that’s declared between the keyword func and the function name.That extra parameter is called receiver.Receiver can be provided as value or pointer variable.Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
Format of method functions is
//func (r receivers) identifier(parameters) (return(s)) { ... }
method.go
type person struct {
first string
last string
age int
children []string
}
type secretAgent struct {
person
ltk bool
}
// func (r receiver) identifiers(parametes) (return(s)) { code }
// receiver will attach Speak() to the struct secretAgent
// any values of that TYPE(secretAgent) has access in this func
func (s secretAgent) Speak() {
fmt.Println(s.first, s.last, s.age)
for i, v := range s.children {
fmt.Println(i, v)
}
}
func main() {
sa1 := secretAgent{
person: person{
first: "Mile",
last: "Kitic",
age: 42,
children: []string{"lea", "mia"},
},
ltk: true,
}
fmt.Println(sa1)
// any value of sa1 has access to the function speak()/method
sa1.Speak()
}
value_and_pointer_methdos.go
// defining a struct object
type Vertex struct {
X, Y float64
}
// defining a method function with value receiver.Thus, it will NOT change the value of the receiver's values
// v Vertex - receivers,Abs() is identifier without parameters, float64 is return
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// defining method function with `pointer` receiver.Thus, it will change the value of the receiver's values
// v *Vertes - receivers,Scale is identifier with float64 parameter
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
// v type is main.Vertex
// method Scale is expecting pointer receiver (*main.Vertex)
// as v variable is addressable (has &v) Go lang is converting v.Scale(10) automaticaly to (&v).Scale(10) as Scale method is expecting pointer receiver
// regardless of v type (value or pointer) on calling method Scale with value or pointer receiver,Golang will use pointer receiver as Scale method only support pointer receiver
v.Scale(10) // methods with pointer receivers take either a value or a pointer as the receiver
// method Abs() is expecting value receiver (main.Vertex)
// as v is value receiver Go can
fmt.Println(v.Abs())
p := &v
p.Scale(10)
// p type is *main.Vertex
// as p can dereference value stored on v via *p Go is automatically converting p.Abs() to (*p).Abs() as method Abs() is only working with value receivers
fmt.Println(p.Abs()) // methods with value receivers take either a value or a pointer as the receiver
}
Go Playground value and pointer receivers
interface type is its interfaceany other type T consist of all methods declered with receiver type Tcorresponding point type *T is a set of all methods declared with receivers *T and TMethods may be declaerd on any named type defined in the same package, so long as its underlaying type is neither a pointer nor an interface.
In summary, in every valid method call expression,exactly one of these three statements is true:
receiver argument has the same type as the receiver parameter,for example both have type T or both have type *Treceiver argument is a variable of type T and the receiver parameter has type *T -> implicit (&variable)receiver argument has type *T and the receiver paramter has type T.The compiler implicitly derefernces the receiver,or loads the value -> implicit (*variable)The rule about pointer vs values for receivers is that value methods can be invoked on pointers and values, but pointers methods can only be invoked on pointers.This rule arises because pointer methods can modify the receiver;invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded.There is a handy exception.When the value is addressable (has (&var) the language takes care of the common case of invoking a POINTER METHOD on a value by inserting the address operator automatically.
When you declare a method using a value receiver, the method will always be operating against a copy of the value used to make the method call.When you call a method declared with a pointer receiver the value used to make the call is shared with the method.
Addressable values:
variable*ptrslice indexingfield selector (of struct)array indexingNon-addressable values:
&map["key"] - values in map&afunc() - return values from func&t.method() - method calls of var tA value is addressable if it is an element of a slice,an element of an addresable array,a field of an addressable struct, or the result of dereferencing a pointer.
Interfaces are named collections of method signatures.It allow us to define behaviour.Interface allow a VALUE to be of more than one TYPE
interface joke - hey baby,if you get this METHOD,you are my TYPE
interfaces_explained.go
// creating person struct with related fields
type person struct {
first string
last string
age int
children []string
}
// creating secretAgent struct with embedded person struct and related fields
type secretAgent struct {
person
ltk bool
}
// format for method fund - func (r receiver) identifiers(parametes) (return(s)) { code }
// receiver will attach Speak() to the struct secretAgent
// any values of that TYPE(secretAgent) has access in this func
// this func has no return values
func (s secretAgent) Speak() {
fmt.Println("I'am", s.first, s.last, " - the secretAgent Speak")
}
// this func has no return values
// receiver will attach Speak() to the struct person
// any values of that TYPE(person) has access in this func
func (p person) Speak() {
fmt.Println("I'am", p.first, p.last, " - the person Speak")
}
// anyone who has this TYPE(interface) has this method(Speak()) is also of TYPE human
type human interface {
// attach this method to the human interface
// if Speak() has return values we would specify them here
Speak()
}
// this function can take many different types, it depends on the methods interfaces and methods which are defined
func bar(h human) {
switch h.(type) {
case person:
// assertion (h.(person).first) is you are asserting something of a certain type
fmt.Println("I was passed into bar as person:", h.(person).first)
case secretAgent:
fmt.Println("I was passed into bar secretAgent with ltk:", h.(secretAgent).ltk)
}
fmt.Println("I was passed into func bar", h)
}
func main() {
sa1 := secretAgent{
person: person{
first: "Mile",
last: "Kitic",
age: 42,
children: []string{"lea", "mia"},
},
ltk: true,
}
// p1 is getting a value of type person
p1 := person{
first: "Mitar",
last: "Miric",
}
fmt.Println("sa1:", sa1)
// any value of sa1 has access to the function Speak()/method
sa1.Speak()
fmt.Println("p1:", p1)
// sa1 is getting a value of type human
// sa1 is getting a vlues from both types, human and secretAgent
bar(sa1)
// p1 is getting a value of type human
// p1 is getting a values from both types, human and person
bar(p1)
}
interfaces.go
// defining interface
type geometry interface {
// method set - list of methods(functions) that a type must have in order to "implement" the interface
area() float64 // defining method, as area() has return value we have to specify float64 as well
perim() float64 // defining method, as perim() has return value we have to specify float64 as well
}
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
// defining area() method for rect struct
func (r rect) area() float64 {
return r.width * r.height
}
// defining perim() method for rect struct
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
// defining area() method for circle struct
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
// defining perim() method for circle struct
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
// calling measure function with r as argument
measure(r)
// calling measure function with c as argument
measure(c)
}
The interface type that specifies zero methods is known as the empty interface -> interface{}.
An empty interface may hold values of any type.(Every type implements at least zero methods.)Empty interfaces are used by code that handles values of unknown type (e.g. fmt.Print takes any number of argumens of interface{}).
Under the hood, interface values can be thought of as a tuple of a value and a concrete type -> (value, type)
empty_interface.go
func main() {
var i interface{}
describe(i) // -> (<nil>, <nil>)
i = 42
describe(i) // -> (42,int)
i = "hello"
describe(i) // -> (hello, string)
i = [3]int{1,2,3}
describe(i) // -> ([1 2 3], [3]int)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
For an expression x of interface type and a type T, the primary expression:
x.(T)
asserts that x is not nil and that the value stored in x is of type T. The notation x.(T) is called a type assertion.
More precisely, if T is not an interface type, x.(T) asserts that the dynamic type of x is identical to the type T. In this case, T must implement the (interface) type of x; otherwise the type assertion is invalid since it is not possible for x to store a value of type T. If T is an interface type, x.(T) asserts that the dynamic type of x implements the interface T.
A type assertions provides access to an interface value’s underlying concrete value:
t := i.(T)
where the inteface value i holds the concrete type T and assigns the underlying T value to the variable t.To test whether an interface value holds a specific type following code is used:
t,ok := i.(T)
or type switch as
switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
type-switch.go
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21)
do("hello")
do(true)
}
Marshaling - transformin the memory representation of an object to a data format suitable for storage or transmission.Marshaling in go is used to encode/convert to json.
func main() {
type ColorGroup struct {
ID int // field names must be uppercase if we want to export them from the package
Name string // field names must be uppercase if we want to export them from the package
Colors []string // field names must be uppercase if we want to export them from the package
}
group := ColorGroup{
ID: 1,
Name: "Reds",
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
b, err := json.Marshal(group)
if err != nil {
fmt.Println("error:", err)
}
os.Stdout.Write(b) // -> {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
}
Unmarshaling is used to decode/convert an json object to struct.
func main() {
var jsonBlob = []byte(`[
{"Name": "Platypus", "Order": "Monotremata"},
{"Name": "Quoll", "Order": "Dasyuromorphia"}
]`)
type Animal struct {
Name string // field name must be uppercase if we want to export them from the package
Order string // field name must be uppercase if we want to export them from the package
}
var animals []Animal
err := json.Unmarshal(jsonBlob, &animals)
if err != nil {
fmt.Println("error:", err)
}
fmt.Printf("%+v", animals)
}
Encoder - an encoder writes JSON values to an output stream.
type user struct {
Name string
Age int
}
func main() {
p1 := user{
Name: "Mile",
Age: 10,
}
p2 := user{
Name: "Mira",
Age: 20,
}
users := []user{p1, p2}
// create *json.Encoder using os.Stdout as io.Writer interface
enc := json.NewEncoder(os.Stdout)
// formating the output to have prettier output
enc.SetIndent("","\t")
// similar as json.Marshal, enc.Encode is used for encoding JSON to os.Stdout
err := enc.Encode(users)
if err != nil {
fmt.Println(err)
}
}
Decoder - reads and decodes JSON values from an input stream.
type Message struct {
Name string
Text string
}
func main() {
var jsonStream = `
{"Name": "Ed", "Text": "Knock knock."}
{"Name": "Sam", "Text": "Who's there?"}
{"Name": "Ed", "Text": "Go fmt."}
{"Name": "Sam", "Text": "Go fmt who?"}
{"Name": "Ed", "Text": "Go fmt yourself!"}`
// *decoder needs io.Reader as input
dec := json.NewDecoder(strings.NewReader(jsonStream))
fmt.Printf("%T\n", dec)
for {
// reading line by line until EOF
var m Message
// similar as json.Unmarshal, dec.Decode is used for decoding JSON value and store it to variable m
// io.EOF is used to read until EOF
if err := dec.Decode(&m); err == io.EOF {
break
} else if err != nil {
fmt.Println(err)
}
fmt.Printf("%s: %s\n", m.Name, m.Text)
}
}
A type satisfies an interface if it possesses all the methods the interface requires.*os.File has methods for Write,Close and Read
▶ go doc -short os.File.Write
func (f *File) Write(b []byte) (n int, err error)
Write writes len(b) bytes to the File. It returns the number of bytes
written and an error, if any. Write returns a non-nil error when n !=
len(b).
▶ go doc -short os.File.Close
func (f *File) Close() error
Close closes the File, rendering it unusable for I/O. On files that support
SetDeadline, any pending I/O operations will be canceled and return
immediately with an error. Close will return an error if it has already been
called.
▶ go doc -short os.File.Read
func (f *File) Read(b []byte) (n int, err error)
Read reads up to len(b) bytes from the File. It returns the number of bytes
read and any error encountered. At end of file, Read returns 0, io.EOF.
We can check what methods are supported by type *os.File by triggering following code:
func main() {
Print(os.Stdout)
}
func Print(x interface{}) {
v := reflect.ValueOf(x)
t := v.Type()
fmt.Printf("type %s\n", t)
for i := 0; i < v.NumMethod(); i++ {
methType := v.Method(i).Type()
fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
strings.TrimPrefix(methType.String(), "func"))
}
}
which will print
type *os.File
func (*os.File) Chdir() error
func (*os.File) Chmod(os.FileMode) error
func (*os.File) Chown(int, int) error
func (*os.File) Close() error
func (*os.File) Fd() uintptr
func (*os.File) Name() string
func (*os.File) Read([]uint8) (int, error)
func (*os.File) ReadAt([]uint8, int64) (int, error)
func (*os.File) ReadFrom(io.Reader) (int64, error)
func (*os.File) Readdir(int) ([]os.FileInfo, error)
func (*os.File) Readdirnames(int) ([]string, error)
func (*os.File) Seek(int64, int) (int64, error)
func (*os.File) SetDeadline(time.Time) error
func (*os.File) SetReadDeadline(time.Time) error
func (*os.File) SetWriteDeadline(time.Time) error
func (*os.File) Stat() (os.FileInfo, error)
func (*os.File) Sync() error
func (*os.File) SyscallConn() (syscall.RawConn, error)
func (*os.File) Truncate(int64) error
func (*os.File) Write([]uint8) (int, error)
func (*os.File) WriteAt([]uint8, int64) (int, error)
func (*os.File) WriteString(string) (int, error)
Interfaces for io.Writer,io.Reader,io.Closer and io.ReadWriter.
▶ go doc -short io.Writer
type Writer interface {
Write(p []byte) (n int, err error)
}
▶ go doc -short io.Reader
type Reader interface {
Read(p []byte) (n int, err error)
}
▶ go doc -short io.Closer
type Closer interface {
Close() error
}
▶ go doc -short io.ReadWriter
type ReadWriter interface {
Reader
Writer
}
Thus,an *os.File satisfies io.Reader, Writer, Closer, and ReadWriter.
On the other hand we can check what methods are supported by type *bytes.Buffer by triggering following code:
func main() {
Print(bytes.NewBuffer([]byte{}))
}
func Print(x interface{}) {
v := reflect.ValueOf(x)
t := v.Type()
fmt.Printf("type %s\n", t)
for i := 0; i < v.NumMethod(); i++ {
methType := v.Method(i).Type()
fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
strings.TrimPrefix(methType.String(), "func"))
}
}
which will print
type *bytes.Buffer
func (*bytes.Buffer) Bytes() []uint8
func (*bytes.Buffer) Cap() int
func (*bytes.Buffer) Grow(int)
func (*bytes.Buffer) Len() int
func (*bytes.Buffer) Next(int) []uint8
func (*bytes.Buffer) Read([]uint8) (int, error)
func (*bytes.Buffer) ReadByte() (uint8, error)
func (*bytes.Buffer) ReadBytes(uint8) ([]uint8, error)
func (*bytes.Buffer) ReadFrom(io.Reader) (int64, error)
func (*bytes.Buffer) ReadRune() (int32, int, error)
func (*bytes.Buffer) ReadString(uint8) (string, error)
func (*bytes.Buffer) Reset()
func (*bytes.Buffer) String() string
func (*bytes.Buffer) Truncate(int)
func (*bytes.Buffer) UnreadByte() error
func (*bytes.Buffer) UnreadRune() error
func (*bytes.Buffer) Write([]uint8) (int, error)
func (*bytes.Buffer) WriteByte(uint8) error
func (*bytes.Buffer) WriteRune(int32) (int, error)
func (*bytes.Buffer) WriteString(string) (int, error)
func (*bytes.Buffer) WriteTo(io.Writer) (int64, error)
Thus, *bytes.Buffer satisfies Reader, Writer, and ReadWriter,but does not satisfy Closer because it does not have a Close method. As a shorthand, Go programmers often say that a concrete type is a particular interface type, meaning that it satisfies the interface. For example, a *bytes.Buffer is an io.Writer; an *os.File is an io.ReadWriter.
Sort package provides methods for sorting slices and user-defined collections.
Example for sorting slice of ints or strings.
func main() {
xi := []int{1, 2, 4, 6, 2, 0, 2}
xs := []string{"Mile", "Anci", "Pera", "Kica"}
fmt.Println("unsorted:", xi)
sort.Ints(xi) //
fmt.Println("sorted:", xi)
fmt.Println("unsorted:", xs)
sort.Strings(xs)
fmt.Println("sorted:", xs)
}
Example for sorting user-defined collection.
type Person struct {
Name string
Age int
}
type ByAge []Person
// by defining methods for ByAge type it also implements interface for sort.Interface
func (a ByAge) Len() int { return len(a) }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func main() {
p1 := Person{"Mile", 26}
p2 := Person{"Mitar", 21}
p3 := Person{"Mira", 32}
p4 := Person{"Bora", 25}
people := []Person{p1, p2, p3, p4}
// listing methods for ByAge(people)
// by defining methods for ByAge type it also implements interface for sort.Interface
fmt.Println("List of methods provided by ByAge(people):")
Print(ByAge(people))
sort.Sort(ByAge(people))
// alternative option to sort records in people based on Name field
// desc order people[i].Age > people[j].Age
// asc order people[i].Age < people[j].Age
sort.Slice(people, func(i, j int) bool {
return people[i].Name < people[j].Name
})
fmt.Println("people:",people)
}
func Print(x interface{}) {
v := reflect.ValueOf(x)
t := v.Type()
fmt.Printf("type %s\n", t)
for i := 0; i < v.NumMethod(); i++ {
methType := v.Method(i).Type()
fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
strings.TrimPrefix(methType.String(), "func"))
}
}
This package implements bcrypt hashing method.
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func main() {
s := "password123"
// arguments []byte(s) and bcrypt.MinCost
// generate encrypted data []byte as bs
bs, err := bcrypt.GenerateFromPassword([]byte(s), bcrypt.MinCost)
if err != nil {
fmt.Println(err)
}
fmt.Println(s)
fmt.Println(string(bs))
s1 := "password1234"
// comparing hash of previous encrypted data with provided []byte(s1)
err = bcrypt.CompareHashAndPassword(bs, []byte(s1))
fmt.Println(err)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(s))
}
Concurrency is a design pattern or the way we write code.Concurrency is not guaranteed that your code will run in parallel.For that you need to have machine with multiple CPUs and your code have to use concurrency patterns.
When a program starts, its only goroutine is that one that calls the main function, so we call it the main goroutine.New goroutines are created by the go statement.Syntactically, a go statemnet is an ordinary function or method call prefixed by the keyword go.A go statement causes the function to be called in a newly created goroutine.Notice the difference:
f() // call f();wait for it to return
go f() // create a new goroutine that calls f();don't wait
A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls method Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls metdho Done when finished. At the same time, method Wait can be used to block until all goroutines have finished.
var wg sync.WaitGroup
func main() {
fmt.Println("OS:\t\t", runtime.GOOS)
fmt.Println("ARCH:\t\t", runtime.GOARCH)
fmt.Println("CPU:\t\t", runtime.NumCPU())
fmt.Println("Goroutine:\t", runtime.NumGoroutine())
// Add adds delta, which may be negative, to the WaitGroup counter.
wg.Add(1)
// launch foo() in new goroutine
go foo()
bar()
fmt.Println("CPU:\t\t", runtime.NumCPU())
fmt.Println("Goroutine:\t", runtime.NumGoroutine())
// Wait blocks until the WaitGroup counter is zero.
wg.Wait()
// when func main() is done it will shutdown all goroutines which where launched from it
}
func foo() {
for i := 0; i < 10; i++ {
fmt.Println("foo:", i)
}
// Done decrements the WaitGroup counter by one.
wg.Done()
}
func bar() {
for i := 0; i < 10; i++ {
fmt.Println("bar:", i)
}
}
When two or more goroutines have unsynchronized access to a shared resource and attempt to read and write to that resource at the same time, you have what’s called a race condition.Read and write operations against a shared resource must always be atomic, or in other words, done by only one goroutine at a time.
Use go run -race main.go to detect if there race condition occurs.
var x = 0
func myincrement() {
x = x + 1
}
func main() {
for i := 0; i < 1000; i++ {
go myincrement()
}
time.Sleep(1 * time.Second)
fmt.Println("final value of x is", x)
}
will output
▶ go run -race race.go
==================
WARNING: DATA RACE
Read at 0x0000006469c0 by goroutine 8:
main.myincrement()
/home/mjv/go/src/learn/test/race.go:11 +0x3a
Previous write at 0x0000006469c0 by goroutine 7:
main.myincrement()
/home/mjv/go/src/learn/test/race.go:11 +0x56
Goroutine 8 (running) created at:
main.main()
/home/mjv/go/src/learn/test/race.go:16 +0x4f
Goroutine 7 (finished) created at:
main.main()
/home/mjv/go/src/learn/test/race.go:16 +0x4f
==================
final value of x is 3
Found 1 data race(s)
exit status 66
Mutex is used for avoiding race conditions which can happen in concurrency designed code.
mutex example
var x = 0
// adding variable for Mutex
var mu sync.Mutex
// adding variable for WaitGroup
var wg sync.WaitGroup
func myincrement() {
// call Done when finish with work, decrement WaitGroup counter when goroutine completes
defer wg.Done()
mu.Lock() // locking mu
x = x + 1
mu.Unlock() // unlocking mu
}
func main() {
for i := 0; i < 1000; i++ {
// increment the WaitGroup counter
wg.Add(1)
go myincrement()
}
time.Sleep(1 * time.Second)
// wait for all goroutines to finish their work
wg.Wait()
fmt.Println("final value of x is", x)
}
Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms.
Most common actions are:
atomic.AddT - secure way to add delta to some value with type Tatomic.LoadT - secure way to read some value with type Tatomic.StoreT - secure way to write to some value with type Tatomic example
// add variable for WaitGroup
var wg sync.WaitGroup
// add varible for atomic
var counter uint64
const gs = 1000
func myincrement(c *uint64) {
// use AddUint64 for secure increasing.Only one goroutine can perform and complete this add operation at a time
// when goroutines attempt to call any atomic function, they're automatically synchronized against the variable that's referenced
atomic.AddUint64(c, 1)
// LoadUint64 is used to read in a safe way
fmt.Println("load:",atomic.LoadUint64(c))
// Gosched yields the processor, allowing other goroutines to run. It does not
// suspend the current goroutine, so execution resumes automatically.
runtime.Gosched()
wg.Done()
}
func main() {
// set the number of goroutines to wait for
wg.Add(gs)
for i := 0; i < gs; i++ {
go myincrement(&counter)
}
time.Sleep(1 * time.Second)
// wait for all goroutines completes their work
wg.Wait()
fmt.Println("final value of c is", counter)
}
A channel is a communication mechanism that lets one goroutine send values to another goroutine.Channels have to pass/receive the value at the same time.By default, sends and receives BLOCK until the other side is ready.Meaning, a receive will block only if there’s no value in the channel to receive and,a send will block only if there’s no available buffer to place the value being sent.This allows goroutines to synchronize without explicit locks or condition variables.
As with maps, a channel is a reference to the data structure created by make.When we copy a channel or pass one as an argument to a function, we are copying a reference, so caller and callee refer to the same data structure.
A send statement transmits a value from one goroutine,through the channel,to another goroutine executing a corresponding receiver expression.A send operatoin on an unbuffered channel blocks the sending goroutine until another goroutine executes a coresponding receive on the same channel, at which point the value is transmitted and both goroutiens may continue.Conversely, if the receive operation was attempted first, the receiving goroutine is blocked until another goroutine performs a send on the same channel.
In a send statment, the <- separates the cahnnel and value operands -> ch <- x
In a receive expressionm <- precedes the channel operand -> x = <-ch
Buffered and unbuffered channels
unbuffered := make(chan int) // capacity is 0
buffered := make(chan int,2) // capacity is 2
If capacity is zero or absent,the channel is unbuffered and communication SUCCEEDS only when BOTH sender and receiver are ready.Otherwise,the channel is buffered and communications succeds without blocking if the buffer is not full (sends) or not empty (receives).A nil channel is NEVER ready for communication.
Directional channel Conversion from bidirectional to undirectional channel types are permitted in any assignment.
c := make(chan int) // bidirectional, send and receive
cs := make(chan <- int) // sends (outbound) only
cr := make(<- chan int) // receives (inbound) only
channels example
func main() {
c := make(chan int)
// send
go foo(c)
// receive
// bar will block until there is a value to receive,in other words until foo completes
bar(c)
fmt.Println("terminating")
}
// declaring foo as send only channel parameter which can receive bidirectional channel
func foo(c chan<- int) {
for i := 0; i < 10; i++ {
fmt.Println("i:", i, "in foo")
}
c <- 42
}
// declaring bar as read only channel parameter which can receive bidirectional channel
func bar(c <-chan int) {
fmt.Println(<-c)
}
Close a channel
A sender can close a channel to indicate that no more values will be sent.Closing is not needed, it is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.
Range over channel
The loop for i := range channel {} receives values from the channel repeatedly until it is closed.
close example
func main() {
c := make(chan int)
// send
go func() {
for i := 0; i < 5; i++ {
c <- i
}
// close the channel so we can read from it with range
// if we hadn't close the channel range c will wait for more values from the channel
close(c)
}()
// range over a channel is pulling values from it until channel is closed
for element := range c {
fmt.Println(element)
}
fmt.Println("terminating")
}
Select statement waits until the communication (send or receive operation) is prepared for some cases to begin.It then performs that communication and execytes the case’s associated statements.The default statement in the select statement is used to protect select statement from blocking.This statement executes when there is no case statement is ready to proceed.
select example
func main() {
even := make(chan int)
odd := make(chan int)
quit := make(chan int)
// send
go send(even, odd, quit)
// receive
receive(even, odd, quit)
fmt.Println("terminating")
}
func send(e, o, q chan<- int) {
for i := 0; i < 100; i++ {
if i%2 == 0 {
e <- i
} else {
o <- i
}
}
q <- 11
}
func receive(e, o, q <-chan int) {
for {
select {
case v := <-e:
fmt.Println("receive from even:", v)
case v := <-o:
fmt.Println("receive from odd:", v)
case <-time.After(time.Second):
fmt.Println("timeout")
case v := <-q:
fmt.Println("receive from quit:", v)
// exit from func receive
return
}
}
}
It is used to check if the channel is closed.After the last value has been received from a closed channel c, any receive from c will succeed without blocking, returning the zero value for the channel element. The form
x, ok := <-c
will also set ok to false for a closed channel.
comma ok idiom example
func main() {
even := make(chan int)
odd := make(chan int)
quit := make(chan bool)
// send
go send(even, odd, quit)
// receive
receive(even, odd, quit)
fmt.Println("terminating")
}
func send(even, odd chan<- int, quit chan<- bool) {
for i := 0; i < 10; i++ {
if i%2 == 0 {
even <- i
} else {
odd <- i
}
}
close(quit)
}
func receive(even, odd <-chan int, quit <-chan bool) {
for {
select {
case v := <-even:
fmt.Println("receive from even:", v)
case v := <-odd:
fmt.Println("receive from odd:", v)
case i, ok := <-quit:
if !ok {
fmt.Println("from comma ok", i, ok)
// exit from func receive
return
} else {
fmt.Println("from comma ok", i, ok)
}
}
}
}
comma idiom example 2
func main() {
c := make(chan int)
go func() {
c <- 42
close(c)
}()
// it will receive a value as well
v, ok := <-c
fmt.Println(v, ok)
// After the last value has been received from a closed
// channel c, any receive from c will succeed without blocking, returning the
// zero value for the channel element and ok will be set to false
v, ok = <-c
fmt.Println(v, ok)
}
A function can read from multiple inputs and proceed until all are closed by multiplexing the input channels onto a single channel that’s closed when all the inputs are closed.
fan-in example
func main() {
even := make(chan int) // bidirectional channel
odd := make(chan int) // bidirectional channel
fanin := make(chan int) // bidirectional channel
// send
go send(even, odd)
// receive
go receive(even, odd, fanin)
for v := range fanin {
fmt.Println(v)
}
fmt.Println("terminating")
}
// populate two outbound(send to) channels with data
func send(even, odd chan<- int) {
for i := 0; i < 10; i++ {
if i%2 == 0 {
even <- i
} else {
odd <- i
}
}
// close outbound channels when there are no data to add to it
close(even)
close(odd)
}
// use two inbound channels and merge it to one outbound channel
func receive(even, odd <-chan int, fanin chan<- int) {
var wg sync.WaitGroup
wg.Add(2)
go func() {
for v := range even {
fanin <- v
}
wg.Done()
}()
go func() {
for v := range odd {
fanin <- v
}
wg.Done()
}()
wg.Wait()
close(fanin)
}
Multiple functions can read from the same channel until that channel is closed.
func main() {
c1 := make(chan int)
c2 := make(chan int)
go populate(c1)
go fanOutIn(c1, c2)
for v := range c2 {
fmt.Println(v)
}
fmt.Println("terminating")
}
// populate c as outbound channel
func populate(c chan<- int) {
for i := 0; i < 10; i++ {
c <- i
}
close(c)
}
// populate c2 as outbound channel with N number of workers which are using c1 as inbound channel
func fanOutIn(c1 <-chan int, c2 chan<- int) {
var wg sync.WaitGroup
for v := range c1 {
wg.Add(1)
go func(v2 int) {
c2 <- timeConsumingWork(v2)
wg.Done()
}(v)
}
fmt.Println("goroutine:", runtime.NumGoroutine())
wg.Wait()
close(c2)
}
func timeConsumingWork(n int) int {
time.Sleep(time.Microsecond * time.Duration(rand.Intn(500)))
return n + rand.Intn(1000)
}
This package is used for processes which are working with request-scoped data ( used IDs,auth tokens,session IDs,tracing IDs,…). Its purpose is to pass request-scope values, cancellation signals,deadline across API boundaries to all the goroutines involved in handling a request.
func main() {
// context.Background() is parent context
// ctx type is *context.cancelCtx, cancel type is context.CancelFunc
ctx, cancel := context.WithCancel(context.Background())
fmt.Println("error check 1:", ctx.Err())
fmt.Println("goroutines check 1:", runtime.NumGoroutine())
go func() {
n := 0
for {
select {
// when cancel() is called this func is terminated
case <-ctx.Done():
return
default:
n++
time.Sleep(time.Millisecond * 200)
fmt.Println("working:", n)
}
}
}()
time.Sleep(time.Second * 2)
fmt.Println("error check 2:", ctx.Err())
fmt.Println("goroutines check 2:", runtime.NumGoroutine())
fmt.Println("about to cancel context")
cancel()
fmt.Println("cancelled context")
time.Sleep(time.Second * 2)
fmt.Println("error check 3:", ctx.Err())
fmt.Println("goroutines check 3:", runtime.NumGoroutine())
}
Following expressions can be used to log errors:
fmt.Println() // logging to stdout
log.Println() // logging to stdout including the date
log.Fatalln() // logging with Print() and calling os.Exit(1) afterwards
log.Panicln() // logging with Print() and calling panic() afterwards
errors.New(string) // createing new type of error type
fmt.Errorf() // wrapper of errors.New(string)
Package testing provides support for automated testing of Go packages. It is intended to be used in concert with the “go test” command, whichautomates execution of any function.In short B is used for Benchmarking,E is used for Examples and T is used for Testing, or in short BET.
Tests must be in the same file that ends with *_test.go.It is recommended to put it the same package as the one being tested.Signature of the test is shown below:
// Function TestFoo is used to test func Foo
// Name of the function for testing must be declared as TestXxx where first X must be uppercase
func TestFoo(t *testing.T) {
// what we got by calling target function
got := Foo("Mile")
// what we want to get as an output from target function
want := "Welcome from Foo: Mile"
if got != want {
t.Errorf("Expected: %v,got: %v\n", want, got)
}
}
table test
// addSum is the function which sum elements of slice of int values
func addSum(xi ...int) int {
total := 0
for _, v := range xi {
total += v
}
return total
}
// TestAddSum is testing addSum function
func TestAddSum(t *testing.T) {
tests := []struct {
list []int
want int
}{
{list: []int{1, 1, 1}, want: 3},
{list: []int{0, 1, 2}, want: 3},
{list: []int{1, -1, 0, 1}, want: 1},
{list: []int{1, 2, 3}, want: 6},
}
for _, tt := range tests {
testname := fmt.Sprintf("%d -> %d", tt.list, tt.want)
t.Run(testname, func(t *testing.T) {
if got := addSum(tt.list...); !reflect.DeepEqual(tt.want, got) {
t.Errorf("Expected: %v, got: %v", tt.want, got)
}
})
}
}
Running test can be done via following commands:
go test -run=funcName // test against funcName
go test -v // verbose
One can check the code coverage of the test via:
go test cover // It is considered as perfect if the coverage value is above 70%.
// create coverprofile's output as c.out
go test -coverprofile c.out
// open c.out in the browser
go tool cover c.out
// print coverage per functions
go tool cover -func=c.out
// write c.out to html file coverage.html
go tool cover -html=c.out -o coverage.html
Same rules applies for the examples as for the test functions.These functions will be visible on documentation page of tha package.
// Function ExampleFoo is used to show how to use func Foo.This will be printed on the documentation page of the package and related func.
// Name of the function used as example must be declared as ExampleXxx where first X must be uppercase
func ExampleFoo() {
fmt.Println(Foo("Mile"))
// Output:
// Welcome from Foo: Mile
}
It is used to measure the performance of the code.
// Function BenchmarkFoo is used to benchmark function Foo
// Name of the function for benchmarking must be declared as BenchmarkXxx where first X must be uppercase
// The benchmark function must run the target code b.N times.
func BenchmarkFoo(b *testing.B) {
for i := 0; i < b.N; i++ {
Foo("Mile")
}
}
Running benchmark tests against some code is done via:
go test -bench . // all functions
go test -bench=funcName // specific function
go test -bench . -v -count N // verbose (-v) plus call benchmark for N times (count)
The output of a bencmark looks like:
▶ go test -bench .
goos: linux
goarch: amd64
pkg: learn/benchmark/foo
BenchmarkFoo-4 7853067 261 ns/op
PASS
ok learn/benchmark/foo 2.213s
which means that loop runs 7853067 times at speed of 261ns per loop.