Programski jezik Go

Bazirano na Go verziji 1.12.4 i A Tour of Go

Milovan Tomašević

Kreirano Dec 1, 2019 Nedelja 01:12, pritisni ESC za mapu

Uvod

Osnovne osobine

  • Statički tipiziran, kompajliran programski jezik
  • Google: Robert Griesemer, Rob Pike, i Ken Thompson
  • Sličan C-u ali sa memory safety, garbage collection i direktnom podrškom za konkurentno programiranje (Communicating Sequential Processes (CSP))
  • Efikasnost, jednostavnost i čitkost
  • Akcenat na mrežnom i multi-core softveru
  • Razvoj započet 2007, javno dostupan od 2009

Instalacija i podešavanja

Instalacija

  • Koristiti instaler za OS
  • …ili raspakovati tarball/zip i podesiti PATH varijablu na go/bin folder
  • Na primer:

    wget https://dl.google.com/go/go$VERSION.$OS-$ARCH.tar.gz
    mkdir -p ~/install/go
    tar -C ~/install/go -xzf go$VERSION.$OS-$ARCH.tar.gz
    
  • gde je VERSION - tekuća verzija (trenutno 1.12.4), OS - operativni sistem (npr. linux) a ARCH - arhitektura (npr. amd64)
  • podesiti PATH:

    export PATH=$PATH:~/install/go/
    

Workspace direktorijum

  • Direktorijum za Go kod
  • Podrazumevano $HOME/go
  • Ukoliko želite da promenite morate podesiti GOPATH varijablu

Testiranje instalacije

$ go version
go version go1.12.4 linux/amd64
  • Napraviti src/hello u workspace direktorijumu (dakle ~/go/src/hello)
  • Napraviti fajl hello.go sa sadržajem:

    package main
    
    import "fmt"
    
    func main() {
      fmt.Printf("Здраво, свете!\n")
    }
    
  • Preći u projektni folder, pokrenuti build i zatim startovati program:

    $ cd ~/go/src/hello
    $ go build
    $ ./hello
    Здраво, свете!
    

Kako pisati Go kod?

Uvod

Go alati podrazumevaju određene konvencije u organizaciji foldera i programskog koda.

Bazirano na How to Write Go Code

Workspaces

  • Obično se sav Go kod smešta na jedno mesto koje zovemo workspace
  • Na ovom mestu se nalazi veći broj repozitorijuma pod sistemima za kontrolu verzija (npr. git).
  • Svaki repozitorijum se sastoji od jednog ili više paketa
  • Svaki paket se sastoji od jednog ili više Go fajlova u jednom direktorijumu
  • Putanja (path) do direktorijuma paketa određuje njegov import path

Workspaces

bin/
    hello                          # command executable
    outyet                         # command executable
src/
    github.com/golang/example/
        .git/                      # Git repository metadata
  hello/
      hello.go               # command source
  outyet/
      main.go                # command source
      main_test.go           # test source
  stringutil/
      reverse.go             # package source
      reverse_test.go        # test source
    golang.org/x/image/
        .git/                      # Git repository metadata
  bmp/
      reader.go              # package source
      writer.go              # package source
    ... (many more repositories and packages omitted) ...

GOPATH

  • Podrazumevano $HOME/go na Unix-like sistemima ili %USERPOFILE%\go na Windows-u.
  • go env GOPATH komanda daje informaciju o tekućoj lokaciji tj. sadržaj GOPATH varijable ili podrazumevanu lokaciju ukoliko nije podešena.
  • Da bi instalirani Go programi bili dostupni:

    $ export PATH=$PATH:$(go env GOPATH)/bin
    

Import putanja (import path)

  • Kratke putanje za standardnu biblioteku. Na primer: fmt, net/http
  • Putanja je relativna u odnosu na $GOPATH/src
  • Mora biti jedinstvena
  • Dobra praksa je upotreba domena VCS hosting sajtova. Na primer:

    $GOPATH/src/github.com/user
    

Prvi Go program

$ mkdir $GOPATH/src/github.com/user/hello
  • Fajl hello.go
package main

import "fmt"

func main() {
  fmt.Printf("Здраво, свете!\n")
}
$ go install github.com/user/hello
# ili
$ cd $GOPATH/src/github.com/user/hello
$ go install
  • hello program će biti instaliran u $GOPATH/bin/
$ $GOPATH/bin/hello
Здраво, свете!
# ili samo
$ hello
Здраво, свете!
  • Sledeći korak je da promenu zabeležimo u sistemu za kontrolu verzija:
$ cd $GOPATH/src/github.com/user/hello
$ git init
Initialized empty Git repository in /home/user/work/src/github.com/user/hello/.git/
$ git add hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
1 file changed, 1 insertion(+)
  create mode 100644 hello.go

Prva Go biblioteka

$ mkdir $GOPATH/src/github.com/user/stringutil
// Package stringutil contains utility functions for working with strings.
package stringutil

// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
  r := []rune(s)
  for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
    r[i], r[j] = r[j], r[i]
  }
  return string(r)
}
  • Provera ispravnosti biblioteke
$ go build github.com/user/stringutil
# ili ako smo već u folderu stringutil
$ go build
  • Modifikovati program hello.go
package main

import (
  "fmt"

  "github.com/user/stringutil"
)

func main() {
  fmt.Println(stringutil.Reverse("!етевс ,овардЗ"))
}
$ go install github.com/user/hello
$ hello
Здраво, свете!
bin/
  hello                 # command executable
src/
    github.com/user/
        hello/
            hello.go      # command source
        stringutil/
            reverse.go    # package source

Imenovanje paketa

  • Prvi iskaz u svakom Go fajlu mora biti:

    package name
    
  • …gde je name ime paketa koje se koristi pri import-u
  • Svi fajlovi koji pripadaju istom paketu moraju koristiti isto ime
  • Go konvencija je da je ime paketa poslednji element import putanje. Primer: ukoliko se paket importuje sa crypto/rot13 tada njegovo ime treba da bude rot13
  • Izvršne komande moraju biti u paketu main
  • Ne zahteva se da ime paketa bude jedinstveno kod svih paketa koji se importuju ali se zahteva da putanja bude

Promena imena pri import-u paketa

Import declaration          Local name of Sin

