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_HOME
to JDK directory. - Include
$JAVA_HOME/bin
inPATH
.
Go:
- Download and extract Go.
- Set environment variable
GOROOT
to Go directory (default/usr/local/go
orC:\Go
). - Include
$GOROOT/bin
inPATH
.
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 fmt
The 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"
)
func main() {
fmt.Printf("Hello, World!\n")
}
- Create a file
hello.go
containing the hello world source code. - Format and run the program:
go fmt hello.go
go run hello.go
Read the documentation of the fmt
package and the Printf
call
go doc fmt
go doc fmt.Printf
os.Args
is an[]string
(likeString[]
in Java)len(os.Args)
returns the number of elements (likearr.length
in Java)
package main
import (
"fmt"
"os"
)
func main() {
if len(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]
:./hello
os.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
for i := 0; i < 10; i++ {
fmt.Printf("%v\n", i)
}
Use of for
like Java's while
i := 0
for i < 10 {
fmt.Printf("%v\n", i)
i++
}
Use of for
like Java's while(true)
i := 0
for {
if i >= 10 {
break
}
fmt.Printf("%v\n", i)
i++
}
Write a program that says hello to multiple people:
> go run hello.go Fabian Thomas Christian
Hello, Fabian!
Hello, Thomas!
Hello, Christian!
Variable declaration
var a int
a = 3
Variable declaration with initializer
var a = 3
Short variable declaration
a := 3
Declaring multiple variables
var (
a, b int
c string
)
var a, b, c = 3, 4, "hello"
Constants (like final
in Java)
const a = 3
func add(a int, b int) int {
return a + b
}
func add(a, b int) int {
return a + b
}
func addAndMult(a, b int) (int, int) {
return a + b, a * b
}
func main() {
sum, product := addAndMult(3, 7)
fmt.Printf("sum = %v, product = %v\n", sum, product)
}
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("world", "hello")
fmt.Printf("%v, %v\n", a, b)
}
package main
import (
"fmt"
"strconv"
"os"
)
func main() {
// We assume that len(os.Args) >= 2
n, err := strconv.Atoi(os.Args[1])
if err != 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:
func main() {
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 _
:
func main() {
sum, _ := addAndMult(3, 7)
fmt.Printf("sum = %v\n", sum)
}
Write a program that prints the first n Fibonacci numbers.
> go run fibonacci.go 10
1 1 2 3 5 8 13 21 34 55
As 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"
)
const path = "./hello.txt"
func main() {
file, err := os.Open(path)
if err != nil {
fmt.Printf("failed to open file: %v\n", err)
os.Exit(-1)
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil {
break
}
fmt.Printf("%v", line)
}
}
Modify the hello world program and add some deferred method calls, like
defer fmt.Printf("defer 1\n")
defer fmt.Printf("defer 2\n")
defer fmt.Printf("defer 3\n")
Figure out the order in which these calls are executed.
package main
import "fmt"
func double(i int) {
i = i * 2
}
func main() {
i := 2
double(i)
fmt.Printf("i = %v\n", i)
}
Result: i = 2
package main
import "fmt"
func double(i *int) {
*i = *i * 2
}
func main() {
i := 2
double(&i)
fmt.Printf("i = %v\n", i)
}
Result: i = 4
package main
import "fmt"
type rectangle struct {
height float64
width float64
}
func main() {
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"
type rectangle struct {
height float64
width float64
}
func (r *rectangle) area() float64 {
return r.width * r.height
}
func main() {
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
return fmt.Sprintf("area is %v", s.area())
type shape interface {
area() float64
}
func areaInfo(s shape) string {
return fmt.Sprintf("area is %v", s.area())
}
func main() {
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:
type Stringer interface {
String() string
}
Like Java's toString()
type error interface {
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:
func main() {
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.566370614359172
Shape 2: rectangle with width 4, height 4, and area 16
Example 1: Define an anonymous function and assign it to a variable
package main
import "fmt"
func main() {
f := func(n int) int {
return 2 * n
}
fmt.Printf("f(21) = %v\n", f(21))
}
Example 2: Pass a function as parameter to another function
package main
import "fmt"
func applyFunction(n int, f func(int) int) {
fmt.Printf("f(%v) = %v\n", n, f(n))
}
func main() {
f := func(n int) int {
return 2 * n
}
applyFunction(21, f)
}
Java Arrays:
int size = 10;
int[] arr = new int[size];
Go Slices:
size := 10
slice := make([]int, size)
Iterate over a slice:
for i, val := range slice {
fmt.Printf("slice[%v] = %v\n", i, val)
}
package main
import "fmt"
func createSlice(size int) []int {
slice := make([]int, size)
for i := 0; i < size; i++ {
slice[i] = i + 1
}
return slice
}
func applyFunction(slice []int, f func(int) int) {
for i, n := range slice {
slice[i] = f(n)
}
}
func main() {
f := func(n int) int {
return 2 * 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
func main() {
add := func(a, b int) int {
return a + b
}
mult := func(a, b int) int {
return a * 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)=55
mult(1..10)=3628800
Bonus task: Implement two versions of combine()
: one with a for
loop, and one recursive version without using for
.
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Printf("%v\n", s)
}
}
func main() {
go say("world")
say("hello")
}
package main
import (
"time"
"fmt"
)
func produceNumber(c chan int) {
time.Sleep(10 * time.Second)
c <- 3
}
func main() {
c := make(chan int)
go produceNumber(c)
n := <-c
fmt.Printf("n = %v\n", n)
}
package main
import (
"time"
"fmt"
)
func produceNumber(c chan int) {
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
c <- i + 1
}
close(c)
}
func main() {
c := make(chan int)
go produceNumber(c)
for n := range c {
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"
)
func produceNumbers(c chan int) {
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
c <- 3
}
close(c)
}
func consumeNumbers(c chan int, done chan interface{}) {
for n := range c {
fmt.Printf("n = %v\n", n)
}
close(done)
}
func main() {
c := make(chan int)
done := make(chan interface{})
go produceNumbers(c)
go consumeNumbers(c, done)
<-done
}
chan c int
chan done interface{}
select {
case n <- c:
// do something with n
case <- 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"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
func main() {
http.HandleFunc("/", handler)
err := http.ListenAndServe("localhost:8000", nil)
if err != 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"
)
var mu sync.Mutex
var count int
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/count", counter)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
// handler echoes the Path component of the requested URL.
func handler(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.
func counter(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"
)
func main() {
for i := 1; i<len(os.Args); i++ {
fmt.Printf("Hello, %v\n", os.Args[i])
}
}
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
if len(os.Args) != 2 {
fmt.Print("Usage: fibonacci <n>\n")
return
}
n, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Print("Invalid number.\n")
return
}
fmt.Print("Result:")
var prev, cur = 0, 1
for i := 0; i < n; i++ {
fmt.Printf(" %v", cur)
prev, cur = cur, prev + cur
}
fmt.Print("\n")
}
package main
import (
"fmt"
)
func main() {
defer fmt.Printf("defer 1\n")
defer fmt.Printf("defer 2\n")
fmt.Printf("Hello, World!\n")
defer fmt.Printf("defer 3\n")
}
package main
import (
"fmt"
"math"
)
type rectangle struct {
height float64
width float64
}
func (r *rectangle) area() float64 {
return r.width * r.height
}
type circle struct {
radius float64
}
func (c *circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func main() {
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"
)
type rectangle struct {
height float64
width float64
}
func (r *rectangle) area() float64 {
return r.width * r.height
}
type circle struct {
radius float64
}
func (c *circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (r *rectangle) String() string {
return fmt.Sprintf("rectangle with width %v, height %v, and area %v", r.width, r.height, r.area())
}
func (c *circle) String() string {
return fmt.Sprintf("circle with radius %v and area %v", c.radius, c.area())
}
func main() {
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 loop
func combine1(slice []int, f func(int, int) int) int {
result := slice[0]
for i := 1; i < len(slice); i++ {
result = f(result, slice[i])
}
return result
}
// recursive
func combine2(slice []int, f func(int, int) int) int {
if len(slice) == 1 {
return slice[0]
}
if len(slice) == 2 {
return f(slice[0], slice[1])
}
left := combine2(slice[:len(slice)/2], f)
right := combine2(slice[len(slice)/2:], f)
return f(left, right)
}
func createSlice(size int) []int {
slice := make([]int, size)
for i:=0; i<size; i++ {
slice[i] = i+1
}
return slice
}
func main() {
add := func(a, b int) int {
return a + b
}
mult := func(a, b int) int {
return a * 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"
)
func produceNumbers(c chan int) {
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
c <- i
}
close(c)
}
func consumeNumbers(c chan int) {
for n := range c {
fmt.Printf("n = %v\n", n)
}
}
func main() {
c := make(chan int)
go produceNumbers(c)
go consumeNumbers(c)
time.Sleep(12 * time.Second)
}
package main
func main() {
c1, c2 := make(chan int), make(chan int)
done := make(chan interface{})
go func() {
<-c1 // read from c1
c2 <- 42 // write to c2
close(done)
}()
go func() {
<-c2 // read from c2
c1 <- 42 // write to c1
<-done
}()
<-done
}
package main
import (
"fmt"
"net/http"
"os"
)
var c = make(chan int)
func producer() {
i := 0
for {
i++
c <- i
}
}
func handler(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)
}
func main() {
go producer()
http.HandleFunc("/", handler)
err := http.ListenAndServe("localhost:8000", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open Web server on localhost:8080: %v\n", err)
}
}