This is my study notes for Golang. It is a quick intro/guide to start with golang if you have prior programming experience.
package main/func main()
package main //Excuetables must be of package main
import "fmt"
func main() {
var g string = "Hello golang"
println(g)
}
func function() string {
return "Five of Diamonds"
}
_ to replace unused variables.new() and make(), they differ though, we will discuss that later, briefly new returns pointer, make return value. read Doc.passed by value except arrays, slices, maps and channels which some calls reference types, these types are passed by reference ( they internally have pointers, so no copying of the actual data happens when passing them) .
//Declration
var g string
//Assignment
g = "golang"
//Declration & Assignment
var g = "golang"
var g string = "golang"
//Shorthand - Declration & Assignmnet
a := 10
b := "golang"
*Uninitialized variables are given its zero value (e.g int = 0, string = "", bool = false)
The possible values for bool are true and false.
(0 to 255)(0 to 65535)(0 to 4294967295)(0 to 18446744073709551615)-128 to 127)(-32768 to 32767)(-2147483648 to 2147483647)(-9223372036854775808 to 9223372036854775807)all IEEE-754 32-bit floating-point numbersall IEEE-754 64-bit floating-point numbersuint8 rune alias for int32If variables/functions starts with Uppercase character, it is accessible outside the scope of its package, if lowercase then it is only accessible inside its package.
package myPkg
var Uppercase = "This is accessible outside the pkg"
var lowercase = "This is not accessible outside the pkg"
func UppercaseFunc() string { return "This is accessible outside the pkg"}
func lowercaseFunc() string { return "This is accessible outside the pkg"}
// Another file:
package main
import "myPkg"
func main() {
//Accessible
println(myPkg.Uppercase)
println(myPkg.UppercaseFunc())
//Not Accessible
println(myPkg.lowercase)
println(myPkg.lowercaseFunc())
}
//take input like cin >> in c++
var x int = 1337
var y string = "string value"
_, err := fmt.Scan(&x, &y)
fmt.Println("You Entered x:", x, " and y: ", y, " Error: ", err)
//take input like scanf in C
_, err = fmt.Scanf("%d %s", &x, &y)
fmt.Println("You Entered x:", x, " and y: ", y, " Error: ", err)
//take input with white spaces
var z string = "string"
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
z = scanner.Text()
fmt.Println("You Entered z:", z)
iota in Go, is a value used within the const block, its value starts at 0 per block, and increment each time it is used again
const (
c0 = iota // c0 == 0
c1 = iota // c1 == 1
c2 = iota // c2 == 2
)
Pointers syntax is essentially like C/C++
var value int = 1000
var pointer *int = &value
println(value) //1000
println(pointer) //0xfffffffff
println(*pointer) //1000
(*pointer)++ //1001
*pointer = *pointer + 10 //1011
println(*pointer) //1011
println(*pointer + *pointer) //1011 + 1011 = 2022
value := 10
if value < 10 {
println("Less Than 10")
} else if value > 10 {
println("Greater Than 10")
} else {
println("Equals 10")
}
//if conditions with statment
//note that value is inscope of all if/else's
if value := 10; value < 10 {
println(value, "Less Than 10")
} else if value > 10{
println(value, "Greater Than 10")
}else{
println(value, "Equals 10")
}
Go doesn't have Ternary Operator ( x < 0 ? A : B ) 🤷
There are 3 forms of for loops, also there is no a while loop syntax in GO (instead it is a form of for loops), also there is no do-while at all
//For loop
for j := 7; j <= 9; j++ { /*stuff*/ }
//While like for loop
i := 1
for i <= 3 { /*stuff*/ i++ }
//Infinite Loop : While(true)
for { /*stuff*/ if (/*stuff*/) break }
for i, v := range arr { //do stuff }
for _, v := range arr { //do stuff }
for i, _ := range arr { //do stuff }
Switch statements in GO doesn't require break; they will break by default, fallthrough keyword used to go to NEXT statement even if condition doesn't match, fallthrough is like a break so no code can be after it. however a workaround is to use labels and goto
i := 2
fmt.Println("Switch for i = ", i, " goes to: ")
switch i {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
i = 4
fallthrough //goes to NEXT case even if doesn't match.
case 3:
fmt.Println("three")
case 4:
fmt.Println("four")
case 5,6:
fmt.Println("five or six")
default:
fmt.Println("default")
}
func0 := func() int {x++; return x}
package main
var x = 0
func main() {
//local x
x := 0
func0 := func() int {x++; return x}
func1 := incrementGlobalX //without ()
func2 := wrapper()
func3 := wrapper()
println(func0(), " : func0 (local x)")
println(func1(), " : func1 (global x)")
println(func2(), " : func2 (per func scope x1)")
println(func3(), " : func3 (per func scope x2)")
println("Second Increment")
println(func0(), " : func0 (local x)")
println(func1(), " : func1 (global x)")
println(func2(), " : func2 (per func scope x1)")
println(func3(), " : func3 (per func scope x2)")
}
func incrementGlobalX() int {
x++
return x
}
func wrapper() func() int {
x := 0
return func() int {
x++
return x
}
}
passed by value except arrays, slices, maps and channels which some calls reference types, these types are passed by reference. // return void
func add(x int, y int) {
fmt.Println("Hello, World!")
}
//-------arguments------return------
func add(x int, y int) int {
return x + y
}
//-----same type arguments-----------
func add(x, y int) int {
return x + y
}
func swap(x, y string) (string, string) {
return y, x
}
//in main
a, b := swap("hello", "world")
fmt.Println(a, b) //prints "world hello"
You can declare return variables and name them at the beginning, they are returned in the end.
*you can override the returns and return whatever you want at the return statement.
//Returns x,y at the end.
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
//return a,b <- u can override default return of x,y.
}
The rightmost argument can be a list of variable size (slice) of data.
// x value here has no use for average. just illustrates the idea of having arguments
// then a variable number of arguments
func average(x int, values ...int) float64{
//print values
fmt.Println("Single argument value: ", x)
fmt.Println("Variable argument values: ", values)
//calculate average
total := 0
for _, value := range values {
total += value
}
return float64(total) / float64(len(values))
}
func main() {
avg := average(10,20,30,40,50)
println("Average:", avg)
}
func0 := func() int {x++; return x} as anonymous functions or to another declared functions
package main
var x = 0
func main() {
//local x
x := 0
func0 := func() int {x++; return x}
func1 := incrementGlobalX //without ()
func2 := wrapper()
func3 := wrapper()
println(func0(), " : func0 (local x)")
println(func1(), " : func1 (global x)")
println(func2(), " : func2 (per func scope x1)")
println(func3(), " : func3 (per func scope x2)")
println("Second Increment")
println(func0(), " : func0 (local x)")
println(func1(), " : func1 (global x)")
println(func2(), " : func2 (per func scope x1)")
println(func3(), " : func3 (per func scope x2)")
}
func incrementGlobalX() int {
x++
return x
}
func wrapper() func() int {
x := 0
return func() int {
x++
return x
}
}
func visit(numbers []int, callback func(int2 int)){
for _, n := range numbers {
callback(n*2)
}
}
func main() {
visit([]int{1,2,3,4}, func(n int){
fmt.Println(n, "- Printed withing the callback function.")
})
}
Defer used before a functions executes the function at the end of the scope of it, think of it as a destructor for the scope. usually used to close opened files/buffers so u open the file and closes it using defer in the next line to keep things clean. they're executed as a stack.
fmt.Println("One")
defer fmt.Println("Four")
defer fmt.Println("Three")
fmt.Println("Two")
//Prints One Two Three Four
Receiver are the way you create a method for a specific type/struct
type rect struct {
width, height int
}
func (r *rect) area() int {
return r.width * r.height
}
//used as
r := rect{2,3}
areaX := r.area()
fmt.Println(areaX)
type Person struct {
First string
Last string
Age int
}
type Employee struct {
Person
ID string
Salary int
}
func (p Person) FullName() string{
return p.First + " " + p.Last
}
//Override
func (p Employee) FullName() string{
return p.ID + " " + p.First + " " + p.Last
}
func main() {
x := Employee{
Person{
"Sherif",
"Abdel-Naby",
12},
"0ID12000ID",
9999,
}
fmt.Println(x)
fmt.Println(x.Person.FullName()) //Sherif Abdel-Naby
fmt.Println(x.FullName()) //0ID12000ID Sherif Abdel-Naby
Arrays, Slices, Maps, and Structs
var x[15] int
var twoD [2][3] int
Slices are of dynamic size.
letters := []string{"a", "b", "c", "d"}
/* using make -> make([]T, len, cap) */
var s []byte
s = make([]byte, 5, 5)
//OR
s := make([]byte, 5)
// both equiavlent to: s == []byte{0, 0, 0, 0, 0}
old capacity - sliced part from the beginning only names := [4]string{"John","Paul","George","Ringo",}
fmt.Println(names) //[John Paul George Ringo]
a := names[0:2]
b := names[1:3]
fmt.Println(a, b) //[John Paul] [Paul George]
b[0] = "XXX"
fmt.Println(a, b) //[John XXX] [XXX George]
fmt.Println(names) //[John XXX George Ringo]
//ALSO
//This is an array literal:
[3]bool{true, true, false}
//And this creates the same array as above, then builds a slice that references it:
[]bool{true, true, false}
for i, v := range arr { //do stuff }
for _, v := range arr { //do stuff }
for i, _ := range arr { //do stuff }
Append return a whole new array (not reference).
var s []int
// append works on nil slices.
s = append(s, 0)
// The slice grows as needed.
s = append(s, 1)
Append add element at the end of the slice if there is enough capacity and return a reference type!, if not enough capacity it allocate and copy to a new array and return it as a new value! and the old array points to the old data.
If Append had enough capacity (didn't allocate new array) then changing a value in the new returned array changes the value in the old! but if it allocated a new array to expand capacity, then changing a value at an index of the newly returned array DOESN'T change the old array!
consider only using append where the left hand side is the same variable in the append first argument (S = append(S, .....) ) to avoid any unexpected results
//Allocate new capacity
var s []int
s = make([]int, 5, 5)
x := append(s, 1, 2 ,3)
x[0] = 1337
s[0] = 6800
fmt.Println(s,x) //[6800 0 0 0 0] [1337 0 0 0 0 1 2 3]
//Doesn't allocat new capacity and return reference
var s []int
s = make([]int, 5, 150)
x := append(s, 1, 2 ,3)
x[0] = 1337
s[0] = 6800
fmt.Println(s,x) //[6800 0 0 0 0] [6800 0 0 0 0 1 2 3]
//notice that 1337 is overwritten
Append Another Slice
a = append(a, b...)
Copy
Copy only copy elements of size = min(len(a), len(b)). so, the new slice to which a copy is to be made must have a size == size of the original array to have all elements copied.
b = make([]T, len(a))
copy(b, a)
// or
b = append([]T(nil), a...)
Cut
a = append(a[:i], a[j:]...)
Delete
a = append(a[:i], a[i+1:]...)
// or
a = a[:i+copy(a[i:], a[i+1:])]
From https://play.golang.org/p/U67R66Oab8r
// To create an empty map, use the builtin `make`:
// `make(map[key-type]val-type)`.
m := make(map[string]int)
// Set key/value pairs using typical `name[key] = val`
// syntax.
m["k1"] = 7
m["k2"] = 13
// Printing a map with e.g. `fmt.Println` will show all of
// its key/value pairs.
fmt.Println("map:", m)
// Get a value for a key with `name[key]`.
v1 := m["k1"]
fmt.Println("v1: ", v1)
// The builtin `len` returns the number of key/value
// pairs when called on a map.
fmt.Println("len:", len(m))
// The builtin `delete` removes key/value pairs from
// a map.
delete(m, "k2")
fmt.Println("map:", m)
// The optional second return value when getting a
// value from a map indicates if the key was present
// in the map. This can be used to disambiguate
// between missing keys and keys with zero values
// like `0` or `""`. Here we didn't need the value
// itself, so we ignored it with the _blank identifier_
// `_`.
_, prs := m["k2"]
fmt.Println("prs:", prs)
//You can use zero value to check if value exist. use second return instead.
fmt.Println("key not found (gets zero value):", m["notFoundKey"])
// You can also declare and initialize a new map in
// the same line with this syntax.
n := map[string]int{"foo": 1, "bar": 2}
fmt.Println("map:", n)
' ' after field type, tags can be used to e.g exclude or rename a field when Encoding/Decoding it to/from JSON
package main
import "fmt"
type Parent struct {
First string
Last string
Age int
}
type Child struct {
Parent
First string
Middle string
}
func main() {
x := Child{
Parent{
"First",
"Last",
12},
"Child's First",
"Middle",
}
fmt.Println(x)
fmt.Println(x.First)
fmt.Println(x.Parent.First)
fmt.Println(x.Middle)
fmt.Println(x.Last)
fmt.Println(x.Parent.Last)
fmt.Println(x.Age)
fmt.Println(x.Parent.Age)
}
Packages might need your type to implement its interface to work, for example the sort package requires you to implement swap, less, equal methods in order to work. also fmt.Println() requires you to implement func (t T) String() string
A type implements an interface by implementing its methods. There is no explicit declaration of intent, no "implements" keyword.
// Here's a basic interface for geometric shapes.
type geometry interface {
area() float64
perim() float64
//extraFunc() string //if we uncomment this, so rect and circle won't
// be implementing the geometry interface
}
// For our example we'll implement this interface on
// `rect` and `circle` types.
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
// To implement an interface in Go, we just need to
// implement all the methods in the interface. Here we
// implement `geometry` on `rect`s.
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
// The implementation for `circle`s.
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
// If a variable has an interface type, then we can call
// methods that are in the named interface. Here's a
// generic `measure` function taking advantage of this
// to work on any `geometry`.
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}
// The `circle` and `rect` struct types both
// implement the `geometry` interface so we can use
// instances of
// these structs as arguments to `measure`.
measure(r)
measure(c)
}
type Person struct {
First string
Last string
Age int
}
type Employee struct {
Person
ID string
Salary int
}
func (p Person) FullName() string{
return p.First + " " + p.Last
}
//Override
func (p Employee) FullName() string{
return p.ID + " " + p.First + " " + p.Last
}
func main() {
x := Employee{
Person{
"Sherif",
"Abdel-Naby",
12},
"0ID12000ID",
9999,
}
fmt.Println(x)
fmt.Println(x.Person.FullName()) //Sherif Abdel-Naby
fmt.Println(x.FullName()) //0ID12000ID Sherif Abdel-Naby
Go Concurrency is made available by what's called go-routines , basically when a function is preceded with the go keyword, it runs in a go-routine, think of go-routine as a thread (though they're different...). go-routines is one of the most important features of Go that makes it and its concurrency model special.
For data synchronization you can use mutex Locks, WaitGroups, and Atomic operations, however.. It's recommended to use Go Channels for data synchronization, though using the sync package (using mutex, locks, atomics, and WaitGroups) is also usable if it make sense for your use case.
//TODO Example on using synchronization by mutex Locks, WaitGroups, and Atomic operations
bidirectional (chan), receive (<-chan) only, or send only(chan <-) , send/receive only channels are useful when channels are passed as arguments, this indicates(and rather enforces) that the passed channel can only be received from (and you can send to), so this introduces some sort of control over how channels are used. think of pkgs where I don't want users to send anything to my channel.Note that I am using time.Sleep at the end to wait for the code to execute as the program will instantly close after running the two go routines.
c := make(chan int)
go func() {
for i := 0; i < 9; i++ {
time.Sleep(time.Second)
c <- i
}
}()
go func() {
for{
fmt.Println( <- c )
}
}()
time.Sleep(time.Second * 15)
Using Range on a channel, it will iterate over values added on the channel until the channel is closed. No need to use time.sleep as the for-range is a blocking function.
c := make(chan int)
go func() {
for i := 0; i <= 10; i++ {
time.Sleep(time.Second)
c <- i
}
close(c)
}()
for n := range c{
fmt.Println(n)
}
Using wait-group to use more than 1 function to write to the same channel.
Using a waitGroup to close the channel once the two writing functions signal wg.Done()
c := make(chan int)
var wg sync.WaitGroup
wg.Add(2)
go func() {
for i := 0; i <= 10; i++ {
time.Sleep(time.Millisecond * 350)
c <- i
}
wg.Done()
}()
go func() {
for i := 1000; i <= 1010; i++ {
time.Sleep(time.Millisecond * 350)
c <- i
}
wg.Done()
}()
go func() {
wg.Wait()
close(c)
}()
for n := range c{
fmt.Println(n)
}
Using dynamic number of function calls.
Also notice passing i inside the go func, this is because the value outside is in a for-loop, hence it is changing, so using it inside the the go-routine will lead to unexpected results.
c := make(chan string)
var wg sync.WaitGroup
n := 10
wg.Add(n)
for i := 0; i < n; i++ {
go func(i int) {
for t := i*10; t < i*10 + 10; t++ {
c <- "From " + strconv.Itoa(i) + " : " + strconv.Itoa(t)
}
wg.Done()
}(i)
}
go func() {
wg.Wait()
close(c)
}()
for x := range c{
fmt.Println(x)
}
Using only channels without waitGroup. This is done using a channel that store bool (or anything really), and use a function to receive n-done signals then close the main channel.
c := make(chan string)
done := make(chan bool)
n := 2
for i := 0; i < n; i++ {
go func(i int) {
for t := i*10; t < i*10 + 10; t++ {
c <- "From " + strconv.Itoa(i) + " : " + strconv.Itoa(t)
}
done <- true
}(i)
}
go func() {
//receive the n-dones from the go-routines
for i := 0; i < n; i++{
<- done
}
close(c)
}()
for x := range c{
fmt.Println(x)
}
In this example we sum values from 0 to i. e.g( i = 3 → 0+1+2+3 = 6)
This code we create a go routine that feeds the increment channels values 1,2,3 another Channel called the sum channel will take the increment channel and processes its values ( so the sum channel will run until the increment channel closes ), then the sum channel will put its sum value for the main to pick. the point here that main can do other stuff while sum channel finish processing. also we can pass any type of channel to sum channel to sum not necessary an incrementing values in a decoupled way.
func main() {
i := 10
//Return A Channel that produces 1, 2, 3...n
c := incrementer(i)
// Take a channel that produces 1,2,3...n and sum these numbers
// returns a channel that have the data in it after summation (so it is not blocking the main itself)
cSum := puller(c)
/* DO STUFF WHILE Puller is working (that's why it is returning a channel */
//Pull from the puller when we want the result (This is blocking now)
//Result for i := 10 should be : 10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + 0 = 55
fmt.Println("Final Sum", <- cSum)
}
//returns an ACTIVE go routine that produces 1,2,3..n
func incrementer(n int) chan int {
out := make(chan int, 10)
//no need to pass n as parameter as it is a non-changing variable in this context
go func() {
for i := 0; i <= n; i++ {
fmt.Println("From incrementer: Produced i = ", i )
out <- i
//just to illustrate it is blocking in main.
time.Sleep(time.Millisecond * 100)
}
close(out)
}()
return out
}
//takes a channel that produces numbers that are to be summed together.
func puller(c chan int) chan int {
out := make (chan int)
go func() {
var sum int
for n := range c{
fmt.Println("From Puller go-routine: Sum + i ->", sum, "+", n, "=", sum + n)
sum += n
}
fmt.Println("Summation Finished -> Outputing SUM")
out <- sum
// also we can output each sum stage for whoever uses the channel and close when finish.
//close(out)
}()
return out
}
Output:
From incrementer: Produced i = 0
From Puller go-routine: Sum + i -> 0 + 0 = 0
From incrementer: Produced i = 1
From Puller go-routine: Sum + i -> 0 + 1 = 1
From incrementer: Produced i = 2
From Puller go-routine: Sum + i -> 1 + 2 = 3
From incrementer: Produced i = 3
From Puller go-routine: Sum + i -> 3 + 3 = 6
From incrementer: Produced i = 4
From Puller go-routine: Sum + i -> 6 + 4 = 10
From incrementer: Produced i = 5
From Puller go-routine: Sum + i -> 10 + 5 = 15
Summation Finished -> Outputing SUM
Final Sum 15
Go doesn't use try/catch and exceptions to handle errors, instead, functions also returns an error along with its own return. programmer should then check this error by an if-condition and deiced what to do accordingly
from "Go by Example"...
// In Go it's idiomatic to communicate errors via an
// explicit, separate return value. This contrasts with
// the exceptions used in languages like Java and Ruby and
// the overloaded single result / error value sometimes
// used in C. Go's approach makes it easy to see which
// functions return errors and to handle them using the
// same language constructs employed for any other,
// non-error tasks.
package main
import "errors"
import "fmt"
// By convention, errors are the last return value and
// have type `error`, a built-in interface.
func f1(arg int) (int, error) {
if arg == 42 {
// `errors.New` constructs a basic `error` value
// with the given error message.
return -1, errors.New("can't work with 42")
}
// A `nil` value in the error position indicates that
// there was no error.
return arg + 3, nil
}
// It's possible to use custom types as `error`s by
// implementing the `Error()` method on them. Here's a
// variant on the example above that uses a custom type
// to explicitly represent an argument error.
type argError struct {
arg int
prob string
}
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
func f2(arg int) (int, error) {
if arg == 42 {
// In this case we use `&argError` syntax to build
// a new struct, supplying values for the two
// fields `arg` and `prob`.
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}
func main() {
// The two loops below test out each of our
// error-returning functions. Note that the use of an
// inline error check on the `if` line is a common
// idiom in Go code.
for _, i := range []int{7, 42} {
if r, e := f1(i); e != nil {
fmt.Println("f1 failed:", e)
} else {
fmt.Println("f1 worked:", r)
}
}
for _, i := range []int{7, 42} {
if r, e := f2(i); e != nil {
fmt.Println("f2 failed:", e)
} else {
fmt.Println("f2 worked:", r)
}
}
// If you want to programmatically use the data in
// a custom error, you'll need to get the error as an
// instance of the custom error type via type
// assertion.
_, e := f2(42)
if ae, ok := e.(*argError); ok {
fmt.Println(ae.arg)
fmt.Println(ae.prob)
}