import   "lib/math"         math.Sin
import m "lib/math"         m.Sin
import . "lib/math"         Sin
  • Import paketa bez upotrebe (zbog side-effect-a)
import _ "lib/math"

Testiranje

  • Lightweight test okvir u paketu testing + go test komanda
  • Jedinični test se piše u fajlu koji se završava na _test.go i koji sadrži funkcije oblika TestXXX sa signaturom func (t *testing.T)
  • go test poziva sve funkcije i ako ona pozove t.Error ili t.Fail test se smatra neuspešnim
  • Fajl $GOPATH/src/github.com/user/stringutil/reverse_test.go
package stringutil

import "testing"

func TestReverse(t *testing.T) {
  cases := []struct {
    in, want string
  }{
    {"Hello, world", "dlrow ,olleH"},
    {"Hello, 世界", "界世 ,olleH"},
    {"", ""},
  }
  for _, c := range cases {
    got := Reverse(c.in)
    if got != c.want {
      t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
    }
  }
}
$ go test github.com/user/stringutil
ok  	github.com/user/stringutil 0.165s
# ili samo
$ go test
ok  	github.com/user/stringutil 0.165s

Udaljeni paketi (Remote packages)

  • Import putanja može biti u obliku URL-a do repozitorijuma za kontrolu verzija (npr. Git ili Mercurial)
  • go get alat će preuzeti kod iz udaljenog repozitorijuma ako nije dostupan u lokalnom radnom prostoru (workspace)
  • Zavisni paketi dostupni preko udaljenih repozitorijuma se takođe automatski preuzimaju
  • Za više informacija videti go help importpath

Go Tour

Interaktivno učenje Go jezika:

Pokretanje lokalno:

$ go get golang.org/x/tour
$ tour

Paketi, varijable, funkcije

Paketi

  • Svaki Go program se sastoji od paketa
  • Program počinje izvršavanje u paketu main i funkciji func main()
package main

import (
  "fmt"
  "math/rand"
)

func main() {
  fmt.Println("My favorite number is", rand.Intn(10))
}
  • Paket se referencira po poslednjoj komponenti import putanje (u prethodnom primeru rand).

Import iskaz

  • Navođenje paketa koji se koriste u posmatranom fajlu
  • Može se navesti više paketa unutar zagrada (tzv. factored import)
package main

import (
  "fmt"
  "math"
)

func main() {
  fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}

Eksportovana imena

Ime je eksportovano iz paketa (odnosno može se importovati u drugim paketima) ako počinje velikim slovom.

package main

import (
  "fmt"
  "math"
)

func main() {
  fmt.Println(math.pi)  // <-- treba math.Pi
}

Imenima koji počinju malim slovom ne može se pristupiti izvan paketa u kome su definisani.

Funkcije

  • Funkcije mogu imati nula ili više argumenata
package main

import "fmt"

func add(x int, y int) int {
  return x + y
}

func main() {
  fmt.Println(add(42, 13))
}
  • Ili kraće
package main

import "fmt"

func add(x, y int) int {
  return x + y
}

func main() {
  fmt.Println(add(42, 13))
}

Višestruke povratne vrednosti

  • Funkcije mogu vratiti proizvoljan broj rezultata
package main

import "fmt"

func swap(x, y string) (string, string) {
  return y, x
}

func main() {
  a, b := swap("hello", "world")
  fmt.Println(a, b)
}

Imenovane povratne vrednosti

  • Povratne vrednosti mogu biti imenovane i u tom slučaju može se koristiti return iskaz bez argumenata (tzv. naked return)
package main

import "fmt"

func split(sum int) (x, y int) {
  x = sum * 4 / 9
  y = sum - x
  return
}

func main() {
  fmt.Println(split(17))
}

Varijable

  • var iskaz definiše varijable. Tip se navodi na kraju
  • var iskaz se može koristiti na nivou paketa ili funkcije
package main

import "fmt"

var c, python, java bool

func main() {
  var i int
  fmt.Println(i, c, python, java)
}

Varijable sa inicijalizatorima

  • var iskaz može imati inicijalizatore, jedan po varijabli
  • Ako se koristi inicijalizator, tip može da se izostavi jer može da se odredi na osnovu inicijalizatora
package main

import "fmt"

var i, j int = 1, 2

func main() {
  var c, python, java = true, false, "no!"
  fmt.Println(i, j, c, python, java)
}

Kratka deklaracija varijabli

  • Unutar funkcija, kraći oblik deklaracije baziran na := dodeli može da se koristi
package main

import "fmt"

func main() {
  var i, j int = 1, 2
  k := 3
  c, python, java := true, false, "no!"

  fmt.Println(i, j, k, c, python, java)
}
  • Ovo nije moguće koristiti van funkcije jer svaki iskaz mora početi sa ključnom rečju

Osnovni tipovi

  • Osnovni tipovi u Go-u su:
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
package main

import (
  "fmt"
  "math/cmplx"
)

var (
  ToBe   bool       = false
  MaxInt uint64     = 1<<64 - 1
  z      complex128 = cmplx.Sqrt(-5 + 12i)
)

func main() {
  fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
  fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
  fmt.Printf("Type: %T Value: %v\n", z, z)
}
Type: bool Value: false
Type: uint64 Value: 18446744073709551615
Type: complex128 Value: (2+3i)

Nulte vrednosti

  • Varijable deklarisane bez inicijalizatora se inicijalizuju na podrazumevane nulte vrednosti:
    • 0 za numeričke tipove
    • false za boolean tip
    • "" (prazan string) za string tip
package main

import "fmt"

