- intro, download, install & setup
- basics, syntax
- control flow
- more types, data structures
- methods & interfaces
- concurrency
- nice to know stuff
- golang gotchas <-- especially if you are coming from python, ruby etc
- workspace
Last active
June 25, 2024 17:28
-
-
Save komuw/de00c2d489f041f4a214 to your computer and use it in GitHub Desktop.
my golang notes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
1. Intro and download | |
url = https://storage.googleapis.com/golang/go1.4.2.linux-386.tar.gz #32bit | |
url = https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz #64bit | |
$ sudo wget --directory-prefix=/usr/local url | |
$ sudo tar --directory=/usr/local -xzf go1.4.2.linux-amd64.tar.gz | |
# add the line | |
export PATH=$PATH:/usr/local/go/bin | |
#to /etc/profile(system-wide install) or $HOME/.profile | |
//some references | |
# https://golang.org/doc/effective_go.html | |
# https://gobyexample.com | |
# https://tour.golang.org | |
# http://golang.org/doc/code.html #how to organise ua workspace | |
# https://www.youtube.com/watch?v=XCsL89YtqCs #how to organise ua workspace(video) | |
# https://lincolnloop.com/blog/introduction-go-debugging-gdb/ #pdb like debugging | |
# https://cgdb.github.io/ #debug | |
# https://github.com/golang/go/wiki#additional-go-programming-wikis | |
*/ | |
/* | |
# create golang code dir #ie workspace | |
$ mkdir $HOME/work/gocode | |
$ export GOPATH=$HOME/work/gocode #add to ~/.profile | |
$ mkdir -p $HOME/work/gocode/src/github.com/komuw | |
$ cd $HOME/work/gocode/src/github.com/komuw | |
$ mkdir hello | |
$ cd hello | |
$ nano hello.go | |
*/ | |
package main | |
import "fmt" | |
func main() { | |
fmt.Printf("hello, world\n") | |
} | |
/* | |
$ go run hello.go | |
$ go install #install the binary | |
# it gets installed in $HOME/work/gocode/bin | |
$ ls $HOME/work/gocode/bin | |
$ $HOME/work/gocode/bin/hello #to run it | |
$ export PATH=$HOME/work/gocode/bin:$PATH #add bin to path | |
#add to ~/.profile | |
$ hello #now u can run hello from anywhere | |
#lets create a package(can be imported by other packages) | |
#they have a package name other than main | |
$ cd $HOME/work/gocode/src/github.com/komuw | |
$ mkdir string | |
$ cd string and touch string.go | |
*/ | |
package string //package name | |
func Reverse(s string) string { | |
b := []byte(s) | |
for i := 0; i<len(b)/2; i++ { | |
j := len(b)-i-1 | |
b[i], b[j] = b[j], b[i] | |
} | |
return string (b) | |
//NB: The fxn begins with capital letter, so that it can be exported and thus able to be imported by other packages. | |
} | |
/* | |
$ go build #check that it compiles | |
$ go install # make it available | |
$ ls $HOME/work/gocode/pkg/linux_amd64/github.com/komuw/ | |
string.a | |
#lets use that package | |
$ cd hello && nano hello.go | |
*/ | |
package main | |
import ( | |
"fmt" | |
"github.com/komuw/string" | |
) | |
func main() { | |
fmt.Printf(string.Reverse("hello, world\n")) | |
} | |
/* | |
$ go install | |
$ hello #run | |
#tests | |
$ cd string | |
$ touch string_test.go #test files live in same dir and end in _test | |
$ go test #run tests | |
#ls ~/.profile | |
#golang | |
export PATH=$PATH:/usr/local/go/bin | |
export GOPATH=$HOME/work/gocode | |
export PATH=$HOME/work/gocode/bin:$PATH | |
*/ | |
// Golang project layout | |
cmd/ <- executables go here | |
keyserver/main.go | |
storageserver/main.go | |
upspin/main.go | |
store/ <- domain logic and differen implemenations regarding storage go here | |
/inprocess/ | |
/remote/ | |
upspin/ <- domain types (models) and interfaces go here | |
upspin.go <- contains: User struct, KeyServer interface, StoreServer interface | |
internal/ <- unimportable packages | |
/account/ | |
/account.go | |
/account_test.go <- unit tests | |
/proxy/ | |
test/ <- integration tests | |
copy_test.go | |
go.mod | |
go.sum |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//I. BASICS | |
package main | |
import "fmt" | |
func main() { | |
fmt.Println("Hello,") | |
} | |
//1. Packages | |
package main | |
import ( | |
"fmt" | |
"math/rand" | |
) | |
func main() { | |
fmt.Println("My favorite number is", rand.Intn(10)) | |
} | |
//every prog is made of packages. | |
//progs start running in package main | |
// the package name is the same as the last element of the import path. | |
//"math/rand" package comprises files that begin with the statement package rand. | |
//Imports: u can do in each line or group em using () | |
import "fmt" | |
import "math" | |
//Exports: After importing a package, you can refer to the names it exports. | |
//a name is exported if it begins with a capital letter. | |
// u cant use math.pi but u can use math.Pi | |
//2. Functions | |
package main | |
import "fmt" | |
func add(x int, y int) string {// this fxn will return a string | |
return x + y | |
} | |
func main() { | |
fmt.Println(add(42, 13)) | |
} | |
//fxns can take zero or more args. note that the arg types come after arg_name | |
//u can do | |
func add(x, y int) //if the args share same type | |
//functions can return multiple values | |
func swap(x, y string) (string, string) { | |
return y, x | |
} | |
func main() { | |
a, b := swap("hello", "world") | |
fmt.Println(a, b) | |
} | |
//return values can be named | |
func split(a int) (x, y int) { //the named return values here are named x & y | |
x = a * 4 / 9 | |
y = a - x | |
return //this is naked return, it returns current values(x,y) of the results; its discouraged though. | |
} | |
func main() { | |
fmt.Println(split(17)) | |
} | |
//3. VARIABLES | |
var c, python, java bool | |
func main() { | |
var i int | |
fmt.Println(i, c, python, java) | |
} | |
//var declares variables | |
//it can be at package, or function level | |
//vars can have intializers, in which case u may omit the type(var will infer type from the intializer) | |
var c, python, java = true, false, "no!" | |
//inside a fxn u can use the := shorthand to declare vars. := does type inference automatically | |
func main() { | |
var i, j int = 1, 2 | |
k := 3 | |
c, java := true, "no!" | |
fmt.Println(i, j, k, c, python, java) | |
} | |
//u cant use := outside a fxn | |
//4. BASIC 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 | |
//vars can be grouped just like imports | |
import ( | |
"fmt" | |
"math/cmplx" | |
) | |
var ( | |
ToBe bool = false | |
MaxInt uint64 = 1<<64 - 1 | |
z complex128 = cmplx.Sqrt(-5 + 12i) | |
) | |
// a << b == a * 2^b (left shift) | |
// a>>b == a / 2^b (right shift) | |
//thus | |
1 << 64 - 1 == (1 * 2^64) - 1 | |
//vars that aren't initialized are given their zero values | |
0 for numeric types. //var f float64 | |
false for boolean. //var b bool | |
"" for strings. //var s string | |
//Type conversions: | |
var i int = 42 | |
var f float64 = float64(i) | |
//Or, put more simply: | |
i := 42 | |
f := float64(i) | |
//Constants | |
const Pi = 3.14 | |
func main() { | |
const World = " " | |
fmt.Println("Hello", World) | |
fmt.Println("Happy", Pi, "Day") | |
} | |
//declared with const keyword | |
//they can be char, string, numeric or boolean | |
//u cant declare em using := | |
//5. A little bit about some go builttin types: golang.org/pkg/builtin/ | |
//int32 is the set of all signed 32-bit integers ie -(2**32)/2 to +(2**32)/2 | |
//ie range -2billion - +2billion | |
//int64 is the set of all signed 64-bit integers. ie -(2**64)/2 to +(2**64)/2 | |
//int8 is the set of all signed 8-bit integers. Range: -128 to 127. -(2**8)/2 to +(2**8)/2 | |
//rune is an alias for int32 and is equivalent to int32 in all ways. | |
//It is used, by convention, to distinguish character values from integer values. | |
//uint32 is the set of all unsigned 32-bit integers. Range: 0 to 4294967295. 0 to +2**32 | |
//byte is an alias for uint8 and is equivalent to uint8 in all ways. | |
//It is used, by convention, to distinguish byte values from 8-bit unsigned integer values. | |
//uint8 is the set of all unsigned 8-bit integers. Range: 0 to 255. | |
//5. Runes, Bytes, strings | |
var y rune | |
y = 'a' | |
x = '⌘' | |
fmt.Println(y) | |
//prints 97 | |
//bcoz the char 'a' is the unicode codepoint U+0061 (or 97 in ints) | |
fmt.Println(x) //8984 | |
//NB: | |
var y rune | |
y = 'abv' | |
fmt.Println(y) | |
//will raise error bcoz runes arent strings but 'abv' is a string | |
var myb byte | |
myb = 'a' | |
myb2 = '⌘' | |
fmt.Println(myb) //97 | |
fmt.Println(myb2) //raises err 'constant 8984 overflows byte' | |
//bcoz remember; bytes are likke uint8 (0-255) and 8984 is greater than 255 | |
func main() { | |
s := []byte("ab") | |
fmt.Println(s) //outputs: [97 98] . char 'b' ==int 98 unicode | |
fmt.Println(string(s)) //outputs 'ab' | |
} | |
//"strings are good for human readable text and bytes for pretty much anything else." - http://vluxe.io/golang-bytes.html | |
const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98" | |
fmt.Println(sample) //outputs: ��=� ⌘ | |
for i := 0; i < len(sample); i++ { | |
fmt.Printf("%x ", sample[i]) //%x (hexadecimal) format | |
//use "%+q" to show quoted string ie(its unicode representation) | |
} | |
//outputs: bd b2 3d bc 20 e2 8c 98 | |
//indexing a string accesses individual bytes, not characters. | |
//when we store a character value in a string, we store its byte-at-a-time representation | |
//A for range loop, by contrast to a normal for loop, 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. | |
const nihongo = "日本語" | |
for key, value := range nihongo { | |
fmt.Printf("%#U starts at byte position %d\n", value, key) | |
} | |
//outputs: | |
U+65E5 '日' starts at byte position 0 | |
U+672C '本' starts at byte position 3 | |
U+8A9E '語' starts at byte position 6 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//II. CONTROL FLOW | |
//1. for loop | |
for i := 0; i < 10; i++ { | |
sum += i //sum=sum+1 | |
} | |
fmt.Println(sum) | |
sum := 1 | |
for ; sum < 1000; { | |
sum += sum | |
} | |
fmt.Println(sum) | |
//u can leave the pre & post statements | |
sum := 0 | |
for ; sum < 1000; { | |
sum += sum | |
} | |
fmt.Println(sum) | |
//this one will never complete, coz intially sum=0 and sum =sum+sum == 0+0, thus will always be zero | |
sum := 0 | |
for sum < 1000 { | |
sum = sum+sum | |
} | |
fmt.Println(sum) | |
//for without anything else(except loop condition) acts like a while statement | |
for { | |
fmt.Println("cool") | |
} | |
//for loop without condition, loops forever | |
//2. if statement | |
func sqrt(x float64) string { | |
if x < 0 { | |
return sqrt(-x) + "i" | |
} | |
return fmt.Sprint(math.Sqrt(x)) | |
} | |
func main() { | |
fmt.Println(sqrt(2), sqrt(-4)) | |
} | |
import ("fmt" | |
"math" | |
) | |
func pow(x, n, lim float64) float64 { | |
if v := math.Pow(x, n); v < lim { | |
return v //v is only available here | |
} | |
return lim //u cant access v here coz v is a var declared in the statement preceding the condition (v<lim) of 'if clause' | |
} | |
func main() { | |
fmt.Println( | |
pow(3, 2, 10) | |
) | |
} | |
//the if statement(like for loop ) can start with a short statement to execute before the CONDITION. | |
//vars declared byTHE STATEMENT are only in scope until the end of the if. | |
//those vars are however AVAILABLE in any else clauses of the if clause | |
func pow(x, n, lim float64) float64 { | |
if v := math.Pow(x, n); v < lim { | |
return v | |
} else { | |
//can use v here | |
fmt.Printf("%g >= %g\n", v, lim) | |
} | |
// can't use v here, though | |
return lim | |
} | |
//3. switch statement | |
import ( | |
"fmt" | |
"runtime" | |
) | |
func main() { | |
fmt.Print("Go runs on ") | |
switch os := runtime.GOOS; os { | |
case "darwin": | |
fmt.Println("OS X.") //if os=='darwin' then this gets executed | |
case "linux": | |
fmt.Println("Linux.") | |
default: | |
// freebsd, openbsd, | |
// plan9, windows... | |
fmt.Printf("%s.", os) | |
} | |
} | |
//semicolons are to separate multiple statements on a line, should you write code that way. | |
//u could write line 83 as: | |
os := runtime.GOOS | |
switch os { | |
case "darwin": //etc | |
//switch without condition is like switch true | |
import ( | |
"fmt" | |
"time" | |
) | |
func main() { | |
t := time.Now() | |
switch { | |
case t.Hour() < 12: | |
fmt.Println("Good morning!") | |
case t.Hour() < 24: | |
fmt.Println("Good afternoon.") | |
default: | |
fmt.Println("Good evening.") | |
} | |
} | |
//it's clean way to write long if-then-else chains. | |
//4. defer | |
func main() { | |
defer fmt.Println("world") | |
fmt.Println("hello") | |
} | |
//output "hello" \n "world" | |
//defer: defers the execution of a function until the surrounding function returns. | |
//it's usually good eg; in file operations;u can defer file closing to ensure the file is always closed after u r done operating with it. | |
//Deferred function calls are pushed onto a stack. When a function returns, | |
//its deferred calls are executed in last-in-first-out order. | |
func main() { | |
fmt.Println("counting") | |
for i := 0; i < 5; i++ { | |
defer fmt.Println(i) | |
} | |
fmt.Println("done") | |
} | |
//output: "counting" "done", 4, 3, 2 .... //ie LIFO | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//III. MORE TYPES; structs, slices, maps | |
//1. Pointers | |
func main() { | |
i := 42 | |
p := &i // point to i | |
fmt.Println(*p) // 42 | |
*p = 24 // set i through the pointer | |
fmt.Println(i) // 24 | |
fmt.Println(p) // 0x1043617c | |
fmt.Println(i==*p) //true | |
y := *p/4 //u can do math with pointer as if its the real value | |
fmt.Println(y) | |
} | |
//pointers hold mem address of a value | |
//if p is a pointer of i, then value i == *p | |
package main | |
import "fmt" | |
func main() { | |
a := 1 | |
b := 7 | |
p := &a //pointer p==address of a | |
fmt.Println(p, *p) //0x1043617c 1 | |
fmt.Println(a, &a) //1 0x1043617c | |
//ie p==&a; *p==a | |
*p = 3 //change value at addr &a to 3 | |
p = &b //p == addr of b | |
//thus value of p==*p ==b ==7 | |
//but value of a==3 | |
fmt.Println("") | |
fmt.Println(p, *p) //0x10436180 7 | |
fmt.Println(&a, a) //0x1043617c 3 | |
} | |
//2. Structs | |
type Vertex struct { | |
X int | |
Y int | |
} | |
func main() { | |
c := Vertex{4,98} | |
fmt.Println(Vertex{1, 2}) | |
fmt.Println(c) | |
fmt.Println(c.Y) | |
p := &c | |
p.X = 101 | |
fmt.Println(c) | |
} | |
//struct is a collection of fields | |
//u access fields using dot, c.Y | |
//pointers work as expected | |
var ( | |
v1 = Vertex{1, 2} // has type Vertex | |
v2 = Vertex{X: 1} // Y:0 is implicit | |
v3 = Vertex{} // X:0 and Y:0 | |
p = &Vertex{1, 2} // has type *Vertex | |
) | |
func main() { | |
fmt.Println(v1, p, v2, v3) | |
} | |
//output: {1 2} &{1 2} {1 0} {0 0} | |
//The special prefix & returns a pointer to the struct value. | |
//3. Arrays | |
var a [10]int | |
//var a is an array of length 10 | |
func main() { | |
var a [2]string | |
a[0] = "Hello" | |
a[1] = "World" | |
fmt.Println(a[0], a[1]) // Hello World | |
fmt.Println(a) //[Hello World] | |
} | |
//arrays cannot be resized; length is part of their type | |
//The type [n]T is an array of n values of type T. | |
//arrays are inexflisible, so u dont see em often; instead u see slices | |
//4. Slices | |
func main() { | |
s := []int{2, 3, 5, 7, 11, 13} | |
fmt.Println("s ==", s) | |
for i := 0; i < len(s); i++ { | |
fmt.Println(i, s[i]) | |
} | |
s[:3] //missing low index implies 0 | |
s[2:] // missing high index implies len(s) | |
s[1:3] | |
s[1:1] //is empty | |
s[2:2+1] //has one element | |
} | |
//A slice points to an array of values and also includes a length. | |
//[]T is a slice with elements of type T. | |
//the slice type is an abstraction built on top of the array type | |
//slices can also be sliced further | |
//s[x:y] includes everything from x to y(excluding y) | |
//Making slices: | |
//they r made in either of two ways: | |
//(a) slicing an array (as seen above) | |
//(b) using make built-in fxn: func make([]T, len, cap) []T | |
b := make([]int, 0, 5) //len=0 & cap=5==optional param | |
len(b); cap(b) //fxns to find this values | |
//zero value of a slice is nil & len==cap==0 | |
var z []int | |
fmt.Println(z, len(z), cap(z)) | |
//Adding to slices: | |
//u can add using the builtin append fxn: http://golang.org/pkg/builtin/#append | |
//func append(slice []Type, elems ...Type) []Type | |
//it's necessary to store the result of append, often in the variable holding the slice itself | |
a := make([]int, 0) //equivalent to: var a []int //len param is manadatory, u cant do: make([]int) | |
a = append(a, 2, 3, 4) | |
fmt.Println(a) //output: [2 3 4] | |
a[2] = 45 //assign to that key | |
//passing in a slice to fxn | |
func showSlice(s string, myvar []int) { | |
fmt.Printf("%s len=%d cap=%d %v\n", s, len(x), cap(x), x) | |
} | |
//NB: myvar []int | |
showSlice("a", a) | |
//Iterating slices: | |
var pow = []int{1, 2, 4, 8} | |
func main() { | |
for i, v := range pow { | |
fmt.Println(i, v) | |
} | |
} | |
//output: key, value | |
//If u only want the value; assign key to = _ | |
for _, value := range pow { | |
fmt.Println(value) | |
} | |
//if u want only the key, use only one var | |
for key := range pow { | |
fmt.Println(key) | |
} | |
//NB: The Capacity of a slice is the max value the Length can reach. | |
//Trying to grow the slice beyond its capacity will step beyond the limits of the array and will trigger a panic. | |
//5. Maps | |
//They map keys to values. U must use make keyword to create 'em | |
//nil map is empty and cant be assigned into | |
type Vertex struct { | |
Lat, Long float64 | |
} | |
func main() { | |
m := make(map[int]Vertex) | |
//m MAPS ints to things of type VERTEX | |
m[4] = Vertex{40.68433, -74.39967,} | |
fmt.Println(m[4]) | |
//c := make(map[string]Vertex) | |
//c["coordinate"] = Vertex{2.33, -3.89,} | |
//fmt.Println(m["coordinate"]) | |
} | |
//map literals r like struct literals except that the keys are required | |
type Komu struct { | |
Lat, Long float64 | |
} | |
var m = make(map[string]Komu) //m maps strings to things of type Komu | |
func main() { | |
m["Bell Labs"] = Komu{40.68433, -74.39967,} | |
m["Google"] = Komu{37.42202, -122.08408,} | |
fmt.Println(m) | |
} | |
//the above declaration could also have bn done at once: | |
func main() { | |
m := map[string]Komu{ | |
"Bell Labs": Komu{40.68433, -74.39967,}, | |
"Google": Komu{37.42202, -122.08408,}, | |
} | |
fmt.Println(m) | |
} | |
////////////////////////////////////////////////////// | |
//if top level type is just a type name, u can omit it from elements of th literal | |
type Vertex struct {Lat, Long float64} | |
var m = map[string]Vertex{ | |
"Bell Labs": {40.68433, -74.39967}, //Vertex omitted here | |
"Google": {37.42202, -122.08408}, | |
} | |
func main() { fmt.Println(m) } | |
//Mutating maps | |
//If u have a map, m | |
m[key] = elem //insert or update an element | |
elem = m[key] //Retrieve an element | |
delete(m, key) //Delete an element | |
elem, t = m[key] //test a key is present with a two-value assignment: | |
//if key exists elem==value at that key & t==true | |
//else; elem==zero value of the maps element type & t==false | |
m := make(map[string]int) | |
//m MAPS strings to ints | |
m["Answer"] = 42 //insert | |
m["Answer"] = 48 //update | |
delete(m, "Answer") | |
v, k := m["Answer"] | |
fmt.Println(v, k) //v==zero value of ints==0 | |
//k==false | |
//6. More About Functions | |
//Functions are values too. | |
//so u can also assign them | |
func main() { | |
hypot := func(x, y float64) float64 { | |
return math.Sqrt(x*x + y*y) | |
} | |
fmt.Println(hypot(3, 4)) | |
} | |
//note that in this case the func has no name | |
func cool(x, y float64) float64 { | |
return math.Sqrt(x*x + y*y) | |
} //u cant declare a fxn with a name (named fxn) inside main???** | |
func main() { | |
hypot := cool(3, 4) | |
fmt.Println(hypot) | |
} | |
//Function closures | |
//A closure is a FunctionValue(see above) that references variables from outside its body. | |
//The function may access and assign to the referenced variables; in this sense the function is "bound" to the variables. | |
func adder() func(int) int { //returns a closure | |
sum := 0 | |
return func(x int) int { //the closure, its bound to the sum variable | |
sum += x | |
return sum | |
} | |
} | |
func main() { | |
pos, neg := adder(), adder() | |
for i := 0; i < 10; i++ { | |
fmt.Println( | |
pos(i), | |
neg(-2*i), | |
) | |
} | |
} | |
//ie: You can make the anonymous function close over a value, making it a closure. | |
valueToCloseOver := "My name is HAL." | |
anon := func(s name) string { | |
return "Hiya, " + name + ". " + valueToCloseOver | |
} | |
anotherFunction(anon) | |
//Regardless of where the anonymous function is passed to, it will always have access to var ‘valueToCloseOver’. | |
//This example is trivial. But passing logic and state around is a powerful coding mechanism. | |
func anotherFunction(f func(string) string) { | |
result := f("David") | |
fmt.Println(result) // Prints "Hiya, David" | |
} | |
//http://blog.denevell.org/golang-closures-anonymous-functions.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//IV. METHODS AND INTERFACES | |
//1. Methods | |
//Go doesnt have classes; but u can defn methods on struct types. | |
func cool() int { | |
//code | |
//a normal/regular fxn called cool that takes no args and returns an int | |
} | |
type my_type struct { } | |
func (m my_type) cool_two() int { | |
//code | |
//a method called cool_two that takes no args, returns an int | |
//but is associated with a type we name my_type | |
} | |
type Vertex struct { X, Y float64 } | |
func (v *Vertex) Abs() float64 { | |
return math.Sqrt(v.X*v.X + v.Y*v.Y) //a method called Abs associated with *Vertex | |
} | |
func main() { | |
v := &Vertex{3, 4} | |
fmt.Println(v.Abs()) | |
} | |
//You can declare a method on ANY TYPE that is DECLARED in YOUR package, not just struct types. | |
//u cant declare methods on a type from a diff package or even a builtin package | |
type MyFloat float64 | |
func (f MyFloat) aha_fxn() float64 { | |
if f < 0 { | |
return float64(-f) | |
} | |
return float64(f) | |
} | |
func main() { | |
f := MyFloat(-math.Sqrt2) | |
fmt.Println(f.aha_fxn()) | |
} | |
//Methods can either be associated with a name type values or pointer to name type | |
//above Abs method is assoc with a pointer to a named type(*Vertex) while aha_fxn is assoc with name type(MyFloat) | |
//reasons to assoc with pointers are: | |
//(a) if the value type is a large struct, using pointers is more efficient | |
//(b) if u want the method to make modifications to the value that its receiver points to. | |
// NB: VERY IMPORTANT | |
// 1. If the receiver is a map, func or chan, use value receiver. | |
// 2. When in doubt, use a pointer receiver. | |
// https://github.com/golang/go/wiki/CodeReviewComments#receiver-type | |
// In general, all methods on a given type should be consistent and have either | |
// value or pointer receivers, but not a mixture of both. | |
type Vertex struct { X, Y float64 } | |
func (v *Vertex) Scale(f float64) { | |
v.X = v.X * f | |
v.Y = v.Y * f | |
} | |
func (v *Vertex) Abs() float64 { | |
return math.Sqrt(v.X*v.X + v.Y*v.Y) | |
//you can treat the receiver(Vertex struct) as if it was an argument being passed to the method(Abs) | |
} | |
func main() { | |
v := &Vertex{3, 4} | |
v.Scale(5) | |
fmt.Println(v, v.Abs()) | |
} | |
//the scale() method makes modifications to v, thus its better to use pointer | |
//for Abs() it doesnt modify v, so it doesnt matter if you assoc with pointer or name type | |
//2. Interfaces. | |
//It's a type that is defined by a set of methods. | |
//A value of interface type can hold any value that implements those methods. | |
type Abser interface { | |
Abs() float64 | |
} | |
type MyFloat float64 | |
func (f MyFloat) Abs() float64 { | |
if f < 0 { | |
return float64(-f) | |
} | |
return float64(f) | |
} | |
type Vertex struct { | |
X, Y float64 | |
} | |
func (v *Vertex) Abs() float64 { | |
return math.Sqrt(v.X*v.X + v.Y*v.Y) | |
} | |
func main() { | |
var a Abser | |
f := MyFloat(-math.Sqrt2) | |
v := Vertex{3, 4} | |
a = f // a MyFloat implements Abser | |
a = &v // a *Vertex implements Abser | |
// In the following line, v is a Vertex (not *Vertex) | |
// and does NOT implement Abser. | |
a = v //THIS WILL FAIL | |
fmt.Println(a.Abs()) | |
} | |
//Interfaces are implemented explicitly | |
import ("os") | |
type Reader interface { | |
Read(b []byte) (n int, err error) | |
} | |
func main() { | |
var w Writer | |
// os.Stdout implements Writer | |
w = os.Stdout | |
fmt.Fprintf(w, "hello, writer\n") | |
} | |
//A type implements an interface by implementing the methods. | |
//One of the most common Interfaces is Stringer defined by the fmt package. | |
/* | |
type Stringer interface { | |
String() string | |
} | |
Stringer is a type that can describe itself as a string. | |
*/ | |
type Person struct { | |
Name string | |
Age int | |
} | |
func (p Person) String() string { | |
return fmt.Sprintf("%v (%v years)", p.Name, p.Age) | |
} | |
func main() { | |
a := Person{"Arthur Dent", 42} | |
fmt.Println(a) | |
} | |
//By convention, one-method interfaces are named by the method name plus an -er suffix | |
//3. Errors | |
//error is a builtin type similar to fmt.Stringer | |
type error interface { | |
Error() string | |
} | |
//fxns often return errors, and calling code shud check for that by comapring with nil | |
i, err := strconv.Atoi("42") | |
if err != nil { | |
fmt.Printf("couldn't convert number: %v\n", err) | |
} | |
fmt.Println("Converted integer:", i) | |
//errors exercise | |
//create a custom error such that Sqrt should return a non-nil error value when given a negative number, | |
//such that ErrNegativeSqrt(-2).Error() returns "cannot Sqrt negative number: -2" | |
import ( | |
"fmt" | |
) | |
//create a new type | |
//and make it an error by giving it a | |
//func (e ErrNegativeSqrt) Error() string | |
type ErrNegativeSqrt float64 | |
func (e ErrNegativeSqrt) Error() string { | |
return fmt.Sprintf("cannot Sqrt negativ number: %v", float64(e)) //float64(e) needed to avoid infinite loop | |
} | |
func Sqrt(f float64) (float64, error) { | |
if f < 0 { | |
return 0, ErrNegativeSqrt(f) | |
} | |
return 0, nil | |
} | |
func main() { | |
fmt.Println(Sqrt(2)) | |
fmt.Println(Sqrt(-2)) | |
} | |
//4. Readers | |
package main | |
import ( | |
"fmt" | |
"io" | |
"strings" | |
) | |
func main() { | |
r := strings.NewReader("Hello, Reader!") | |
b := make([]byte, 8) //u can replace 8 with len("Hello, Reader!") | |
for { | |
n, err := r.Read(b) | |
fmt.Printf("n = %v err = %v b = %v\n", n, err, b) | |
fmt.Printf("b[:n] = %q\n", b[:n]) | |
if err == io.EOF { | |
break | |
} | |
} | |
fmt.Println(string(b)) //prints eader! R | |
//if b := make([]byte, len("Hello, Reader!")) then; | |
//fmt.Println(string(b)) == "Hello, Reader!" | |
} | |
//the io.Reader interface has a Read method: | |
//func (T) Read(b []byte) (n int, err error) | |
//Read populates the given byte slice with data and returns the number of bytes populated and an error value. | |
//It returns an io.EOF error when the stream ends. | |
//5. Web servers | |
//u can server HTTP requests using any value that implements http.Handler interface | |
type Handler interface { | |
ServeHTTP(w ResponseWriter, r *Request) | |
} | |
// remember that an inteface is satisified if all its methods** are implemented. | |
//so all u have to do is implement ServeHTTP() fxn and http.Handler will be satisified | |
import ( | |
"fmt" | |
"log" | |
"net/http" | |
) | |
type Hello struct{} | |
func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
fmt.Fprint(w, "Hello!") //implement ServeHTTP() fxn thus satisfyin the http.Handler | |
} | |
func main() { | |
var h Hello | |
err := http.ListenAndServe("localhost:4000", h) | |
if err != nil { | |
log.Fatal(err) | |
} | |
} | |
//In this example, the type Hello implements http.Handler. | |
//webservers exercise: https://tour.golang.org/methods/14 | |
//for this exercise we r 2Implement the following types and define ServeHTTP methods on them. | |
//and also register them to handle specific paths in our web server. | |
type Mystring string | |
type Mystruct struct { | |
Greeting string | |
Punct string | |
Who string | |
} | |
//answer: | |
package main | |
import ( | |
"fmt" | |
"net/http" | |
) | |
type Mystring string | |
type Mystruct struct { | |
Greeting string | |
Punct string | |
Who string | |
} | |
func (s Mystring) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
fmt.Fprint(w, s) | |
} | |
func (s Mystruct) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
fmt.Fprint(w, s.Greeting, s.Punct, s.Who) | |
} | |
func main() { | |
var cool_str Mystring | |
cool_str = "I'm a frayed knot." | |
var cool_struct Mystruct | |
cool_struct = Mystruct{"Hello", ":", "Gophers!"} | |
// your http.Handle calls here | |
http.Handle("/string", cool_str) | |
http.Handle("/struct", &cool_struct) | |
http.ListenAndServe("localhost:4000", nil) | |
} | |
//from: http://golang.org/pkg/net/http/#Handle | |
func Handle(pattern string, handler Handler) //takes a string(uri) and a Handler | |
//remember that handler is anything that implements the Handler interface ie implements | |
// ServeHTTP() fxn | |
//also from: http://golang.org/pkg/net/http/#ListenAndServe | |
func ListenAndServe(addr string, handler Handler) error //takes an address as a string and a Handler an returns an error(if any) | |
//typically for this, Handler is usually nil | |
//its common to do: | |
err := http.ListenAndServe(":12345", nil) | |
if err != nil { | |
log.Fatal("ListenAndServe: ", err) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//V. CONCURRENCY | |
//1. Goroutines | |
go f(x, y, z) //starts a new goroutine running f(x, y, z) | |
//The evaluation of f, x, y, and z happens in the current goroutine(the one handling main() fxn) | |
//and the execution of f happens in the new goroutine. | |
import ( | |
"fmt" | |
"time" | |
) | |
func say(s string) { | |
for i := 0; i < 5; i++ { | |
time.Sleep(1 * time.Millisecond) | |
fmt.Println(s) | |
} | |
} | |
func main() { | |
go say("world") | |
say("hello") //if u don't add this synchronous call here, main() fxn will execute b4 the | |
//go say goroutine has finished and thus exit. u could replace it with something like; time.Sleep(40 * time.Millisecond) | |
} | |
//output: hello world hello world etc | |
//goroutines run in the same address space, so access to shared memory must be synchronized. golang comes | |
//with a sync pkg that u can use to manage that. But u dont have to bcoz u probably ought 2b using channels | |
//another way to write the same program with synchronisation; | |
import ( | |
"fmt" | |
"sync" | |
) | |
var wg sync.WaitGroup | |
func say(s string) { | |
for i := 0; i < 5; i++ { | |
fmt.Println(s) | |
} | |
wg.Done() //decrement no of goroutines to wait for | |
} | |
func main() { | |
wg.Add(1) //add no of gorutines to wait for | |
go say("world") | |
wg.Wait() //wait for the one goroutine 2finish execution | |
} | |
//SERIOUSLY THIS SYNCHRONIZATION AND RACE CONDITION THING CAN BE HARD | |
//USE CHANNELS. | |
//2. Channels | |
//Channels are a typed conduit through which you can send and receive values. | |
ch <- v // Send v to channel ch. | |
v := <-ch // Receive from channel ch & assign value to v. | |
ch := make(chan int) //Like maps and slices, channels must be created before use: | |
//By default, sends and receives block until the other side is ready. | |
//THIS ALLOWS goroutines to SYNCHRONIZE WITHOUT explicit LOCKS or condition variables. | |
func sum(a []int, c chan int) { | |
counter := 0 | |
for _, v := range a { | |
counter = counter + v | |
} | |
c <- counter // send counter to c | |
} | |
func main() { | |
a := []int{7, 2, 8} | |
c := make(chan int) //make a channel of ints(it can only send/receive ints) | |
go sum(a, c) | |
x := <-c //receive from c and assign to x | |
fmt.Println(x) | |
} | |
//output: 17 | |
//3. Buffered channels | |
ch := make(chan int, 100) //buffer length==100 | |
//Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty. | |
func main() { | |
c := make(chan int, 2) | |
c <- 1 | |
c <- 45 | |
//c <- 45 //this will thro err, coz c can only receive a max of two | |
fmt.Println(<-c) | |
fmt.Println(<-c) | |
//fmt.Println(<-c) //if u enable this, it'll thro error because c is a ch with buffer of 2 | |
//and it has already sent all it had | |
} | |
//4. Channels, range & close | |
//A SENDER(and not receiver) can close a channel to indicate that no more values will be sent | |
//Receivers can test whether a channel has been closed | |
v, ok := <-ch | |
//ok is false if there are no more values to receive and the channel is closed. | |
//The loop for i := range c receives values from the channel repeatedly until it is closed. | |
func fibonacci(n int, c chan int) { | |
x, y := 0, 1 | |
for i := 0; i < n; i++ { | |
c <- x | |
x, y = y, x+y | |
} | |
close(c) | |
} | |
func main() { | |
c := make(chan int, 10) | |
go fibonacci(cap(c), c) | |
for i := range c { | |
fmt.Println(i) | |
} | |
} | |
//NB: chanels aren't like files(YOU DONT have to close them) | |
//Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop | |
//5. Channels & select statement | |
//The select statement lets a goroutine wait on multiple communication operations. | |
//A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready. | |
func fibonacci(c, quit chan int) { //two channels of ints | |
x, y := 0, 1 | |
for { ////for loop bila condition, loops forever | |
select { | |
case c <- x: //send to c; start by sending x==0 | |
x, y = y, x+y | |
case <-quit: //if a receive into channel quit is got, end fxn | |
fmt.Println("quit") | |
return | |
//default: //optional | |
//The default case in a select is run if no other case is ready. | |
} | |
} | |
} | |
var c = make(chan int) | |
var quit = make(chan int) | |
func cool() { | |
for i := 0; i < 4; i++ { | |
z := <-c //receive from channel c and assign to z | |
fmt.Println(z) | |
} | |
quit <- 0 //send 0 to channel quit | |
} | |
func main() { | |
go cool() | |
fibonacci(c, quit) | |
} | |
//output: 0, 1, 1, 2, quit | |
//if in fnx cool() we interchanged and sent to quit channel befor receiving from c channel | |
//the output would be: "quit" & nothing else | |
//this is coz fibonnanci is listening(waiting) for both conditions and whichever comes first is executed | |
//if case quit is got then return and end | |
//6. cancelling channels using context. | |
// gen is a broken generator that will leak a goroutine. | |
func gen() <-chan int { | |
ch := make(chan int) | |
go func() { | |
var n int | |
for { | |
ch <- n | |
n++ | |
} | |
}() | |
return ch | |
} //gen starts a goroutine with an infinite loop, but the caller(below) consumes the values until n is equal to 5. | |
for n := range gen() { | |
fmt.Println(n) | |
if n == 5 { | |
break | |
} | |
} | |
// Once the caller is done with the generator (when it breaks the loop), the goroutine will run forever executing the infinite loop. | |
// THE FIX is | |
// gen is a generator that can be cancellable by cancelling the ctx. | |
func gen(ctx context.Context) <-chan int { | |
ch := make(chan int) | |
go func() { | |
var n int | |
for { | |
select { | |
case <-ctx.Done(): | |
return // avoid leaking of this goroutine when ctx is done. | |
case ch <- n: | |
n++ | |
} | |
} | |
}() | |
return ch | |
} | |
ctx, cancel := context.WithCancel(context.Background()) | |
defer cancel() // make sure all paths cancel the context to avoid context leak | |
for n := range gen(ctx) { | |
fmt.Println(n) | |
if n == 5 { | |
cancel() | |
break | |
} | |
} | |
// see: | |
// 1. https://rakyll.org/leakingctx/ | |
// 2. https://github.com/uber-go/goleak | |
// 3. https://gist.github.com/komuw/0252dd7cd660edce4b483829dddfebc3 | |
//7. Pipelines | |
https://gist.github.com/claudiofahey/3afcf4f4fb3d8d3b35cadb100d4fb9b7 | |
//8. what can you transport in a channel? | |
// There is really nothing that is not safe to transport in a channel. | |
// But there are some considerations: | |
// For example if you transport an http.Response then you will be making a copy on the way in and out of the channel. | |
// So that may not be safe, but it has nothing to do with channels, just that some objects are not safe to copy. | |
// If your value is not safe to copy, then use pointers in your channel. | |
// Another thing to consider is concurrent access. | |
// If you send a pointer to an object over a channel, but then continue to access is from the sending goroutine, you may be creating a race. | |
// Even if you pass an object by value, so it gets copied, you still need to make sure it | |
// does not contain any pointers, maps, slices, ect, before accessing the original and the copy concurrently, since go copy is shallow. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1. //What's the difference between concurrency and parallelism | |
- Concurrency is like having a juggler juggle many balls. Regardless of how it seems, | |
the juggler is only catching/throwing one ball at a time. | |
- Parallelism is having multiple jugglers juggle balls simultaneously. | |
- concurrency: u can apply for a passport and as u wait in line, u r wrting code, and as ua code compiles, u make a biz call etc | |
- parallelism: two of u are writing same code together | |
//a program running concurrently: | |
package main | |
import ( | |
"fmt" | |
"sync" | |
) | |
var wg sync.WaitGroup | |
func cool1() { | |
for char := 'a'; char < 'a'+26; char++ { | |
fmt.Printf("%c ", char) | |
} | |
wg.Done() //announce u r done | |
} | |
func cool2() { | |
for number := 1; number < 27; number++ { | |
fmt.Printf("%d ", number) | |
} | |
wg.Done() | |
} | |
func main() { | |
wg.Add(2) | |
fmt.Println("Starting Go Routines") | |
go cool1() | |
go cool2() | |
fmt.Println("\nWaiting To Finish") | |
wg.Wait() //wait for goroutine 2finish execution | |
fmt.Println("\nTerminating Program") | |
} | |
//output: | |
Starting Go Routines | |
Waiting To Finish | |
a b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Terminating Program | |
//more at: http://www.goinggo.net/2014/01/concurrency-goroutines-and-gomaxprocs.html | |
//if we made a small change | |
import "time" | |
func cool1() { | |
time.Sleep(1 * time.Microsecond) | |
for char := 'a'; char < 'a'+26; char++ { | |
fmt.Printf("%c ", char) | |
} | |
wg.Done() | |
} | |
//output is: | |
Starting Go Routines | |
Waiting To Finish | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 a b c d e f g h i j k l m n o p q r s t u v w x y z | |
Terminating Program | |
//we add a small sleep in first goroutine & this is enough to cause the scheduler to swap the two goroutines: | |
//about the WaitGroup: http://golang.org/pkg/sync/#WaitGroup | |
type WaitGroup struct { | |
// contains filtered or unexported fields | |
} | |
//A WaitGroup waits for a collection of goroutines to finish. | |
//The main goroutine(main fxn) calls Add to set the number of goroutines to wait for. | |
//Then each of the goroutines runs and calls Done when finished. [wg.Done() will decrements number of goroutine 2wait for] | |
//At the same time, Wait can be used to block until all goroutines have finished. | |
//Program running in parrallel | |
package main | |
import ( | |
"fmt" | |
"runtime" | |
"sync" | |
"time" | |
) | |
var wg sync.WaitGroup | |
func alphabets() { | |
for char := 'a'; char < 'a'+26; char++ { | |
fmt.Printf("%c ", char) | |
time.Sleep(time.Second * 1) | |
} | |
wg.Done() //decrement number of goroutines to wait for | |
} | |
func numbers() { | |
for number := 1; number < 27; number++ { | |
fmt.Printf("%d ", number) | |
time.Sleep(time.Second * 5) | |
} | |
wg.Done() | |
} | |
func main() { | |
runtime.GOMAXPROCS(2) //seems like this doen't affect much*** | |
wg.Add(2) //wait for two goroutines | |
fmt.Println("Starting Go Routines") | |
go alphabets() | |
go numbers() | |
fmt.Println("\nWaiting To Finish") | |
wg.Wait() //wait for the two goroutines to finish executing | |
fmt.Println("\nTerminating Program") | |
} | |
//output: | |
Starting Go Routines | |
Waiting To Finish | |
a 1 b c d e 2 f g h i j 3 k l m n o 4 p q r s t 5 u v w x y 6 z 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Terminating Program | |
//look at concurrent_prog.go and parallel_prog.go in this same folder | |
2. //GOLANG TYPE SWITCH | |
//An interface{} type is a type that could be any value. | |
var x interface{} = "string" | |
var y interface{} = 123 | |
//If you’re not sure about what the interface{} type could be, you can use a type switch: | |
switch v := x.(type) { | |
case string: | |
fmt.Println(v) | |
case int32, int64: | |
fmt.Println(v) | |
case *int: | |
fmt.Printf("pointer to integer %d\n", *t) // t has type *int | |
case SomeCustomType: | |
fmt.Println(v) | |
default: | |
fmt.Printf("unexpected type %T", t) // %T prints whatever type t has | |
} | |
3. //Allocation using make and new | |
//new(T) allocates zeroed storage for a new item of type T and returns a pointer to a newly allocated zero value of type T. | |
f := new(File) //type *File | |
//make(T, args) creates MAPS, SLICES, CHANNELS only, and it returns an initialized (not zeroed) value of type T (not *T). | |
4. //2D slices | |
a := [][]int{} //aslice of slices(and the slices are of ints) | |
row1 := []int{1, 2, 3} | |
row2 := []int{4, 5, 6} | |
a = append(a, row1) | |
a = append(a, row2) | |
fmt.Println(a) //[ [1 2 3] [4 5 6] ] | |
5. //Control default format of a custom type | |
//If you want to control the default format for a custom type, | |
//all thats required is to define a method with the signature String() string on the type. | |
//u however need 2convert the argument to the basic string type to prevent 'process took too long' error(because of infinite recursion) | |
type MyString string | |
func (m MyString) String() string { | |
return fmt.Sprintf("Bob=%s", string(m)) // OK: note conversion. | |
} | |
func main() { | |
var x MyString = "hello cool" | |
fmt.Println(x) | |
} | |
//this prints: Bob=hello cool | |
//instead of: hello cool | |
6. //in golang: | |
a << b == a* (2**b) //so; | |
4<<3 == 4*(2**3) == 4*8 == 32 | |
7. //The init function | |
//each source file can define its own niladic init function to set up whatever state is required | |
func init() { | |
if user == "" { | |
log.Fatal("$USER not set") | |
} | |
if home == "" { | |
home = "/home/" + user | |
} | |
} | |
//- imported packages are initialized. | |
//- variable declarations in the package have evaluated their initializers | |
//- init is then called | |
//a file can have more than one init file | |
// func init() is discouraged. Have a look at, http://peter.bourgon.org/blog/2017/06/09/theory-of-modern-go.html | |
// You shouldn't have: | |
//- Package level variables | |
//- func init | |
8. //pointers vs values | |
func Append(slice, data []byte) []byte { | |
l := len(slice) | |
if l + len(data) > cap(slice) { // reallocate | |
// Allocate double what's needed, for future growth. | |
newSlice := make([]byte, (l+len(data))*2) | |
// The copy function is predeclared and works for any slice type. | |
copy(newSlice, slice) | |
slice = newSlice | |
} | |
slice = slice[0:l+len(data)] | |
for i, c := range data { | |
slice[l+i] = c | |
} | |
return slice | |
} | |
//this is a small fxn that appends to slices. | |
//There's an inbuilt Append fxn that u should use; this is here for experment | |
//the problem is we are passing in slice and data as values(not pointers) | |
//so to modify slice, we have to return it for changes to take effect | |
//if we used pointers though, changes will take effect automatically and we dont have to return | |
type ByteSlice []byte | |
func (p *ByteSlice) Append(data []byte) { | |
slice := *p | |
// Body as above, without the return. | |
*p = slice | |
} | |
//we can do better, if we modfy it to loook like a standard Write method | |
func (p *ByteSlice) Write(data []byte) (n int, err error) { | |
slice := *p | |
// Again as above. | |
*p = slice | |
return len(data), nil | |
} | |
//then the type *ByteSlice satisfies the standard interface io.Writer, which is handy. For instance, we can print into one. | |
var b ByteSlice | |
fmt.Fprintf(&b, "This hour has %d days\n", 7) | |
//RULE: value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers. | |
9. //Interfaces | |
//we've seen a type implementing an interface(by implementing its methods) | |
//a type can also implement multiple interfaces (multiple inheritance??) | |
//we are going to implement a type that implements the sort.Interface and also a custom formatter | |
import( | |
"fmt" | |
"sort" | |
) | |
type Sequence []int | |
// Methods required by sort.Interface. | |
func (s Sequence) Len() int { return len(s) } | |
func (s Sequence) Less(i, j int) bool { | |
return s[i] < s[j] | |
} | |
func (s Sequence) Swap(i, j int) { | |
s[i], s[j] = s[j], s[i] | |
} | |
// Method for printing - sorts the elements before printing. | |
func (s Sequence) String() string { | |
sort.Sort(s) | |
str := "[" | |
for i, elem := range s { | |
if i > 0 { | |
str += " " | |
} | |
str += fmt.Sprint(elem) | |
} | |
return str + "]" | |
} | |
func main() { | |
x := make(Sequence, 0) | |
x= append(x, 33,14,5,65, 3,1) | |
fmt.Println(x) | |
} | |
//outputs: [1 3 5 14 33 65] | |
10. //Conversions | |
//we've implemented a string() method above for Sequence | |
//but we r just re-implementing the functionality of the inbuilt String method for slices | |
//so we can convert our sequence to slice of ints and take advantage of its String() without re-implementing it | |
func (s Sequence) String() string { | |
sort.Sort(s) | |
return fmt.Sprint([]int(s)) //conversion | |
} | |
func main() { | |
x := make(Sequence, 0) | |
x = append(x, 33, 14, 5, 65, 3, 1, 3) | |
fmt.Println(x) | |
} | |
//infact slices of ints already have an inbuilt sort fxn so all we've to do is convert Sequence to []int and call | |
//its builtin sort fxn | |
type Sequence []int | |
// Method for printing - sorts the elements before printing | |
func (s Sequence) String() string { | |
sort.IntSlice(s).Sort() | |
return fmt.Sprint([]int(s)) | |
} | |
11 //Interfaces and Methods | |
//Since almost anything can have methods attached, almost anything can satisfy an interface | |
// Simpler counter server. | |
type Counter int | |
func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) { //ResponseWriter is itself an interface | |
*ctr++ | |
fmt.Fprintf(w, "counter = %d\n", *ctr) | |
} //handler to count the number of times the page is visited. | |
//receiver needs to be a pointer so the increment is visible to the caller | |
12. //Blank(_) identifier | |
//1. u can use it in multiple assignmenets if u dont need to use one of the vars | |
_, err := os.Stat(path); | |
//2. to silence compile errors for unused vars | |
var _ = []int{2,3,7} //no errors even if u dont use this var | |
//3. unused imports | |
import _ "net/http/pprof" //when u just want to import a package | |
//for its side effects(eg its init fxn) | |
13. //Embedding | |
type Reader interface { | |
Read(p []byte) (n int, err error) | |
} | |
type Writer interface { | |
Write(p []byte) (n int, err error) | |
} | |
// ReadWriter is the interface that combines the Reader and Writer interfaces. | |
type ReadWriter interface { | |
Reader | |
Writer | |
} | |
14.//logging | |
package main | |
import ( | |
_ "io" | |
"log" | |
"os" | |
) | |
func main() { | |
Trace := log.New(os.Stdout, "TRACE: ", log.Ldate|log.Ltime|log.Lshortfile) | |
Trace.Println("I have something standard to say") | |
//func New(out io.Writer, prefix string, flag int) *Logger | |
} | |
//or simpler | |
func main() { | |
log.Println("hello") | |
} | |
//back to embedding in no 13; | |
type Job struct { | |
Command string | |
*log.Logger //embedded field alonside a regular field | |
} //embedded field dont have to be named | |
func main() { | |
mylogger := log.New(os.Stdout, "TRACE: ", log.Ldate|log.Ltime|log.Lshortfile) | |
job := Job{"hi", mylogger} | |
job.Println("Yo") | |
} | |
f, err := os.OpenFile("testlogfile", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) | |
defer f.Close() | |
log.SetOutput(f) | |
log.Println("This is a test log entry") | |
//log to file | |
15. //Goroutines | |
//it's a fxn executing concurrently with other goroutines in the same address space | |
//they'r multiplexed onto multiple OS threads so if one should block, others continue to run | |
go list.Sort() //run concurrently; don't wait for it. when call completes goroutine exits, silently | |
func Announce(message string, delay time.Duration) { | |
go func() { | |
time.Sleep(delay) | |
fmt.Println(message) | |
}() // Note the parentheses - must call the function. | |
} //execute goroutine as fxn literal | |
//this goroutiines have no way of signaling completion; thats why we need channels | |
//goroutines are multiplexed onto multiple os threads. | |
//If one goroutine blocks an OS thread, other goroutines in this thread will migrate so that they may continue running. | |
//You do not have to worry about these details. | |
16. //Channels | |
//A channel can allow the launching goroutine to wait for a computation to complete. | |
func main() { | |
c := make(chan int) // Allocate a channel. | |
go func() { // Start the sort in a goroutine; when it completes, signal on the channel. | |
time.Sleep(100000); fmt.Println("iii") //COMPUTATION | |
c <- 1 // Send a signal; value does not matter. | |
}() | |
<-c // Wait for sort to finish; discard sent value. | |
} | |
//Receivers block until there is data to receive. | |
//sender blocks until the receiver has received the value. | |
//ie (sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.) | |
//Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty. | |
17. //Errors | |
//errors have type error, a simple built-in interface. | |
type error interface { | |
Error() string | |
} | |
//a library developer is free to implement this interface and even provide more context | |
//eg os.Open returns an error(nil if none); and this error is internally implemented as; | |
type PathError struct { | |
Op string // "open", "unlink", etc. | |
Path string // The associated file. | |
Err error // Returned by the system call. | |
} | |
func (e *PathError) Error() string { | |
return e.Op + " " + e.Path + ": " + e.Err.Error() | |
} | |
//example output: open /etc/passwx: no such file or directory | |
18. //Panic | |
//sometimes we don't wont to just throw an error, we wont the program to exit at the error. | |
//panic is a fxn that takes a single argument of arbitrary type(often a string) to be printed as the program dies. | |
var user = os.Getenv("USER") | |
func init() { | |
if user == "" { | |
panic("no value for $USER") | |
} | |
} | |
//when panic is called: it stops execution of current fxn & runs any deferred functions along the way | |
19. //Recover | |
//a call to recover stops the unwinding of panic and returns the argument passed to panic. | |
package main | |
import "fmt" | |
func yo(x int){ | |
if x < 0 { | |
panic("ha ha") | |
} | |
} | |
func main(){ | |
defer func(){ | |
if err:=recover();err != nil { | |
fmt.Println(err) | |
} | |
}() //this is an anonymous fxn, SO IT MUST BE CALLED; hence paranthesis | |
yo(-8) | |
} | |
//output: ha ha | |
18. //some OO stuff | |
package main | |
import "fmt" | |
type Person struct { | |
Name string | |
Address Address //some sort of inheritance | |
} | |
type Address struct { | |
BoxNumber string | |
City string | |
} | |
func (p *Person) Talk() { | |
fmt.Println("Hi, my name is", p.Name) | |
} | |
func (p *Person) Location() { | |
fmt.Println("I live at", p.Address.BoxNumber, p.Address.City) | |
} | |
func main() { | |
addr := Address{ | |
BoxNumber: "13", | |
City: "Gotham", | |
} | |
p := Person{ | |
Name: "Steve", | |
Address: addr, | |
} | |
p.Talk() | |
p.Location() | |
} //Hi, my name is Steve //I live at 13 Gotham | |
19. //Embedded Types contd | |
//A struct's fields usually represent the has-a relationship. For example a Circle has a radius. Suppose we had a person struct: | |
type Person struct { | |
Name string | |
} | |
func (p *Person) Talk() { | |
fmt.Println("Hi, my name is", p.Name) | |
} | |
//And we wanted to create a new Citizen struct. We could do this: | |
type Citizen struct { | |
Person Person | |
country string | |
} | |
//This would work, but we would rather say a Citizen is a Person, rather than an Citizen has a Person. Go supports relationships like this by using an embedded type. Also known as anonymous fields, embedded types look like this: | |
type Citizen struct { | |
Person | |
country string | |
} | |
//We use the type (Person) and don't give it a name. When defined this way the Person struct can be accessed using the type name: | |
a := new(Citizen) | |
a.Person.Talk() | |
//But we can also call any Person methods directly on the Android: | |
a := new(Android) | |
a.Talk() | |
20. //Iterating bug | |
package main | |
import "fmt" | |
func main() { | |
strings := []string{"name","Komu"} | |
for _, value := range strings { | |
fmt.Printf("Value: %s and Pointer is: %v\n", value, &value) | |
} | |
} | |
//output: | |
//Value: name and Pointer is: 0x1040a120 | |
//Value: Komu and Pointer is: 0x1040a120 | |
//you can see the pointer, remained the same for each iteration(0x1040a120) | |
//to fix that u shud use a temp variable | |
for _, value := range strings { | |
tmp := value | |
fmt.Printf("Value: %s and Pointer is: %v\n", tmo, &tmp) | |
} | |
//works | |
//u should never use the pointer(address) of an iterated variable | |
21. //Shadowing | |
package main | |
import "fmt" | |
func main() { | |
x := 1 | |
fmt.Println(x) //prints 1 | |
{ | |
fmt.Println(x) //prints 1 | |
x := 2 | |
fmt.Println(x) //prints 2 | |
} | |
fmt.Println(x) //prints 1 (bad if you need 2) | |
} | |
//use go vet to see shadowed vars | |
$ go tool vet -shadow your_file.go | |
22. // Nice notes when reviewing golang code(code review) | |
https://github.com/golang/go/wiki/CodeReviewComments | |
23. // Golang scheduler and net poller | |
```bash | |
Currently, there are operating system threads and there is a set, of size | |
GOMAXPROCS, of "being able to run a goroutine." | |
Threads move in and out of the set based on what the goroutines they are executing do. | |
When thinking about the Go scheduler it helps to think about goroutines and threads separately, | |
although goroutines always execute on threads. | |
Let's say that a goroutine executes some system call that does not complete immediately. | |
When it enters the system call it does not know it is going to block. | |
What happens today (and it has been different in the past) is that there is a system monitor thread (not goroutine) | |
that wakes up every so often (the wakeup frequency depends on various things). | |
The system monitor thread will check on all the executing goroutines. | |
It will observe that one of the goroutines entered a system call some time ago but has not come out, so it is presumably blocked. | |
Because goroutines run on threads, this of course means that the thread is blocked in a system call. | |
The system monitor thread takes away that thread's "right to run a goroutine" and grants it to a different thread (creating a new thread if necessary). | |
That other thread will look at the list of runnable goroutines, and start executing one. | |
When the thread/goroutine blocked in the system call return from the call, the thread will try to reacquire the "right to run a goroutine;" | |
if it fails, the goroutine will be put on the queue of goroutines waiting to run, and the thread will go to sleep until | |
the system monitor thread wakes it up. | |
This is all complicated by the concurrent garbage collector, but probably not in a relevant way. | |
``` - Ian Lance Taylor, https://groups.google.com/forum/#!topic/golang-dev/HJcGESXfJfs | |
Also have a look at; https://rakyll.org/scheduler/ | |
24. // Compile-time checks to ensure type implements desired interfaces. | |
type Unarchiver interface { | |
Unarchive(source, destination string) error | |
} | |
type Tar struct {} | |
func (t *Tar) Unarchive(source, destination string) error {} | |
var ( | |
_ = Unarchiver(new(Tar)) // compile check | |
) | |
25. // Accept interfaces, return structs | |
// The pattern to follow when designing functions/methods is the following; | |
func funcName(a INTERFACETYPE) CONCRETETYPE | |
26. // How to use interfaces | |
// do not do Java-style interfaces, ie; | |
// animals.go | |
package animals | |
type Animal interface { | |
Speaks() string | |
} | |
type Dog struct{} // implementation of Animal | |
func (a Dog) Speaks() string { return "woof" } | |
// circus.go | |
package circus | |
import "animals" | |
func Perform(a animal.Animal) string { return a.Speaks() } | |
// Instead do this; | |
// animals.go | |
package animals | |
type Dog struct{} | |
func (a Dog) Speaks() string { return "woof" } | |
// circus.go | |
package circus | |
type Speaker interface { | |
Speaks() string | |
} | |
func Perform(a Speaker) string { return a.Speaks() } | |
//ie: | |
/* | |
1. Define the types | |
2. Define the interface at point of use. | |
*/ | |
// 27. Defining an interface upfront | |
// This is usually a code smell(see 26 above) for overengineering. | |
// But there are situations where you need define an interface upfront. several: | |
/* | |
1. Sealed interfaces | |
2. Abstract data types | |
3. Recursive interfaces | |
*/ | |
/* | |
1. Sealed interfaces is an interface with unexported methods. | |
This means users outside the package is unable to create types that fulfil the interface. | |
This is useful for emulating a sum type as an exhaustive search for the types that fulfil the interface can be done. | |
*/ | |
package numbers | |
type Positive interface { | |
Do() error | |
isPositive() // unexported making it SEALED | |
} | |
type Float64 float64 | |
// implement Positive for Float64 | |
type Int int | |
// implement Positive for Int | |
func Add(a, b Positive) error { | |
switch a.(type) { | |
case Float64: | |
return nil | |
case Int: | |
return nil | |
default: | |
return errors.New("FAIL") | |
} | |
} | |
// Now you cannot declare and create a Positive anywhere outside package numbers. | |
// Therefore in all the functions in numbers you can confidently and exhaustively pattern match on the types. | |
// You can use https://github.com/BurntSushi/go-sumtype as an analysis tool to easily pick up any non-exhaustive pattern match. | |
// For more, see; https://blog.chewxy.com/2018/03/18/golang-interfaces/ | |
// 28. you can send closures/funcs to channels | |
package main | |
import ( | |
"fmt" | |
) | |
// A SenderFunc is a function that takes no arguments and returns an int | |
type SenderFunc func() int | |
func sendLoop(c chan SenderFunc) { | |
for val := range c { | |
z := val() | |
fmt.Println(z) | |
} | |
} | |
func main() { | |
c := make(chan SenderFunc, 1) | |
mySender := func() int { | |
return 3434 | |
} | |
go func() { | |
c <- mySender | |
close(c) | |
}() | |
sendLoop(c) | |
} | |
// see: | |
// 1. https://news.ycombinator.com/item?id=19036106 | |
// 2. http://tleyden.github.io/blog/2016/12/20/understanding-function-closures-in-go/ | |
// 29. | |
// - If you have a struct, you should put pointers at the beginning of the struct. | |
// - Also any zero width struct field should not be placed at the end of a struct | |
/* | |
Reason is: | |
So the GC scans through from the beginning of the object until there's no more pointers, | |
and checks all the things marked as pointers. | |
If you have pointers at the end of an object, the gc has to scan through the entire object looking for them. | |
If they're all at the beginning, it stops after the last pointer. | |
A zero width struct field placed at the end, incurs an extra byte. | |
*/ - https://gophers.slack.com/archives/C0VP8EF3R/p1550064857080700 | |
- https://twitter.com/commaok/status/1155318095147393024 | |
// 30. Pattern for making a function type satisfy an interface | |
type Greeter interface { | |
Greet(who string) error | |
} | |
type GreeterFunc func(string) error | |
func (f GreeterFunc) Greet(who string) error { | |
return f(who) | |
} | |
// -also see: https://github.com/golang/go/issues/21670#issuecomment-325459202 | |
// 31. move closures to a struct with method with appropriate signature | |
/* | |
"A few times recently I've moved closures (for filepath.Walk(), for example) | |
to a struct with state with a method with the appropriate signature, | |
and then use a method expression as the argument to Walk." - @dgryski (on gophers-slack; https://gophers.slack.com/archives/C0VP8EF3R/p1560367178028100) | |
*/ | |
package main | |
import ( | |
"fmt" | |
"os" | |
"path/filepath" | |
) | |
type MyStruct struct{} | |
func (m MyStruct) MyWalkFunc(path string, info os.FileInfo, err error) error { | |
if err != nil { | |
return err | |
} | |
fmt.Printf("visited file or dir: %q\n", path) | |
return nil | |
} | |
func main() { | |
cool := MyStruct{} | |
err := filepath.Walk(".", cool.MyWalkFunc) | |
if err != nil { | |
fmt.Printf("error walking the path %v\n", err) | |
return | |
} | |
} | |
// 32. Count goroutines and fail if they aren't cleaned up(as a test mechanism) | |
- https://git.openprivacy.ca/cwtch.im/cwtch/src/master/testing/cwtch_peer_server_integration_test.go | |
- https://twitter.com/dan_ballard/status/1152346004173099009?s=21 | |
// 33. writing general purpose mock | |
type A interface { | |
Cool(int) error | |
} | |
type MockA struct { | |
CoolFn func(int) error | |
} | |
func (m MockA) Cool(y int) error { | |
return m.CoolFn(y) | |
} | |
// - https://twitter.com/peterbourgon/status/1159921583382835201 | |
// 34. | |
// Using Refreturn tool to find good candidates for mid-stack iniling | |
// - https://github.com/caddyserver/caddy/issues/2681#issuecomment-513908111 | |
// 35. Inline/Anonymous interfaces | |
package main | |
import "fmt" | |
// If you have an interface | |
type Xyz interface { | |
A() string | |
B() int | |
} | |
// and a func that takes that interface as an arg, BUT only uses one of the interfaces methods | |
func mineOne(x Xyz) string { | |
return x.A() | |
} | |
// when writing tests for that func, you need a type that implements all the methods of the interface | |
// even though you only need one method. | |
type xyzImpl struct{} | |
func (i xyzImpl) A() string { | |
return "hi" | |
} | |
func (i xyzImpl) B() int { | |
return int(32) | |
} | |
// However, instead you could change the func to take in an inline/anonymous interface | |
func mineTwo( | |
x interface{ A() string }, | |
) string { | |
return x.A() | |
} | |
// now, when writing tests for that func, you need ONLY implement a type that implements the one method of the interface | |
type mockXyzImpl struct{} | |
func (m mockXyzImpl) A() string { | |
return "hello" | |
} | |
func main() { | |
ans1 := mineOne(xyzImpl{}) | |
fmt.Println(ans1) | |
ans2 := mineTwo(mockXyzImpl{}) | |
fmt.Println(ans2) | |
} | |
// 36. Never return double nil | |
// If you have a func with this signature | |
FindDialByID(id int) (*Dial, error) | |
// What happens if you dont find a Dial? Do you return `nil, nil` to indicate no-dial-found and no-error? | |
// Do not do that. See: https://www.gobeyond.dev/crud/ | |
// Instead, not finding a dial should result in a non-nil error. | |
// Always return either the object or an error; both should not be nil | |
// 37. Navigating Go source code. | |
// (i) go to src/runtime/trace.go (https://github.com/golang/go/blob/go1.16.3/src/runtime/trace.go) | |
// it defines all events that `go tool trace` can emit. | |
// if you have a question about Go internals; you can look at the above file to see | |
// if there is a related event defined therein. | |
// eg, if you are interested to learn how goroutine creation works. | |
// theres an event called `traceEvGoCreate` defined in that file. | |
// use your IDE to find all the references of `traceEvGoCreate` (ie where it is used) | |
// (a) https://sourcegraph.com/github.com/golang/[email protected]/-/blob/src/runtime/trace.go#L36:2&tab=references | |
// (b) https://sourcegraph.com/github.com/golang/[email protected]/-/blob/src/runtime/trace.go#L1055:6&tab=references | |
// (c) https://sourcegraph.com/github.com/golang/[email protected]/-/blob/src/runtime/proc.go#L3990-4093 | |
// go backwards and you will eventually get to the functionality that does goroutine creation | |
// (ii) when you checkout the go source in your local machine, add the following env vars when in that directory(u can use direnv) | |
// export GOROOT=$(pwd) # will make goToDef use the checked go code instead of ua system installed Go | |
// export PATH=$PWD/bin:$PATH # so that when you re-compile Go, it will now always use the compiled Go instead of the system installed Go | |
// https://www.youtube.com/watch?v=q1jcRa5Dz_k(by Felix Geisendörfer) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Golang gotchas: | |
greate read: http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/ | |
*/ | |
1. //Shadowing | |
package main | |
import "fmt" | |
func main() { | |
x := 1 | |
fmt.Println(x) //prints 1 | |
{ | |
fmt.Println(x) //prints 1 | |
x := 2 | |
fmt.Println(x) //prints 2 | |
} | |
fmt.Println(x) //prints 1 (bad if you need 2) | |
} | |
//use go vet to see shadowed vars | |
$ go tool vet -shadow your_file.go | |
2. //Iterating bug | |
package main | |
import "fmt" | |
func main() { | |
strings := []string{"name","Komu"} | |
for _, value := range strings { | |
fmt.Printf("Value: %s and Pointer is: %v\n", value, &value) | |
} | |
} | |
//output: | |
//Value: name and Pointer is: 0x1040a120 | |
//Value: Komu and Pointer is: 0x1040a120 | |
//you can see the pointer, remained the same for each iteration(0x1040a120) | |
//to fix that u shud use a temp variable | |
for _, value := range strings { | |
tmp := value | |
fmt.Printf("Value: %s and Pointer is: %v\n", tmo, &tmp) //works | |
} | |
//u should never use the pointer(address) of an iterated variable | |
3. //Strings are immutable | |
// Strings are read-only byte slices(with a few extra properties) | |
func main() { | |
x := "text" | |
x[0] = 'T' | |
fmt.Println(x) | |
} //COMPILE error | |
func main() { | |
x := "text" | |
xbytes := []byte(x) | |
xbytes[0] = 'T' | |
fmt.Println(string(xbytes)) //WORKS | |
} | |
4. //strings and indices | |
func main() { | |
x := "text" | |
fmt.Println(x[0]) //print 116 rather than 't' | |
//116 is the codepoint for 't' | |
} | |
func main() { | |
x := "text" | |
fmt.Println( string(x[0]) ) | |
} // prints 't' | |
5. //string length | |
data = u'♥' | |
print(len(data)) //prints: 1 in python | |
func main() { | |
data := "♥" | |
fmt.Println(len(data)) //prints: 3 | |
} | |
//len() function returns the number of bytes instead of the number of characters | |
import ( | |
"fmt" | |
"unicode/utf8" | |
) | |
data := "♥" | |
fmt.Println(utf8.RuneCountInString(data)) //prints 1 | |
6. //log.Fatal and log.Panic abort app | |
import "log" | |
func main() { | |
log.Fatalln("Fatal Level: log entry") //app exits here | |
log.Println("Normal Level: log entry") //not called | |
} | |
7. //Value of iteration keys in strings | |
//The index value (the first value returned by the "range" operation) is the index of the first byte for the | |
//current "character" (unicode code point/rune) returned in the second value. It's not the index for the current "character" | |
8. //Unexported(lowercase) Struct Fields Are Not Encoded | |
import ( | |
"fmt" | |
"encoding/json" | |
) | |
type MyData struct { | |
One int | |
two string //lowercase(unexported) | |
} | |
func main() { | |
in := MyData{1,"two"} | |
fmt.Printf("%#v\n",in) //prints main.MyData{One:1, two:"two"} | |
encoded,_ := json.Marshal(in) | |
fmt.Println(string(encoded)) //prints {"One":1} | |
} | |
9. //closing http response body | |
//when u make http request, u need to close the http response body | |
func main() { | |
resp, err := http.Get("https://api.ipify.org?format=json") | |
defer resp.Body.Close()//not ok | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
//blah blah | |
//if req fails, resp==nil and defer resp.Body.Close() will cause a runtime panic | |
resp, err := http.Get("https://api.ipify.org?format=json") | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
defer resp.Body.Close()//ok, most of the time :-) | |
//when you get a redirection failure both resp and err variables will be non-nil | |
//thus defer resp.Body.Close() will not even be called** | |
package main | |
import ( | |
"fmt" | |
"net/http" | |
"io/ioutil" | |
) | |
func main() { | |
resp, err := http.Get("https://api.ipify.org?format=json") | |
if resp != nil { | |
defer resp.Body.Close() //FIX | |
} | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
body, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
fmt.Println(string(body)) | |
} | |
10. //closing http connections | |
//some servers keep network connections open for a while (based on their keep-alive configs) | |
//the standard http library will close the network connections only when the target HTTP server asks for it | |
//thus your app may run out of sockets/file descriptors under certain conditions. | |
//1. u can ask it close the connection after your request is done by setting the Close field in the request variable to true. | |
//2. or u can add a Connection request header and set it to close | |
//3. or create a custom http transport configuration that disables http connection reuse globally | |
//If you send a lot of requests to the same HTTP server it's ok to keep the network connection open | |
package main | |
import ( | |
"fmt" | |
"net/http" | |
"io/ioutil" | |
) | |
func main() { | |
req, err := http.NewRequest("GET","http://golang.org",nil) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
req.Close = true //method 1 | |
//or do this: | |
//req.Header.Add("Connection", "close") //method 2 | |
resp, err := http.DefaultClient.Do(req) | |
if resp != nil { | |
defer resp.Body.Close() | |
} | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
body, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
fmt.Println(len(string(body))) | |
} | |
func main() { | |
tr := &http.Transport{DisableKeepAlives: true} //method 3 | |
client := &http.Client{Transport: tr} | |
resp, err := client.Get("http://golang.org") | |
if resp != nil { | |
defer resp.Body.Close() | |
} | |
11. //recovering from panic | |
//The recover() function can be used to catch/intercept a panic. Calling recover() will do the trick only when it's done (called DIRECTLY) in a deferred function. | |
func main() { | |
recover() //doesn't do anything | |
panic("not good") | |
recover() //won't be executed :) | |
fmt.Println("ok") | |
} | |
func main() { | |
defer func() { | |
fmt.Println("recovered:",recover()) //WORKS | |
}() | |
panic("not good") | |
} | |
12. //type declarations and method inheritance | |
//When you create a type declaration by defining a new type from an existing (non-interface) type, | |
//you don't inherit the methods defined for that existing type. | |
import "sync" | |
type myMutex sync.Mutex | |
func main() { | |
var mtx myMutex | |
mtx.Lock() //error | |
} | |
//If you do need the methods from the original type you can define a new struct type embedding the original type as an anonymous field. | |
type myLocker struct { | |
sync.Mutex | |
} | |
func main() { | |
var lock myLocker | |
lock.Lock() //ok | |
lock.Unlock() //ok | |
} | |
13. //breaking out of for-switch and for-select statements | |
//A "break" statement without a label only gets you out of the inner switch/select block. | |
//If using a "return" statement is not an option then defining a label for the outer loop is the next best thing. | |
func main() { | |
loop: | |
for { | |
switch { | |
case true: | |
fmt.Println("breaking out...") | |
break loop //this will break of of both inner(switch) and outer(for) loops | |
} | |
} | |
fmt.Println("out!") | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// DOC: https://github.com/alco/gostart | |
/* | |
Anything you import is relative to your $GOPATH/src. If you have a dir like: | |
$GOPATH/src/ | |
└github.com/komuw/ | |
└── gopher | |
├── main.go | |
└── sub | |
└── sub.go | |
in your `main.go` file you'll need to import `sub` as follows: | |
*/ | |
package main | |
import "github.com/komuw/gopher/sub" | |
func main() { | |
sub.ExportedFunction() | |
} | |
// To keep it simple, ALWAYS create a new directory inside $GOPATH/src/github.com/komuw/ when you're starting a new Go project. | |
1. Splitting package into multiple files: | |
/* | |
Go treats files in a single dir as belonging to one package | |
as long as they all have the same name in their package declarations. | |
`$ cd $GOPATH/src/github.com/komuw/example` | |
`$ cat helper.go` | |
*/ | |
package main | |
func privateHelperFunc() int { | |
return 25 | |
} | |
/* | |
# now edit main.go to call this function | |
`$ cat main.go` | |
*/ | |
package main | |
// we do not need to import helper.go since it belongs to same package as main.go, ie pkg `main` | |
func main() { | |
fmt.Println("Hello world! My lucky number is", privateHelperFunc()) | |
} | |
/* | |
We cannot simply `go run main.go` because `main.go` calls a function defined in another file. | |
We can either; | |
- pass all files as arguments to go run or | |
- build the current package and then run the produced binary. | |
*/ | |
`$ go run *.go //or` | |
`$ go build` | |
`$ ./example` | |
2. Splitting package into multiple subpackages: | |
/* | |
Use a subpackage only when its functionality is tied to the main package which contains it | |
`$ cd $GOPATH/src/github.com/komuw/example` | |
`$ mkdir math` | |
`$ cat math/math.go` | |
*/ | |
package math | |
func Mul2(x int) int { | |
return x * 2 | |
} | |
/* | |
`$ cat $GOPATH/src/github.com/komuw/example/main.go` | |
*/ | |
package main | |
import ( | |
"fmt" | |
"github.com/komuw/example/math" // just "math" would not work, it would import std package math | |
) | |
func main() { | |
fmt.Println("number is", math.Mul2(privateHelperFunc())) | |
} | |
/* | |
`$ go run *.go` | |
*/ | |
/* | |
Note that if you were to replace package math in `math.go` with package main, | |
this would not make math.go part of the same main package that both `main.go` and `helper.go` belong to. | |
It would be treated as another main package and you would get an error when trying to run or import it because it doesn't define a main function. | |
*/ | |
3. Create a package for others to use (i.e. a non-main package): | |
/* | |
Packages that declare `package main` and are meant to be built to produce an executable binary are usually called `commands`. | |
The other flavor of Go packages is used as libraries or modules. | |
You can't build them into an executable. | |
Their purpose is to be imported into another package (not necessarily main package) to provide functionality to that package. | |
You create a package the same way as you would create a command. | |
The only difference is that instead of package main you write `package some_name` | |
*/ | |
/* | |
`$ cd $GOPATH/src/util` | |
`$ cat cool.go` // file name is arbitrary and doesn't make any significance to Go | |
*/ | |
package util | |
import "math" | |
func Square(x float32) float32 { | |
return x * x | |
} | |
/* | |
`$ go build` | |
This produces no output, it is only used to ensure it can compile with no errors | |
To precompile the package for use by other package, | |
you run `go install`. | |
This will put `util.a` into `$GOPATH/pkg/<arch>/util.a` | |
When building (`go build`) a main package, the resulting executable will be placed in the current directory. | |
Running go install, on the other hand, will build the source as usual and place the executable in $GOPATH/bin. | |
Running `go build` inside the directory of a simple package does not produce any binary. | |
Building a package this way is used to verify that it compiles cleanly. | |
*/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment