Notes for my Go programming workshop.
- Fabian Stäber, ConSol Software GmbH
- Java Developer, Gopher
- Some Go projects on GitHub, like github.com/fstab/grok_exporter
- Online tutorial:https://tour.golang.org
- Book: Alan A. A. Donovan, Brian W. Kernighan: The Go Programming Language, Addison-Wesley, 2015
- Google: Google keyword for the Go programming language is 'golang'
- This text:https://github.com/fstab/go-programming-for-java-developers
- Slides:https://goo.gl/P7XAXn (docs.google.com)
- Created at Google in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson
- Main design goal: Simplicity (not many features)
- Main purpose: System tools programming
Go is very similar to Java
- C-like Syntax
- Strongly typed
- Packages, Imports
- ...
Java developers will learn Go very quickly.
Java:
- Download and extract JDK.
- Set environment variable
JAVA_HOMEto JDK directory. - Include
$JAVA_HOME/bininPATH.
Go:
- Download and extract Go.
- Set environment variable
GOROOTto Go directory (default/usr/local/goorC:\Go). - Include
$GOROOT/bininPATH.
Optional (not needed in this workshop): Set GOPATH to workspace, like $HOME/go.
- Install go
- Run the following commands
go go help go help run go help fmtThe go command is more like mvn than like javac.
go get ... <-- download from github, bitbucket, etc. go test ... <-- like 'mvn test' go install ... <-- install binary to $GOPATH/bin Like mvn, the go command assumes a defined directory structure:
$GOPATH/bin <- compiled executables $GOPATH/pkg <- compiled libraries $GOPATH/src/github.com/... <- source code $GOPATH/src/bitbucket.org/... <- source code $GOPATH/src/... <- source code In real-world go development, you run go install to compile the source code and create an executable in $GOPATH/bin.
However, in this workshop we use go run, which is a shortcut to quickly run a single go file. The go run command does not require the directory structure above.
package main import ( "fmt" ) funcmain(){fmt.Printf("Hello, World!\n") }- Create a file
hello.gocontaining the hello world source code. - Format and run the program:
go fmt hello.go go run hello.goRead the documentation of the fmt package and the Printf call
go doc fmt go doc fmt.Printfos.Argsis an[]string(likeString[]in Java)len(os.Args)returns the number of elements (likearr.lengthin Java)
package main import ( "fmt""os" ) funcmain(){iflen(os.Args) !=2{fmt.Printf("Usage: ./hello <name>\n") return } fmt.Printf("Hello, %v\n", os.Args[1]) }os.Args[0]is the name of the executable- Example:
./hello Fabian:os.Args[0]:./helloos.Args[1]:Fabian
- With
go run hello.go Fabian, the program will be compiled to an executable in a temporary directory, this temporary executable is executed. ->os.Args[0]is the path to the temporary executable created bygo run.
Use of for like Java's for
fori:=0; i<10; i++{fmt.Printf("%v\n", i) }Use of for like Java's while
i:=0fori<10{fmt.Printf("%v\n", i) i++ }Use of for like Java's while(true)
i:=0for{ifi>=10{break } fmt.Printf("%v\n", i) i++ }Write a program that says hello to multiple people:
> go run hello.go Fabian Thomas ChristianHello, Fabian!Hello, Thomas!Hello, Christian!Variable declaration
varainta=3Variable declaration with initializer
vara=3Short variable declaration
a:=3Declaring multiple variables
var ( a, bintcstring ) vara, b, c=3, 4, "hello"Constants (like final in Java)
consta=3funcadd(aint, bint) int{returna+b }funcadd(a, bint) int{returna+b }funcaddAndMult(a, bint) (int, int){returna+b, a*b } funcmain(){sum, product:=addAndMult(3, 7) fmt.Printf("sum = %v, product = %v\n", sum, product) }package main import"fmt"funcswap(x, ystring) (string, string){returny, x } funcmain(){a, b:=swap("world", "hello") fmt.Printf("%v, %v\n", a, b) }package main import ( "fmt""strconv""os" ) funcmain(){// We assume that len(os.Args) >= 2n, err:=strconv.Atoi(os.Args[1]) iferr!=nil{fmt.Printf("Error: %v is not a valid number.\n", os.Args[1]) return } fmt.Printf("Your number is %v\n", n) }Go's nil is like Java's null.
In go it's a syntax error if a variable is declared but not used:
funcmain(){sum, product:=addAndMult(3, 7) fmt.Printf("sum = %v\n", sum) }Syntax error, because variable product is declared but not used.
To explicitly ignore a return value, use _:
funcmain(){sum, _:=addAndMult(3, 7) fmt.Printf("sum = %v\n", sum) }Write a program that prints the first n Fibonacci numbers.
> go run fibonacci.go 101 1 2 3 5 8 13 21 34 55As a simple way to play with functions and loops, implement the square root function using Newton's method.
In this case, Newton's method is to approximate Sqrt(x) by picking a starting point z and then repeating:
To begin with, just repeat that calculation 10 times and see how close you get to the answer for various values (1, 2, 3, ...).
Next, change the loop condition to stop once the value has stopped changing (or only changes by a very small delta). See if that's more or fewer iterations. How close are you to the math.Sqrt?
Hint: to declare and initialize a floating point value, give it floating point syntax or use a conversion:
z := float64(1)
z := 1.0
package main import ( "bufio""fmt""os" ) constpath="./hello.txt"funcmain(){file, err:=os.Open(path) iferr!=nil{fmt.Printf("failed to open file: %v\n", err) os.Exit(-1) } deferfile.Close() reader:=bufio.NewReader(file) for{line, err:=reader.ReadString('\n') iferr!=nil{break } fmt.Printf("%v", line) } }Modify the hello world program and add some deferred method calls, like
deferfmt.Printf("defer 1\n") deferfmt.Printf("defer 2\n") deferfmt.Printf("defer 3\n")Figure out the order in which these calls are executed.
package main import"fmt"funcdouble(iint){i=i*2 } funcmain(){i:=2double(i) fmt.Printf("i = %v\n", i) }Result: i = 2
package main import"fmt"funcdouble(i*int){*i=*i*2 } funcmain(){i:=2double(&i) fmt.Printf("i = %v\n", i) }Result: i = 4
package main import"fmt"typerectanglestruct{heightfloat64widthfloat64 } funcmain(){square:=&rectangle{height: 4.0, width: 4.0, } fmt.Printf("square has area of %v\n", square.height*square.width) }- A struct is like a final class in Java.
- Variables starting with an upper case letter are public, variables starting with a lower case letter are package private.
package main import"fmt"typerectanglestruct{heightfloat64widthfloat64 } func (r*rectangle) area() float64{returnr.width*r.height } funcmain(){square:=&rectangle{height: 4.0, width: 4.0, } fmt.Printf("square has area of %v\n", square.area()) }Define struct circle with variable radius that also has a method area().
Hint: go doc math.Pi
Java: Implicit conversion from float64 to String
return"the area is " + s.area();Go: Explicit string formatting
returnfmt.Sprintf("area is %v", s.area())typeshapeinterface{area() float64 } funcareaInfo(sshape) string{returnfmt.Sprintf("area is %v", s.area()) } funcmain(){c:=&circle{radius: 2.0, } r:=&rectangle{width: 4.0, height: 4.0, } fmt.Printf("circle: %v\n", areaInfo(c)) fmt.Printf("rectangle: %v\n", areaInfo(r)) }Go has some pre-defined interfaces. The most important ones are:
typeStringerinterface{String() string }Like Java's toString()
typeerrorinterface{Error() string }Remember file, err := os.Open(path)? The err is of type error.
Implement a String() method for rectangle and circle.
The following code:
funcmain(){c:=&circle{radius: 2.0, } r:=&rectangle{width: 4.0, height: 4.0, } fmt.Printf("Shape 1: %v\n", c) fmt.Printf("Shape 2: %v\n", r) }Should produce the following output:
Shape 1: circle with radius 2 and area 12.566370614359172Shape 2: rectangle with width 4, height 4, and area 16Example 1: Define an anonymous function and assign it to a variable
package main import"fmt"funcmain(){f:=func(nint) int{return2*n } fmt.Printf("f(21) = %v\n", f(21)) }Example 2: Pass a function as parameter to another function
package main import"fmt"funcapplyFunction(nint, ffunc(int) int){fmt.Printf("f(%v) = %v\n", n, f(n)) } funcmain(){f:=func(nint) int{return2*n } applyFunction(21, f) }Java Arrays:
intsize = 10; int[] arr = newint[size];Go Slices:
size:=10slice:=make([]int, size)Iterate over a slice:
fori, val:=rangeslice{fmt.Printf("slice[%v] = %v\n", i, val) }package main import"fmt"funccreateSlice(sizeint) []int{slice:=make([]int, size) fori:=0; i<size; i++{slice[i] =i+1 } returnslice } funcapplyFunction(slice []int, ffunc(int) int){fori, n:=rangeslice{slice[i] =f(n) } } funcmain(){f:=func(nint) int{return2*n } slice:=createSlice(10) fmt.Printf("%v\n", slice) applyFunction(slice, f) fmt.Printf("%v\n", slice) }Result:
[1 2 3 4 5 6 7 8 9 10][2 4 6 8 10 12 14 16 18 20]Example 1:
slice:=createSlice(10) fmt.Printf("%v\n", slice) part:=slice[3:7] applyFunction(part, f) fmt.Printf("%v\n", slice)Result:
[1 2 3 4 5 6 7 8 9 10][1 2 3 8 10 12 14 8 9 10]Example 2:
slice:=createSlice(10) fmt.Printf("%v\n", slice) firstHalf:=slice[:len(slice)/2] applyFunction(firstHalf, f) fmt.Printf("%v\n", slice) secondHalf:=slice[len(slice)/2:] applyFunction(secondHalf, f) fmt.Printf("%v\n", slice)Result:
[1 2 3 4 5 6 7 8 9 10][2 4 6 8 10 6 7 8 9 10][2 4 6 8 10 12 14 16 18 20]Write a function combine() such that
funcmain(){add:=func(a, bint) int{returna+b } mult:=func(a, bint) int{returna*b } slice:=createSlice(10) fmt.Printf("add(1..10)=%v\n", combine(slice, add)) fmt.Printf("mult(1..10)=%v\n", combine(slice, mult)) }Prints the following output
add(1..10)=55mult(1..10)=3628800Bonus task: Implement two versions of combine(): one with a for loop, and one recursive version without using for.
package main import ( "fmt""time" ) funcsay(sstring){fori:=0; i<5; i++{time.Sleep(100*time.Millisecond) fmt.Printf("%v\n", s) } } funcmain(){gosay("world") say("hello") }package main import ( "time""fmt" ) funcproduceNumber(cchanint){time.Sleep(10*time.Second) c<-3 } funcmain(){c:=make(chanint) goproduceNumber(c) n:=<-cfmt.Printf("n = %v\n", n) }package main import ( "time""fmt" ) funcproduceNumber(cchanint){fori:=0; i<10; i++{time.Sleep(1*time.Second) c<-i+1 } close(c) } funcmain(){c:=make(chanint) goproduceNumber(c) forn:=rangec{fmt.Printf("n = %v\n", n) } }Re-write the channel examples with two go-routines:
- One producer routine producing numbers
- One consumer routine printing numbers to stdout
Terminology: A function running as a background thread is a go-routine.
package main import ( "time""fmt" ) funcproduceNumbers(cchanint){fori:=0; i<10; i++{time.Sleep(1*time.Second) c<-3 } close(c) } funcconsumeNumbers(cchanint, donechaninterface{}){forn:=rangec{fmt.Printf("n = %v\n", n) } close(done) } funcmain(){c:=make(chanint) done:=make(chaninterface{}) goproduceNumbers(c) goconsumeNumbers(c, done) <-done }chancintchandoneinterface{} select{casen<-c: // do something with ncase<-done: // shut down }Implement a deadlock (go-routine A is reading from channel of go-routine B and vice versa) and see what happens.
- Write to channel blocks
- Channels may have capacity
- Reading from a closed channel returns
nil - Writing to a closed channel panics
-> Always the producer should close a channel.
package main import ( "fmt""net/http""os" ) funchandler(w http.ResponseWriter, r*http.Request){fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) } funcmain(){http.HandleFunc("/", handler) err:=http.ListenAndServe("localhost:8000", nil) iferr!=nil{fmt.Fprintf(os.Stderr, "Failed to open Web server on localhost:8080: %v\n", err) } }Include a unique number in each response:
- First response gets number 1
- Second response gets number 2
- etc.
Avoid race conditions. Don't communicate through shared memory, use channels!!!
From The Go Programming Language, Chapter 1.7:
package main import ( "fmt""log""net/http""sync" ) varmu sync.Mutexvarcountintfuncmain(){http.HandleFunc("/", handler) http.HandleFunc("/count", counter) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } // handler echoes the Path component of the requested URL.funchandler(w http.ResponseWriter, r*http.Request){mu.Lock() count++mu.Unlock() fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) } // counter echoes the number of calls so far.funccounter(w http.ResponseWriter, r*http.Request){mu.Lock() fmt.Fprintf(w, "Count %d\n", count) mu.Unlock() }Topics covered in this workshop:
- control flow: if/else, for
- functions, error handling, deferred methods
- pointers, structs, methods, interfaces
- arrays & slices
- multithreading
package main import ( "fmt""os" ) funcmain(){fori:=1; i<len(os.Args); i++{fmt.Printf("Hello, %v\n", os.Args[i]) } }package main import ( "fmt""os""strconv" ) funcmain(){iflen(os.Args) !=2{fmt.Print("Usage: fibonacci <n>\n") return } n, err:=strconv.Atoi(os.Args[1]) iferr!=nil{fmt.Print("Invalid number.\n") return } fmt.Print("Result:") varprev, cur=0, 1fori:=0; i<n; i++{fmt.Printf(" %v", cur) prev, cur=cur, prev+cur } fmt.Print("\n") }package main import ( "fmt" ) funcmain(){deferfmt.Printf("defer 1\n") deferfmt.Printf("defer 2\n") fmt.Printf("Hello, World!\n") deferfmt.Printf("defer 3\n") }package main import ( "fmt""math" ) typerectanglestruct{heightfloat64widthfloat64 } func (r*rectangle) area() float64{returnr.width*r.height } typecirclestruct{radiusfloat64 } func (c*circle) area() float64{returnmath.Pi*c.radius*c.radius } funcmain(){r:=&rectangle{height: 4.0, width: 4.0, } c:=&circle{radius: 2.0, } fmt.Printf("square has area of %v\n", r.area()) fmt.Printf("circle has area of %v\n", c.area()) }package main import ( "fmt""math" ) typerectanglestruct{heightfloat64widthfloat64 } func (r*rectangle) area() float64{returnr.width*r.height } typecirclestruct{radiusfloat64 } func (c*circle) area() float64{returnmath.Pi*c.radius*c.radius } func (r*rectangle) String() string{returnfmt.Sprintf("rectangle with width %v, height %v, and area %v", r.width, r.height, r.area()) } func (c*circle) String() string{returnfmt.Sprintf("circle with radius %v and area %v", c.radius, c.area()) } funcmain(){c:=&circle{radius: 2.0, } r:=&rectangle{width: 4.0, height: 4.0, } fmt.Printf("Shape 1: %v\n", c) fmt.Printf("Shape 2: %v\n", r) }package main import"fmt"// for loopfunccombine1(slice []int, ffunc(int, int) int) int{result:=slice[0] fori:=1; i<len(slice); i++{result=f(result, slice[i]) } returnresult } // recursivefunccombine2(slice []int, ffunc(int, int) int) int{iflen(slice) ==1{returnslice[0] } iflen(slice) ==2{returnf(slice[0], slice[1]) } left:=combine2(slice[:len(slice)/2], f) right:=combine2(slice[len(slice)/2:], f) returnf(left, right) } funccreateSlice(sizeint) []int{slice:=make([]int, size) fori:=0; i<size; i++{slice[i] =i+1 } returnslice } funcmain(){add:=func(a, bint) int{returna+b } mult:=func(a, bint) int{returna*b } slice:=createSlice(10) fmt.Printf("for loop:\n") fmt.Printf("add(1..10)=%v\n", combine1(slice, add)) fmt.Printf("mult(1..10)=%v\n", combine1(slice, mult)) fmt.Printf("\nrecursive:\n") fmt.Printf("add(1..10)=%v\n", combine2(slice, add)) fmt.Printf("mult(1..10)=%v\n", combine2(slice, mult)) }package main import ( "time""fmt" ) funcproduceNumbers(cchanint){fori:=0; i<10; i++{time.Sleep(1*time.Second) c<-i } close(c) } funcconsumeNumbers(cchanint){forn:=rangec{fmt.Printf("n = %v\n", n) } } funcmain(){c:=make(chanint) goproduceNumbers(c) goconsumeNumbers(c) time.Sleep(12*time.Second) }package main funcmain(){c1, c2:=make(chanint), make(chanint) done:=make(chaninterface{}) gofunc(){<-c1// read from c1c2<-42// write to c2close(done) }() gofunc(){<-c2// read from c2c1<-42// write to c1<-done }() <-done }package main import ( "fmt""net/http""os" ) varc=make(chanint) funcproducer(){i:=0for{i++c<-i } } funchandler(w http.ResponseWriter, r*http.Request){fmt.Fprintf(w, "Request number is %v. Note that you browser might send two requests per 'reload'.\n", <-c) } funcmain(){goproducer() http.HandleFunc("/", handler) err:=http.ListenAndServe("localhost:8000", nil) iferr!=nil{fmt.Fprintf(os.Stderr, "Failed to open Web server on localhost:8080: %v\n", err) } }