func main() {
  var i int
  var f float64
  var b bool
  var s string
  fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
0 0 false ""

Konverzija tipova

  • Izraz oblika T(v) konvertuje vrednost v u tip T
  • Na primer:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
  • … ili jednostavnije:
i := 42
f := float64(i)
u := uint(f)
  • Za razliku od C-a u Go-u dodela između različitih tipova zahteva eksplicitnu konverziju
package main

import (
  "fmt"
  "math"
)

func main() {
  var x, y int = 3, 4
  var f float64 = math.Sqrt(float64(x*x + y*y))
  var z uint = uint(f)
  fmt.Println(x, y, z)
}

Inferencija tipova (Type inference)

  • Kada je desna strana iskaza dodele (bilo := bilo var =) tipizirana, leva strana će biti istog tipa
var i int
j := i // j is an int
  • Ali ako je na desnoj strani netipizirana numerička konstanta, tip zavisi od preciznosti konstante:
i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

Konstante

  • Deklarisane kao varijable ali upotrebom ključne reči const
  • Mogu biti osnovnih tipova
  • Ne mogu se definisati upotrebom :=
package main

import "fmt"

const Pi = 3.14

func main() {
  const World = "世界"
  fmt.Println("Hello", World)
  fmt.Println("Happy", Pi, "Day")

  const Truth = true
  fmt.Println("Go rules?", Truth)
}

Numeričke konstante

  • Vrednosti proizvoljne preciznosti (arbitrary-precision)
  • Kod netipiziranih konstanti tip se određuje na osnovu konteksta
package main

import "fmt"

const (
  // Create a huge number by shifting a 1 bit left 100 places.
  // In other words, the binary number that is 1 followed by 100 zeroes.
  Big = 1 << 100
  // Shift it right again 99 places, so we end up with 1<<1, or 2.
  Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
  return x * 0.1
}

func main() {
  fmt.Println(needInt(Small))
  fmt.Println(needFloat(Small))
  fmt.Println(needFloat(Big))
  fmt.Println(needInt(Big))  // <-- constant overflow
}
const Huge = 1e1000

# Sledeća linija ne može da se kompajlira
# Greška je 'constant 1.00000e+1000 overflows float64'
fmt.Println(Huge)

# Ali ovo radi bez problema jer se kalkulacija obavlja od strane kompajlera
# u vreme kompajliranja
fmt.Println(Huge / 1e999)

Enumerisane konstante (iota)

  • Kada nam je potrebna enumeracija
const (
    CategoryBooks = iota // 0
    CategoryHealth       // 1
    CategoryClothing     // 2
)
  • ili u kombinaciji sa tipom (da bi kompajler mogao da spreči greške sa tipovima)
type Stereotype int

const (
    TypicalNoob Stereotype = iota // 0
    TypicalHipster                // 1
    TypicalUnixWizard             // 2
    TypicalStartupFounder         // 3
)
  • Ako želimo da preskočimo vrednosti
type AudioOutput int

const (
    OutMute AudioOutput = iota // 0
    OutMono                    // 1
    OutStereo                  // 2
    _
    _
    OutSurround                // 5
)
  • Možemo kreirati i izraze sa iota
type Allergen int

const (
    IgEggs Allergen = 1 << iota // 1 << 0 which is 00000001
    IgChocolate                 // 1 << 1 which is 00000010
    IgNuts                      // 1 << 2 which is 00000100
    IgStrawberries              // 1 << 3 which is 00001000
    IgShellfish                 // 1 << 4 which is 00010000
)

Iskazi kontrole toka: for, if, else, switch i defer

For iskaz

  • Go ima samo jedan iskaz za petlje – for
  • Tri komponente:
    • init iskaz – izvršava se pre prve iteracije
    • uslov – evaluira se pre svake iteracije i u zavisnosti od rezultata ciklus se izvršava ili se petlja prekida
    • post iskaz – izvršava se na kraju svake iteracije
package main

import "fmt"

func main() {
  sum := 0
  for i := 0; i < 10; i++ {
    sum += i
  }
  fmt.Println(sum)
}
  • Zagrade () se ne navode kao u nekim drugim jezicima ali je navođenje {} za telo petlje obavezno
  • Varijable kreirane u init iskazu su dostupne samo unutar petlje
  • init i post iskazi su opcioni
package main

import "fmt"

func main() {
  sum := 1
  for ; sum < 1000; {
    sum += sum
  }
  fmt.Println(sum)
}

for je while u Go-u

  • Kada nemamo init i post iskaz možemo izostaviti ; i dobijamo ekvivalent while petlje u drugim jezicima
package main

import "fmt"

func main() {
  sum := 1
  for sum < 1000 {
    sum += sum
  }
  fmt.Println(sum)
}

"Beskonačna" petlja

  • Ako se izostavi i uslov dobijamo beskonačnu petlju
package main

func main() {
  for {
  }
}

Uslovi – if iskaz

  • Kao i kod for i kod if iskaza zagrade nije potrebno navoditi
package main

import (
  "fmt"
  "math"
)

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))
}

if sa kratkim iskazom

  • Kao i for i if može da ima kratak iskaz (najčešće :=) koji se izvršava pre uslova
  • Varijable deklarisane u ovom iskazu su dostupne samo unutar if iskaza
package main

import (
  "fmt"
  "math"
)

func pow(x, n, lim float64) float64 {
  if v := math.Pow(x, n); v < lim {
    return v
  }
  return lim
}

func main() {
  fmt.Println(
    pow(3, 2, 10),
    pow(3, 3, 20),
  )
}

if i else

  • Varijable deklarisane u if kratkom iskazu su dostupne i u opcionom else bloku
package main

import (
  "fmt"
  "math"
)

func pow(x, n, lim float64) float64 {
  if v := math.Pow(x, n); v < lim {
    return v
  } else {
    fmt.Printf("%g >= %g\n", v, lim)
  }
  // can't use v here, though
  return lim
}

func main() {
  fmt.Println(
    pow(3, 2, 10),
    pow(3, 3, 20),
  )
}
27 >= 20
9 20

switch iskaz

  • Kraći način pisanja sekvence if/else iskaza. Izvršava prvi case blok gde je vrednost jednaka vrednošću izraza uslova
  • Može imati kratak iskaz kao i if
package main

import (
  "fmt"
  "runtime"
)

func main() {
  fmt.Print("Go runs on ")
  switch os := runtime.GOOS; os {
  case "darwin":
    fmt.Println("OS X.")
  case "linux":
    fmt.Println("Linux.")
  default:
    // freebsd, openbsd,
    // plan9, windows...
    fmt.Printf("%s.\n", os)
  }
}
  • Za razliku od drugih jezika izvršava se samo jedan case blok (nema "propadanja"). Takođe case prihvata izraz koji ne mora biti konstanta i čija vrenost ne mora biti numerička

Evaluacija switch iskaza

  • Evaluacija case blokova ide od vrha prema dnu dok se ne nađe prvi blok čija vrednost je jednaka zadatom uslovu
  • f() se ne poziva ako je i==0
switch i {
case 0:
case f():
}
package main

import (
  "fmt"
  "time"
)

func main() {
  fmt.Println("When's Saturday?")
  today := time.Now().Weekday()
  switch time.Saturday {
  case today + 0:
    fmt.Println("Today.")
  case today + 1:
    fmt.Println("Tomorrow.")
  case today + 2:
    fmt.Println("In two days.")
  default:
    fmt.Println("Too far away.")
  }
}

switch bez uslova

  • Ekvivalentno sa switch true
  • Ćistiji način pisanja dugih if-then-else lanaca
package main

import (
  "fmt"
  "time"
)

func main() {
  t := time.Now()
  switch {
  case t.Hour() < 12:
    fmt.Println("Good morning!")
  case t.Hour() < 17:
    fmt.Println("Good afternoon.")
  default:
    fmt.Println("Good evening.")
  }
}

defer iskaz

  • Odlaže izvršavanje funkcije dok se ne vratimo iz funkcije u kojoj se nalazimo
  • Parametri funkcije se evaluiraju na mestu poziva defer ali se ciljna funkcija ne poziva do povratka
package main

import "fmt"

func main() {
  defer fmt.Println("world")

  fmt.Println("hello")
}
hello
world

Stekovanje defer poziva

  • defer pozivi se smeštaju na stek i po povratku funcije se izvršavaju u LIFO redosledu
package main

import "fmt"

func main() {
  fmt.Println("counting")

  for i := 0; i < 10; i++ {
    defer fmt.Println(i)
  }

  fmt.Println("done")
}
counting
done
9
8
...

struct, slice i map

Pokazivači (Pointers)

  • Memorijska adresa vrednosti varijable
  • *T je pokazivač na vrednost tipa T
  • & operator vraća pokazivač na zadati argument/vrednost

    i := 42
    p = &i  // p je pokazivač na vrednost 42
    
  • * operator označava vrednost na koju pokazivač pokazuje

    fmt.Println(*p) // čitanje i vrednosti kroz pokazivač p
    *p = 21         // postavljanje i vrednosti kroz pokazivač p
    
    • Ovo se još naziva i dereferenciranje ili indirekcija
    • Za razliku od jezika C, Go nema pokazivačku aritmetiku
package main

import "fmt"

func main() {
  i, j := 42, 2701

  p := &i         // point to i
  fmt.Println(*p) // read i through the pointer
  *p = 21         // set i through the pointer
  fmt.Println(i)  // see the new value of i

  p = &j         // point to j
  *p = *p / 37   // divide j through the pointer
  fmt.Println(j) // see the new value of j
}

Strukture (struct)

  • Kolekcija polja
package main

import "fmt"

type Vertex struct {
  X int
  Y int
}

func main() {
  fmt.Println(Vertex{1, 2})
}
  • Poljima se pristupa upotrebom . operatora
package main

import "fmt"

type Vertex struct {
  X int
  Y int
}

func main() {
  v := Vertex{1, 2}
  v.X = 4
  fmt.Println(v.X)
}

Pokazivači na strukture

  • Poljima strukture se može pristupiti preko pokazivača na strukturu
  • Sintaksno, ako imamo pokazivač p na strukturu, polju X bi mogli pristupiti sa (*p).X
  • Pošto je ovakva sintaksa teža za korišćenje uvedena je prečica p.X tj. nije potrebno eksplicitno dereferenciranje

    package main
    
    import "fmt"
    
    type Vertex struct {
      X int
      Y int
    }
    
    func main() {
      v := Vertex{1, 2}
      p := &v
      p.X = 1e9
      fmt.Println(v)
    }
    

struct literali

  • Kreiranje strukture listanjem vrednosti njenih polja
  • Moguće je koristiti sintaksu Name: za postavljanje vrednosti polja i u tom slučaju redosled je irelevantan
  • Ukoliko se koristi operator & vraća se pokazivač na strukturu
package main

import "fmt"

type Vertex struct {
  X, Y int
}

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)
}

Nizovi (Arrays)

  • [n]T – niz od n elemenata tipa T
  • var a [10]int – niz od 10 elemenata tipa int
  • Nizovi su fiksne veličine
package main

import "fmt"

func main() {
  var a [2]string
  a[0] = "Hello"
  a[1] = "World"
  fmt.Println(a[0], a[1])
  fmt.Println(a)

  primes := [6]int{2, 3, 5, 7, 11, 13}
  fmt.Println(primes)
}

Isečci (Slices)

  • Niz je fiksne dužine
  • Isečak je "prozor" na niz koji ima dinamičku veličinu
  • []T – isečak tipa T
  • Isečak se formira iznad niza na sledeći način:

    a[low : high]
    
  • Ovo formira polu-otvoren interval elemenata uključujući prvi ali isključujući poslednji

    package main
    
    import "fmt"
    
    func main() {
      primes := [6]int{2, 3, 5, 7, 11, 13}
    
      var s []int = primes[1:4]
      fmt.Println(s)
    }
    

Isečci kao pokazivači

  • Isečci se ponašaju kao pokazivači na nizove ispod njih
  • Izmena elementa kroz isečak menja elemente niza
package main

import "fmt"

func main() {
  names := [4]string{
    "John",
    "Paul",
    "George",
    "Ringo",
  }
  fmt.Println(names)

  a := names[0:2]
  b := names[1:3]
  fmt.Println(a, b)

  b[0] = "XXX"
  fmt.Println(a, b)
  fmt.Println(names)
}
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]

Literali za isečke

[3]bool{true, true, false}
[]bool{true, true, false}
package main

import "fmt"

func main() {
  q := []int{2, 3, 5, 7, 11, 13}
  fmt.Println(q)

  r := []bool{true, false, true, true, false, true}
  fmt.Println(r)

  s := []struct {
    i int
    b bool
  }{
    {2, true},
    {3, false},
    {5, true},
    {7, true},
    {11, false},
    {13, true},
  }
  fmt.Println(s)
}

Podrazumevane granice intervala

  • Kada se kreira isečak niza moguće je izostaviti bilo koju granicu.
  • Podrazumevana donja granica je 0
  • Podrazumevana gornja granica je dužina niza
  • Ekvivalentni izrazi:

    a[0:10]
    a[:10]
    a[0:]
    a[:]
    
package main

import "fmt"

func main() {
  s := []int{2, 3, 5, 7, 11, 13}

  s = s[1:4]
  fmt.Println(s)

  s = s[:2]
  fmt.Println(s)

  s = s[1:]
  fmt.Println(s)
}
[3 5 7]
[3 5]
[5]

Dužina i kapacitet isečka

  • Isečak ima dužinu i kapacitet
  • Dužina je broj elemenata isečka. Kapacitet je broj elemenata počevši od prvog elementa isečka do poslednjeg elementa potpornog niza.
  • dužina i kapacitet isečka s: len(s), cap(s)
  • Dužina isečka se može povećati ponovnim isecanjem (reslicing) u skladu sa kapacitetom
package main

import "fmt"

func main() {
  s := []int{2, 3, 5, 7, 11, 13}
  printSlice(s)

  // Slice the slice to give it zero length.
  s = s[:0]
  printSlice(s)

  // Extend its length.
  s = s[:4]
  printSlice(s)

  // Drop its first two values.
  s = s[2:]
  printSlice(s)
}

func printSlice(s []int) {
  fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]

Prazni (nil) isečci

  • Nula vrednost za isečak je nil
  • Ovakav isečak ima dužinu i kapacitet 0 i nema potporni niz
package main

import "fmt"

func main() {
  var s []int
  fmt.Println(s, len(s), cap(s))
  if s == nil {
    fmt.Println("nil!")
  }
}
[] 0 0
nil!

Kreiranje isečaka sa make

  • make funkcija alocira niz sa nultim vrednostima i vraća njegov isečak

    a := make([]int, 5)  // len(a)=5
    
  • moguće je definisati i kapacitet

     b := make([]int, 0, 5) // len(b)=0, cap(b)=5
    
    b = b[:cap(b)] // len(b)=5, cap(b)=5
    b = b[1:]      // len(b)=4, cap(b)=4
    
package main

import "fmt"

func main() {
  a := make([]int, 5)
  printSlice("a", a)

  b := make([]int, 0, 5)
  printSlice("b", b)

  c := b[:2]
  printSlice("c", c)

  d := c[2:5]
  printSlice("d", d)
}

func printSlice(s string, x []int) {
  fmt.Printf("%s len=%d cap=%d %v\n",
    s, len(x), cap(x), x)
}
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]

Isečak isečaka

  • Isečak može da sadrži bilo koji tip, uključujući i druge isečke
package main

import (
  "fmt"
  "strings"
)

func main() {
  // Create a tic-tac-toe board.
  board := [][]string{
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
  }

  // The players take turns.
  board[0][0] = "X"
  board[2][2] = "O"
  board[1][2] = "X"
  board[1][0] = "O"
  board[0][2] = "X"

  for i := 0; i < len(board); i++ {
    fmt.Printf("%s\n", strings.Join(board[i], " "))
  }
}

Dodavanje na isečak

func append(s []T, vs ...T) []T
  • Prvi parametar je isečak, ostali su vrednosti koje se dodaju
  • Povratna vrednost je novi isečak koji sadrži nove elemente
  • Ako potporni niz nema dovoljan kapacitet alocira se novi
package main

import "fmt"

func main() {
  var s []int
  printSlice(s)

  // append works on nil slices.
  s = append(s, 0)
  printSlice(s)

  // The slice grows as needed.
  s = append(s, 1)
  printSlice(s)

  // We can add more than one element at a time.
  s = append(s, 2, 3, 4)
  printSlice(s)
}

func printSlice(s []int) {
  fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]

range

  • range forma for petlje iterira kroz elemente isečaka ili mape
package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
  for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
  }
}
  • Ukoliko ne koristite indeks pri iteraciji moguće ga je ignorisati upotrebom specijalnog imena _

    for i, _ := range pow
    for _, value := range pow
    
  • Ako vam treba samo indeks možete izostaviti drugu varijablu:

    for i := range pow
    
    package main
    
    import "fmt"
    
    func main() {
      pow := make([]int, 10)
      for i := range pow {
        pow[i] = 1 << uint(i) // == 2**i
      }
      for _, value := range pow {
        fmt.Printf("%d\n", value)
      }
    }
    

Mape

  • Mapiranje ključeva na vrednosti – asocijativni niz
  • Nula vrednost je nil
  • nil mapa nema ključeve niti se ključevi mogu dodati
  • make funkcija vraća inicijalizovanu mapu spremnu za upotrebu
package main

import "fmt"

type Vertex struct {
  Lat, Long float64
}

var m map[string]Vertex

func main() {
  m = make(map[string]Vertex)
  m["Bell Labs"] = Vertex{
    40.68433, -74.39967,
  }
  fmt.Println(m["Bell Labs"])
}

Literali za mape

package main

import "fmt"

type Vertex struct {
  Lat, Long float64
}

var m = map[string]Vertex{
  "Bell Labs": Vertex{
    40.68433, -74.39967,
  },
  "Google": Vertex{
    37.42202, -122.08408,
  },
}

func main() {
  fmt.Println(m)
}
  • Ime tipa literala se može izostaviti:
package main

import "fmt"

type Vertex struct {
  Lat, Long float64
}

var m = map[string]Vertex{
  "Bell Labs": {40.68433, -74.39967},
  "Google":    {37.42202, -122.08408},
}

func main() {
  fmt.Println(m)
}

Izmena vrednosti mapa

  • postavljanje vrednosti
m[key] = elem
  • čitanje vrednosti
elem = m[key]
  • brisanje
delete(m, key)
  • čitanje i provera da li element postoji
elem, ok = m[key]

Ako element postoji ok će imati vrednost true inače false

package main

import "fmt"

func main() {
  m := make(map[string]int)

  m["Answer"] = 42
  fmt.Println("The value:", m["Answer"])

  m["Answer"] = 48
  fmt.Println("The value:", m["Answer"])

  delete(m, "Answer")
  fmt.Println("The value:", m["Answer"])

  v, ok := m["Answer"]
  fmt.Println("The value:", v, "Present?", ok)
}

Funkcije kao vrednosti

  • Funkcije se u Go-u vrednosti takođe.
  • Mogu se čuvati kao varijable, prosleđivati kao parametri funkcije ili dobijati nazad kao povratne vrednosti
package main

import (
  "fmt"
  "math"
)

func compute(fn func(float64, float64) float64) float64 {
  return fn(3, 4)
}

func main() {
  hypot := func(x, y float64) float64 {
    return math.Sqrt(x*x + y*y)
  }
  fmt.Println(hypot(5, 12))

  fmt.Println(compute(hypot))
  fmt.Println(compute(math.Pow))
}
13
5
81

Zatvorenja (Closures)

  • Go funkcije mogu biti zatvorenja u smislu da funkcija može da referencira vrednosti koje se nalaze izvan nje na mestu njenog kreiranja čak i kada se pozove sa drugog mesta. Kažemo da su referencirane vrednosti "vezane" za funkciju (bounded)
package main

import "fmt"

func adder() func(int) int {
  sum := 0
  return func(x int) int {
    sum += x
    return sum
  }
}

func main() {
  pos, neg := adder(), adder()
  for i := 0; i < 10; i++ {
    fmt.Println(
      pos(i),
      neg(-2*i),
    )
  }
}

Metode i interfejsi

Metode i interfejsi

  • Go nema klase ali se mogu definisati metode nad tipovima
  • Metoda je funkcija koja ima specijalni receiver parametar
package main

import (
  "fmt"
  "math"
)

type Vertex struct {
  X, Y float64
}

func (v *Vertex) Abs() float64 {
  return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
  v := Vertex{3, 4}
  fmt.Println(v.Abs())
}

Metode su funkcije

  • Metode se u osnovi ponašaju kao obične funkcije
package main

import (
  "fmt"
  "math"
)

type Vertex struct {
  X, Y float64
}

func Abs(v Vertex) float64 {
  return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
  v := Vertex{3, 4}
  fmt.Println(Abs(v))
}
  • Metode se ne moraju definisati samo nad strukturama. Na primer:
package main

import (
  "fmt"
  "math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
  if f < 0 {
    return float64(-f)
  }
  return float64(f)
}

func main() {
  f := MyFloat(-math.Sqrt2)
  fmt.Println(f.Abs())
}

Pokazivački prijemnici (Pointer receivers)

  • "Prijemnik" može biti pokazivačkog tipa
  • U tom slučaju metoda može ažurirati vrednost prijemnika
  • S obzirom da je ovo često potrebno, a i performanse su bolje jer nema kopiranja, pokazivački prijemnici su češći

    package main
    
    import (
      "fmt"
      "math"
    )
    
    type Vertex struct {
      X, Y float64
    }
    func (v Vertex) Abs() float64 {
      return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    func (v *Vertex) Scale(f float64) {
      v.X = v.X * f
      v.Y = v.Y * f
    }
    
    func main() {
      v := Vertex{3, 4}
      v.Scale(10)
      fmt.Println(v.Abs())
    }
    

Pokazivači i funkcije

package main

import (
  "fmt"
  "math"
)

type Vertex struct {
  X, Y float64
}

func Abs(v Vertex) float64 {
  return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func Scale(v *Vertex, f float64) {    // ako uklonimo `*`?
  v.X = v.X * f
  v.Y = v.Y * f
}

func main() {
  v := Vertex{3, 4}
  Scale(&v, 10)
  fmt.Println(Abs(v))
}

Metode i indirekcija pokazivača

  • Funkcija sa pokazivačkim arugmentom mora primiti pokazivač

    var v Vertex
    ScaleFunc(v, 5)  // Compile error!
    ScaleFunc(&v, 5) // OK
    
  • ali metode mogu primiti bilo vrednosti bilo pokazivače kao prijemnike

    var v Vertex
    v.Scale(5)  // OK
    p := &v
    p.Scale(10) // OK
    
  • Za iskaz v.Scale(5) Go će pozvati metodu sa pokazivačkim prijemnikom iako v u ovom slučaju može biti vrednost a ne pokazivač
  • Odnosno Go će automatski interpretirati iskaz kao (&v).Scale(5) pošto Scale metoda ima pokazivački prijemnik
package main

import "fmt"

type Vertex struct {
  X, Y float64
}

func (v *Vertex) Scale(f float64) {
  v.X = v.X * f
  v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f float64) {
  v.X = v.X * f
  v.Y = v.Y * f
}

func main() {
  v := Vertex{3, 4}
  v.Scale(2)
  ScaleFunc(&v, 10)

  p := &Vertex{4, 3}
  p.Scale(3)
  ScaleFunc(p, 8)

  fmt.Println(v, p)
}
  • Isto imamo i u suprotnom smeru
  • Funkcije koje primaju vrednosne arugmente ne mogu prihvatiti pokazivač
var v Vertex
fmt.Println(AbsFunc(v))  // OK
fmt.Println(AbsFunc(&v)) // Compile error!
  • ali metode sa vrednosnim prijemnikom mogu prihvatiti bilo vrednosti bilo pokazivače
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK

Go će u ovom slučaju interpretirati p.Abs() kao (*p).Abs()

package main

import (
  "fmt"
  "math"
)

type Vertex struct {
  X, Y float64
}

func (v Vertex) Abs() float64 {
  return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func AbsFunc(v Vertex) float64 {
  return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
  v := Vertex{3, 4}
  fmt.Println(v.Abs())
  fmt.Println(AbsFunc(v))

  p := &Vertex{4, 3}
  fmt.Println(p.Abs())
  fmt.Println(AbsFunc(*p))
}

Kada koristiti vrednosni a kada pokazivački prijemnik?

Dva razloga za upotrebu pokazivačkog prijemnika:

  1. Kada metoda treba da modifikuje prijemnik
  2. Izbegavanje kopiranja prijemnika
package main

import (
  "fmt"
  "math"
)

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)
}

func main() {
  v := &Vertex{3, 4}
  fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
  v.Scale(5)
  fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}

Interfejsi

  • Interfejs tip je definisan skupom signatura metoda
  • Kažemo da tip implementira interfejs ako implementira skup metoda koje interfejs definiše (implicitno)
  • Varijabla tipa interfejsa može sadžati bilo koju vrednost koja implementira dati skup metoda interfejsa
type Abser interface {
  Abs() float64
}

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
  a = v                // In this line, v is a Vertex (not *Vertex)
  fmt.Println(a.Abs()) // and does NOT implement Abser.
}

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)
}
./compile7.go:22:4: cannot use v (type Vertex) as type Abser in assignment:
Vertex does not implement Abser (Abs method has pointer receiver)

Interfejsi se implementiraju implicitno

  • Tip implementira interfejs tako što implementira metode interfejsa
  • Ne postoji implements ključna reč
  • Implicitni interfejsi razdvajaju definiciju od implementacije
package main

import "fmt"

type I interface {
  M()
}

type T struct {
  S string
}

// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
  fmt.Println(t.S)
}

func main() {
  var i I = T{"hello"}
  i.M()
}

Interfejs vrednosti

  • Interfejs vrednosti možemo zamisliti kao uređeni par vrednosti i konkretnog tipa:

    (value, type)
    
  • Interfejs vrednost čuva konretnu vrednost tipa koji implementira dati interfejs
  • Poziv metode interfejsa izvršava poziv metode tipa vrednosti nad sadržanom vrednošću
type I interface {
  M()
}

type T struct {
  S string
}
func (t *T) M() {
  fmt.Println(t.S)
}

type F float64
func (f F) M() {
  fmt.Println(f)
}

func main() {
  var i I
  i = &T{"Hello"}
  describe(i)
  i.M()
  i = F(math.Pi)
  describe(i)
  i.M()
}

func describe(i I) {
  fmt.Printf("(%v, %T)\n", i, i)
}
(&{Hello}, *main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793

interfejs vrednosti sa nil sadržanom vrednošću

  • Ako je vrednost sadržana u interfejsu nil metoda se poziva sa nil prijemnikom
  • Iako interfejs sadrži nil vrednost on nije nil interfejs
  • U Go-u je normalno da se metoda pozove sa nil prijemnikom
type I interface {
  M()
}

type T struct {
  S string
}

func (t *T) M() {
  if t == nil {
    fmt.Println("<nil>")
    return
  }
  fmt.Println(t.S)
}

func main() {
  var i I
  var t *T
  i = t
  describe(i)
  i.M()
  i = &T{"hello"}
  describe(i)
  i.M()
}

func describe(i I) {
  fmt.Printf("(%v, %T)\n", i, i)
}
(<nil>, *main.T)
<nil>
(&{hello}, *main.T)
hello

nil interfejs vrednost

  • nil interfejs ne sadrži ni vrednost ni tip
  • Pozivanje metode nad nil interfejsom je run-time greška jer ne znamo tip koji bi odredio metodu koju treba pozvati
package main

import "fmt"

type I interface {
  M()
}

func main() {
  var i I
  describe(i)
  i.M()
}

func describe(i I) {
  fmt.Printf("(%v, %T)\n", i, i)
}
(<nil>, <nil>)
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x488ac1]

goroutine 1 [running]:
main.main()
  /tmp/compile12.go:12 +0x91

Prazan interfejs

  • Tip interfejsa bez ijedne metode je poznat kao "prazan interfejs" (empty interface)
interface{}
  • Prazan interfejs može sadržati vrednost bilo kog tipa (svaki tip implementira 0 metoda)
  • Koriste se za čuvanje vrednosti nepoznatog tipa
  • Npr. fmt.Print prihvata proizvoljan broj argumenata tipa interface{}
package main

import "fmt"

func main() {
  var i interface{}
  describe(i)

  i = 42
  describe(i)

  i = "hello"
  describe(i)
}

func describe(i interface{}) {
  fmt.Printf("(%v, %T)\n", i, i)
}
(<nil>, <nil>)
(42, int)
(hello, string)

Type assertions

  • Omogućava pristup tipu vrednosti sadržane u interfejsu

    t := i.(T)
    
  • Prethodni iskaz tvrdi da i sadrži vrednost tipa T i dodeljuje tu vrednost varijabli t tipa T
  • Ukoliko i ne sadrži vrednost tipa T program se prekida uz panic grešku
  • Za proveru da li interfejs sadrži vrednost određenog tipa koristi se comma-ok iskaz

    t, ok := i.(T)
    
package main

import "fmt"

func main() {
  var i interface{} = "hello"

  s := i.(string)
  fmt.Println(s)

  s, ok := i.(string)
  fmt.Println(s, ok)

  f, ok := i.(float64)
  fmt.Println(f, ok)

  f = i.(float64) // panic
  fmt.Println(f)
}
hello
hello true
0 false
panic: interface conversion: interface {} is string, not float64

Type switches

  • Omogućava više type assertions u nizu
  • Slično običnom switch iskazu ali svaki case navodi tip a ne vrednost
switch v := i.(type) {
case T:
    // here v has type T
case S:
    // here v has type S
default:
    // no match; here v has the same type as i
}
  • Vrednost u zaglavlju se navodi slično kao type assertion ali se umesto tipa piše ključna reč type
package main

import "fmt"

func do(i interface{}) {
  switch v := i.(type) {
  case int:
    fmt.Printf("Twice %v is %v\n", v, v*2)
  case string:
    fmt.Printf("%q is %v bytes long\n", v, len(v))
  default:
    fmt.Printf("I don't know about type %T!\n", v)
  }
}

func main() {
  do(21)
  do("hello")
  do(true)
}

Stringers

  • Jedan od najviše korišćenih interfajsa je Stringer definisan u fmt paketu
type Stringer interface {
    String() string
}
  • Stringer je tip koji može da se transformiše u string
  • fmt paket (i mnogi drugi) zahtevaju ovaj interfejs kada štampaju vrednosti
package main

import "fmt"

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}
  z := Person{"Zaphod Beeblebrox", 9001}
  fmt.Println(a, z)
}
Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)

Greške

  • Greške se opisuju sa error vrednostima
  • error tip je ugrađeni interfejs

    type error interface {
        Error() string
    }
    
  • Funkcije često vraćaju error vrednosti i pozivaoci bi trebali da proveravaju da li je greška nil

    i, err := strconv.Atoi("42")
    if err != nil {
        fmt.Printf("couldn't convert number: %v\n", err)
        return
    }
    fmt.Println("Converted integer:", i)
    
package main

import (
  "fmt"
  "time"
)

type MyError struct {
  When time.Time
  What string
}

func (e *MyError) Error() string {
  return fmt.Sprintf("at %v, %s",
    e.When, e.What)
}

func run() error {
  return &MyError{
    time.Now(),
    "it didn't work",
  }
}

func main() {
  if err := run(); err != nil {
    fmt.Println(err)
  }
}
at 2019-05-27 15:04:16.262931451 +0200 CEST m=+0.000213564, it didn't work

Readers

  • io paket definiše io.Reader interfejs

    func (T) Read(b []byte) (n int, err error)
    
  • Ovaj interfejs implementiraju mnogi tipovi: fajlovi, mrežne konekcije, kompresori itd.
  • Read metoda puni zadati byte isečak sa podacima i vraća broj bajtova koji su upisani i grešku ukoliko je ima. Specijalna greška io.EOF označava da se došlo do kraja.
  • Sledeći kod kreira strings.Reader i vrši čitanje po 8 bajtova odjednom
package main

import (
  "fmt"
  "io"
  "strings"
)

func main() {
  r := strings.NewReader("Hello, Reader!")

  b := make([]byte, 8)
  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
    }
  }
}
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""

Konkurencija

Go rutine Goroutines

  • Go rutina je nit (thread) kreirana i upravljana od strane Go izvršnog okruženja (runtime)

    go f(x, y, z)
    

    startuje novu Go rutinu koja izvršava

    f(x, y, z)
    
  • Evaluacija f, x, y i z se dešava u tekućoj Go rutini dok se izvršavanje funkcije f odvija u novoj Go rutini
  • Izvršavanje svih Go rutina odvija se u istom adresnom prostoru tako da pristup deljenoj memoriji mora biti sinhronizovan. Paket sync definiše korisne primitive za sinhronizaciju iako u Go-u ovo često nije neophodno.
package main

import (
  "fmt"
  "time"
)

func say(s string) {
  for i := 0; i < 5; i++ {
    time.Sleep(100 * time.Millisecond)
    fmt.Println(s)
  }
}

func main() {
  go say("world")
  say("hello")
}
hello
world
world
hello
world
...

Kanali (Channels)

  • Tipizirane veze preko kojih se mogu slati vrednosti upotrebom operatora kanala (channel operator) - <-

    ch <- v    // Send v to channel ch.
    v := <-ch  // Receive from ch, and assign value to v.
    
  • Tok podataka je u smeru strelice
  • Kanali se kreiraju dinamički upotrebom make funkcije

    ch := make(chan int)
    
  • Podrazumevano čitanje i pisanje u kanal je blokirajuće dok druga strana ne obavi suprotnu operaciju. Ovo omogućava implicitnu sinhronizaciju Go rutina.
  • Sledeći primer vrši sumiranje brojeva isečka tako što se posao deli između dve Go rutine
package main

import "fmt"

func sum(s []int, c chan int) {
  sum := 0
  for _, v := range s {
    sum += v
  }
  c <- sum // send sum to c
}

func main() {
  s := []int{7, 2, 8, -9, 4, 0}

  c := make(chan int)
  go sum(s[:len(s)/2], c)
  go sum(s[len(s)/2:], c)
  x, y := <-c, <-c // receive from c

  fmt.Println(x, y, x+y)
}
-5 17 12

Baferovani kanali

  • Kanali mogu biti baferovani i u tom slučaju pisanje se blokira samo ukoliko je kanal pun. Čitanje se blokira samo ukoliko je kanal prazan.
  • Kreiranje bafera omogućeno je drugim parametrom make funkcije

    ch := make(chan int, 100)
    
package main

import "fmt"

func main() {
  ch := make(chan int, 2)
  ch <- 1
  ch <- 2
  ch <- 3   // overflow blocks current Go rutine!
  fmt.Println(<-ch)
  fmt.Println(<-ch)
}
fatal error: all goroutines are asleep - deadlock!

Zatvaranje kanala

  • Go rutina koja šalje podatke može zatvoriti kanal close funkcijom da signalizira da neće više slati podatke
  • Go rutina koja prima podatke koristi comma-ok idiom da testira da li je kanal zatvoren

    v, ok := <-ch
    

    ok će biti false ako više nema podataka i kanal je zatvoren

  • Petlja for i := range c prihvata podatke sa kanala c sve dok se ne kanal ne zatvori
  • Napomena: samo pošiljalac zatvara kanal, nikada rutina koja prihvata podatke. Slanje na zatvoren kanal izaziva panic.
  • Napomena 2: Kanale nije neophodno zatvoriti. To se radi samo ukoliko je potrebno signalizirati prijemniku da nema više podataka, npr. kada se koristi range petlja
package main

import (
  "fmt"
)

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)
  }
}

select

  • select iskaz omogućava Go rutini da čeka na više komunikacionih operacija
  • select blokira dok jedna od grana nije u mogućnosti da se izvrši. Ukoliko je više u mogućnosti da se izvrši izbor se vrši slučajno
func fibonacci(c, quit chan int) {
  x, y := 0, 1
  for {
    select {
    case c <- x:
      x, y = y, x+y
    case <-quit:
      fmt.Println("quit")
      return
    }
  }
}

func main() {
  c := make(chan int)
  quit := make(chan int)
  go func() {
    for i := 0; i < 10; i++ {
      fmt.Println(<-c)
    }
    quit <- 0
  }()
  fibonacci(c, quit)
}

select/default

  • default grana select iskaza se izvršava ukoliko nijedna druga nije spremna
  • Koristite default da čitate ili pišete bez blokiranja
select {
case i := <-c:
    // use i
default:
    // receiving from c would block
}
package main

import (
  "fmt"
  "time"
)

func main() {
  tick := time.Tick(100 * time.Millisecond)
  boom := time.After(500 * time.Millisecond)
  for {
    select {
    case <-tick:
      fmt.Println("tick.")
    case <-boom:
      fmt.Println("BOOM!")
      return
    default:
      fmt.Println("    .")
      time.Sleep(50 * time.Millisecond)
    }
  }
}
    .
    .
tick.
  ...
BOOM!

sync.Mutex

  • Ukoliko nam je potreban isključiv pristup deljenim podacima
  • Kod koji pristupa pišemo između Lock/Unlock poziva
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
  v   map[string]int
  mux sync.Mutex
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
  c.mux.Lock()
  // Lock so only one goroutine at a time can access the map c.v.
  c.v[key]++
  c.mux.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
  c.mux.Lock()
  // Lock so only one goroutine at a time can access the map c.v.
  defer c.mux.Unlock()
  return c.v[key]
}

func main() {
  c := SafeCounter{v: make(map[string]int)}
  for i := 0; i < 1000; i++ {
    go c.Inc("somekey")
  }
  time.Sleep(time.Second)
  fmt.Println(c.Value("somekey"))
}

Reference