Hlavní navigace

Programovací jazyk Go: dokončení popisu vlastností samotného jazyka

Pavel Tišnovský

Dnes dokončíme popis vlastního jazyka. Nejprve se budeme zabývat zbylými operátory, ukážeme si způsob využití konstrukce select-case pro komunikaci mezi gorutinami a popíšeme si deklaraci konstant i význam identifikátoru iota.

Doba čtení: 35 minut

Sdílet

11. Blokující zápis do kanálu

12. Kombinace čtení a zápisu v konstrukci select-case

13. Deklarace konstant v programovacím jazyku Go

14. Demonstrační příklad: konstanty různých typů, blok s konstantami

15. Automatické generování celočíselné řady pomocí klíčového slova iota

16. Praktické použití klíčového slova iota

17. Funkce s variabilním počtem parametrů v programovacím jazyku Go

18. Základní informace o adresářové struktuře při práci se složitějšími projekty

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Dokončení popisu operátorů

V úvodní části dnešního článku navážeme na článek předchozí, v němž jsme se kromě dalších věcí zabývali i operátory, které je možné v programovacím jazyku Go použít. Již jsme si popsali standardní aritmetické operátory, relační operátory (a jejich omezení) a taktéž bitové posuny. Musíme si ovšem popsat i bitové operátory a mj. i operace určené pro zvýšení či snížení číselné hodnoty o jedničku (což ovšem nejsou plnohodnotné operátory, jak ostatně uvidíme v dalším textu).

Jen pro připomenutí si znovu ukažme tabulku všech operátorů, které je možné použít v programovacím jazyce Go:

aritmetické + * / %  
aritmetické s přiřazením += -= *= /= %=  
logické && || !      
posuny a bitové operace << >> & | ^ &^
posuny a bitové operace s přiřazením <<= >>= &= |= ^= &^=
relační == != < <= > >=
operace s adresami * &        
unární operátory + ^      
další operátory <- :=        

2. Bitové operátory

V této kapitole se budeme zabývat bitovými operacemi, které jsou v programovacím jazyce Go realizovány s využitím pětice operátorů. Tyto operátory jsou vypsány v následující tabulce:

Operátor Kombinace s přiřazením Význam
^   negace bit po bitu (podobně jako operátor ~ v C)
     
& &= logický součin prováděný bit po bitu
| |= logický součet prováděný bit po bitu
^ ^= logická nonekvivalence prováděná bit po bitu
&^ &^= maskování bitů vybraných zadanou maskou (operace AND NOT)

První operátor je unární, tj. aplikuje se pouze na jediný operand, který je zapsán za operátor, podobně jako u dalších unárních operátorů (operace ++ a – jsou výjimkou, ale v tomto případě se nejedná o plnohodnotné operátory). Tento operátor slouží pro negaci všech bitů v celočíselné hodnotě. Operátor lze aplikovat jak na hodnoty bez znaménka (unsigned), tak i na hodnoty se znaménkem (signed), takže můžeme například napsat:

var b uint8 = 0
 
var x uint = 1
var y uint = 2
var z int = 1
var w int = 2
 
fmt.Printf("%x\n", ^b)
fmt.Printf("%x\n", ^x)
fmt.Printf("%x\n", ^y)
fmt.Printf("%x\n", ^z)
fmt.Printf("%x\n", ^w)

S výsledky:

ff
fffffffffffffffe
fffffffffffffffd
-2
-3
Poznámka: v programovacích jazycích odvozených od céčka se tento operátor většinou nezapisuje znakem ^, ale s využitím znaku ~.

Další trojice operátorů zapisovaných znaky &, | a ^ patří mezi binární operátory, které jsou aplikovány na dvojici operandů. Opět se může jednat jak o hodnoty se znaménkem, tak i o hodnoty bez znaménka. Význam těchto operátorů odpovídá podobně zapisovaným operátorům v C, C++ či Javě (viz též demonstrační příklad uvedený v závěru kapitoly). Zajímavější a ve vyšších programovacích jazycích pravděpodobně i unikátní je však poslední z bitových operátorů, který se zapisuje dvojicí znaků &^. Tento operátor provádí logický součin bit po bitu, ovšem všechny bity druhého operandu jsou nejdříve znegovány. Operátor &^ se tedy používá pro maskování, kdy například budeme potřebovat vynulovat bit 1 pomocí operace x &^ 1 atd.:

var b uint8 = 0xff
 
fmt.Printf("%x\n", b)
fmt.Printf("%x\n", b &^ 1)
fmt.Printf("%x\n", b &^ 2)
fmt.Printf("%x\n", b &^ 0x0f)

S výsledky:

ff
fe
fd
f0

Všechny binární bitové operátory jsou představeny v dnešním prvním demonstračním příkladu, v němž jsou aplikovány na dvě celočíselné proměnné:

package main
 
import "fmt"
 
func main() {
        x := 1
        y := 0xfe
 
        fmt.Printf("%x & %x == %x\n", x, y, x&y)
        fmt.Printf("%x &^ %x == %x\n", x, y, x&^y)
        fmt.Printf("%x | %x == %x\n", x, y, x|y)
        fmt.Printf("%x ^ %x == %x\n", x, y, x^y)
 
        x ^= y
        fmt.Printf("new x = %x\n", x)
 
        x |= y
        fmt.Printf("new x = %x\n", x)
 
        x ^= y
        fmt.Printf("new x = %x\n", x)
 
        x &^= 0x01
        fmt.Printf("new x = %x\n", x)
 
        fmt.Println()
 
        x = 1
        y = 2
 
        fmt.Printf("%x & %x == %x\n", x, y, x&y)
        fmt.Printf("%x &^ %x == %x\n", x, y, x&^y)
        fmt.Printf("%x | %x == %x\n", x, y, x|y)
        fmt.Printf("%x ^ %x == %x\n", x, y, x^y)
 
}
Poznámka: povšimněte si, že pokud se bitové operátory použijí ve své základní formě (bez přiřazení), zapisují se „nalepené“ na operandy – nepoužívají se zde oddělovací mezery (viz příkaz go fmt).

Po spuštění tohoto příkladu získáme tyto výsledky:

1 & fe == 0
1 &^ fe == 1
1 | fe == ff
1 ^ fe == ff
new x = ff
new x = ff
new x = 1
new x = 0
 
1 & 2 == 0
1 &^ 2 == 1
1 | 2 == 3
1 ^ 2 == 3

3. Logické (Booleovské) operátory

Pro úplnost si ještě zopakujme, že v programovacím jazyku Go nalezneme tři operátory, které jsou aplikovatelné na logické (pravdivostní) hodnoty true a false. Jedná se o logickou negaci, logický součin a logický součet. Tyto tři operátory se zapisují následujícím způsobem (u binárních operátorů existuje i možnost jejich kombinace s přiřazením):

Operátor Kombinace s přiřazením Význam
!   negace pravdivostní hodnoty
     
&& &&= logický součin dvou pravdivostních hodnot
|| ||= logický součet dvou pravdivostních hodnot

Opět se podívejme na jednoduchý demonstrační příklad, v němž jsou všechny tři výše zmíněné operátory použity:

package main
 
import "fmt"
 
func main() {
        x := true
        y := false
 
        fmt.Printf("!%v == %v\n", x, !x)
        fmt.Printf("!%v == %v\n", y, !y)
 
        fmt.Printf("%v && %v == %v\n", x, y, x && y)
        fmt.Printf("%v || %v == %v\n", x, y, x || y)
 
        fmt.Printf("%v && %v || %v && %v == %v\n", x, y, true, false, x && y || x && false)
        fmt.Printf("%v && %v || %v && %v == %v\n", x, y, true, false, x && y || x && true)
}

Po překladu a spuštění tohoto příkladu získáme tyto výsledky:

!true == false
!false == true
true && false == false
true || false == true
true && false || true && false == false
true && false || true && false == true
Poznámka: povšimněte si, že funkce fmt.Printf podporuje přímý tisk pravdivostních hodnot, takže je nemusíme převádět na celočíselné hodnoty.

V Go nelze použít varianty & a | s nezkráceným vyhodnocením výrazu. Operátory && a || vždy používají zkrácené vyhodnocování (short circuit), což konkrétně znamená, že pokud je po vyhodnocení prvního operandu zřejmé, jaký bude výsledek operace, druhý operand se nevyhodnocuje. To má význam v případě, že se při vyhodnocování volají funkce, popř. pokud se například dělí nulou atd.:

package main
 
import "fmt"
 
func f1() bool {
        println("f1")
        return true
}
 
func f2() bool {
        println("f2")
        return false
}
 
func f3() bool {
        println("f2")
        return false
}
 
func main() {
        fmt.Printf("short circuit &&: %v\n", f1() && f2())
        fmt.Printf("short circuit ||: %v\n", f1() || f2())
        fmt.Printf("short circuit &&: %v\n", f2() && f3())
        fmt.Printf("short circuit ||: %v\n", f2() || f3())
}

Při pohledu na výsledky předchozího demonstračního příkladu se skutečně můžeme přesvědčit, že se druhá funkce ve výrazu v některých případech vůbec nevolá:

f1
f2
short circuit &&: false
f1
short circuit ||: true
f2
short circuit &&: false
f2
f2
short circuit ||: false

4. Logické operátory a typové kontroly jazyka Go

Žádné další operace nejsou s pravdivostními (logickými) hodnotami dovoleny! To například znamená, že následující demonstrační příklad nebude možné přeložit a pochopitelně ani spustit:

package main
 
import "fmt"
 
func main() {
        x := true
        y := false
 
        fmt.Printf("^%v == %v\n", x, ^x)
        fmt.Printf("^%v == %v\n", y, ^y)
 
        fmt.Printf("%v & %v == %v\n", x, y, x & y)
        fmt.Printf("%v | %v == %v\n", x, y, x | y)
        fmt.Printf("%v + %v == %v\n", x, y, x + y)
        fmt.Printf("%v - %v == %v\n", x, y, x - y)
}

Při pokusu o překlad vypíše překladač tato chybová hlášení:

./02B_boolean_operators_improper_usage.go:16:31: invalid operation: ^ bool
./02B_boolean_operators_improper_usage.go:17:31: invalid operation: ^ bool
./02B_boolean_operators_improper_usage.go:19:40: invalid operation: x & y (operator & not defined on bool)
./02B_boolean_operators_improper_usage.go:20:40: invalid operation: x | y (operator | not defined on bool)
./02B_boolean_operators_improper_usage.go:21:40: invalid operation: x + y (operator + not defined on bool)
./02B_boolean_operators_improper_usage.go:22:40: invalid operation: x - y (operator - not defined on bool)

Samozřejmě to platí i naopak – logické operátory nejsou aplikovatelné na celá čísla, čísla s plovoucí řádovou čárkou apod. Ani tento program tedy není v žádném případě korektní:

package main
 
import "fmt"
 
func main() {
        x := 1
        y := 2
 
        fmt.Printf("!%v == %v\n", x, !x)
        fmt.Printf("!%v == %v\n", y, !y)
 
        fmt.Printf("%v && %v == %v\n", x, y, x && y)
        fmt.Printf("%v || %v == %v\n", x, y, x || y)
&nbsp;
        fmt.Printf("%v && %v || %v && %v == %v\n", x, y, true, false, x && y || x && false)
        fmt.Printf("%v && %v || %v && %v == %v\n", x, y, true, false, x && y || x && true)
}

Takto krátký program obsahuje až překvapivě mnoho chyb:

./02C_boolean_operators_improper_usage.go:16:31: invalid operation: ! int
./02C_boolean_operators_improper_usage.go:17:31: invalid operation: ! int
./02C_boolean_operators_improper_usage.go:19:41: invalid operation: x && y (operator && not defined on int)
./02C_boolean_operators_improper_usage.go:20:41: invalid operation: x || y (operator || not defined on int)
./02C_boolean_operators_improper_usage.go:22:66: invalid operation: x && y (operator && not defined on int)
./02C_boolean_operators_improper_usage.go:22:76: cannot convert false (type untyped bool) to type int
./02C_boolean_operators_improper_usage.go:22:76: invalid operation: x && false (mismatched types int and bool)
./02C_boolean_operators_improper_usage.go:23:66: invalid operation: x && y (operator && not defined on int)
./02C_boolean_operators_improper_usage.go:23:76: cannot convert true (type untyped bool) to type int
./02C_boolean_operators_improper_usage.go:23:76: invalid operation: x && true (mismatched types int and bool)
Poznámka: již minule jsme si řekli, že pro logické hodnoty true a false není definováno uspořádání (jinými slovy, není možné rozhodnout, která z těchto hodnot je větší nebo menší). To znamená, že ze šesti dostupných relačních operací je možné pro logické hodnoty použít pouze porovnání na rovnost == a nerovnost !=.

5. Operace pro zvýšení a snížení hodnoty o jedničku

Programovací jazyk Go umožňuje jednoduché zvýšení či snížení nějaké číselné hodnoty (celého čísla, čísla s plovoucí řádovou čárkou, dokonce i čísla komplexního). Tyto operace jsou zapisovány znaky ++ a --, které musíme vždy zapsat ZA operand. Na rozdíl od prakticky všech céčkovských programovacích jazyků ovšem není povoleno použít tyto znaky před operandem. Navíc – což je ještě více omezující podmínka – musí být tyto operace zapsány jako příkaz (statement), nikoli jako součást složitějšího výrazu (expression). Z těchto důvodů se operace ++ a – většinou ani neuvádí v seznamu operátorů, protože tvoří samostatnou syntaktickou kategorii. Nejjednodušší příklad použití operací ++ a – může vypadat následovně:

x := 1
x++

popř. pochopitelně můžeme celočíselnou hodnotu o jedničku snížit:

x := 1
x--

Zajímavé a užitečné je chování operací ++ a – ve chvíli, kdy máme k dispozici ukazatel na proměnnou (resp. obecněji ukazatel na hodnotu). V takovém případě můžeme napsat:

x := 1
px := &x
*px++

Na posledním řádku se zvýší hodnota proměnné x, protože jazyk Go má odlišnou tabulku priorit operátorů. V programovacím jazyku C má ovšem stejně zapsaný příkaz odlišný význam, protože nejprve přečte hodnotu z adresy obsažené v px a poté zvýší hodnotu px (tedy adresy) o jedničku (to bude v našem případě potenciálně nekorektní operace, protože v px je adresa proměnné a nikoli například adresa prvku pole).

#include <stdio.h>
 
int main(void) {
    int x = 1;
    int *px = &x;
    *px++;
    printf("%d\n", x);
    return 0;
}

Můžeme si vyzkoušet, že předchozí céčkovský příklad se skutečně chová odlišně:

$ gcc px.c
$ ./a.out
1

Stejné chování, jakého jsme dosáhli v Go, musíme naprogramovat s využitím závorek pro změnu priorit operátorů:

#include <stdio.h>
 
int main(void) {
    int x = 1;
    int *px = &x;
    (*px)++;
    printf("%d\n", x);
    return 0;
}

Otestování chování upraveného céčkovského programu:

$ gcc px.c
$ ./a.out
2

Vraťme se nyní zpět k popisu programovacího jazyka Go. Operace ++ a – je možné použít i pro hodnoty typu complex64 a complex128. V tomto případě se operace týkají reálné složky komplexních čísel, imaginární složka zůstane nezměněna:

z := 1 + 2i
z++

V dalším demonstračním příkladu, jehož úplný zdrojový kód naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article07/03_in­c_dec.go, jsou ukázány některé možnosti korektního použití operací ++ a --:

package main
 
import "fmt"
 
func main() {
        x := 1
        fmt.Printf("x = %d\n", x)
 
        x++
        fmt.Printf("x = %d\n", x)
 
        x--
        fmt.Printf("x = %d\n", x)
 
        px := &x
        fmt.Printf("x = %d\n", *px)
 
        *px++
        fmt.Printf("x = %d\n", *px)
 
        *px--
        fmt.Printf("x = %d\n", *px)
 
        y := 3.14
        fmt.Printf("y = %f\n", y)
        y++
        fmt.Printf("y = %f\n", y)
 
        z := 1 + 2i
        fmt.Printf("z = %f\n", z)
        z++
        fmt.Printf("z = %f\n", z)
}

Po překladu a spuštění tohoto příkladu by se na standardním výstupu měly objevit tyto řádky:

x = 1
x = 2
x = 1
x = 1
x = 2
x = 1
y = 3.140000
y = 4.140000
z = (1.000000+2.000000i)
z = (2.000000+2.000000i)

6. Nepodporovaná použití operátorů ++ a --

V předchozí kapitole jsme si řekli, že možnosti použití operací ++ a – jsou v programovacím jazyku Go omezenější, než je tomu v klasických jazycích odvozených od céčka. V první řadě není dovoleno zapsat tyto operace před operandy. Na toto nekorektní použití nás upozorní překladač:

package main
 
import "fmt"
 
func main() {
        x := 1
        fmt.Printf("x = %d\n", x)
 
        ++x
        fmt.Printf("x = %d\n", x)
 
        --x
        fmt.Printf("x = %d\n", x)
}

Při pokusu o překlad tohoto demonstračního příkladu se vypíše chyba u první operace ++x

./04_inc_dec_bad_usage.go:16:2: syntax error: unexpected ++, expecting }

Dále je nutné ++ a – použít jen v samostatném příkazu, nikoli ve výrazu. Ani následující příklad tedy není pro překladač programovacího jazyka Go korektní:

package main
 
import "fmt"
 
func main() {
        x := 1
        fmt.Printf("x = %d\n", x)
 
        fmt.Printf("x = %d\n", x++)
 
        fmt.Printf("x = %d\n", x--)
}

Překladač nyní vypíše chyby u obou operací ++ i --:

./05_inc_dec_bad_usage.go:16:26: syntax error: unexpected ++, expecting comma or )
./05_inc_dec_bad_usage.go:18:26: syntax error: unexpected --, expecting comma or )

Naproti tomu v počítané variantě programové smyčky for operace ++ a – použít můžeme, protože se na příslušném místě očekává iterační příkaz:

package main
 
func main() {
        var i int
        for i = 0; i < 10; i++ {
                println(i)
        }
        println()
        println(i)
}

7. Klíčové slovo select: posílání zpráv přes kanály

Jedno z posledních klíčových slov programovacího jazyka Go, s nímž jsme se doposud nesetkali, je slovo select. Toto klíčové slovo se používá společně s větvemi case a popř. i s větví default pro vytvoření programové konstrukce určené pro příjem dat popř. i pro vysílání dat do gorutin s využitím kanálů. V některých aplikacích totiž nemůžeme čekat na data posílaná do jediného kanálů tak, jak je to naznačeno v následujícím kódu:

func main() {
        channel := make(chan int)
 
        go message(1, channel)
 
        code, status := <-channel
 
        fmt.Printf("received code: %d and status: %t\n", code, status)
        fmt.Println("main end")
}

Namísto toho je někdy zapotřebí zapsat zhruba následující kód:

func main() {
        channel1 := make(chan int)
        channel2 := make(chan int)
 
        go message(1, channel1)
        go message(2, channel2)
        go message(3, channel3)
 
        code, status := <-channel1
        NEBO
        code, status := <-channel2
        NEBO
        code, status := <-channel3
 
        fmt.Printf("received code: %d and status: %t\n", code, status)
        fmt.Println("main end")
}

A právě zvýrazněná část kódu je zapisována s využitím slova select.

Toto slovo se ovšem může použít i v opačném významu, tj. při posílání dat, tedy přibližně v tomto významu:

func main() {
        channel1 := make(chan int)
        channel2 := make(chan int)
 
        go message(1, channel1)
        go message(2, channel2)
        go message(3, channel3)
 
        channel1 <- data1
        NEBO
        channel2 <- data2
        NEBO
        channel3 <- data3
 
        fmt.Printf("received code: %d and status: %t\n", code, status)
        fmt.Println("main end")
}
Poznámka: opět si dovolím poukázat na to, jak jednoduše (a především bez potenciálních chyb) je možné v Go implementovat paralelní popř. asynchronní kód s využitím základních konstrukcí tohoto jazyka: gorutin (klíčové slovo go), kanálů (operátor ← ) a klíčového slova select.

8. Použití konstrukce select-case pro čekání na data z gorutin

Ukažme si nyní velmi jednoduché použití konstrukce select-case při čekání na data, která mohou být poslána do jednoho ze dvou kanálů ch1 a ch2. V tomto programu jsou nejdříve vytvořeny dva kanály s kapacitou 1 (jediné celé číslo):

ch1 := make(chan int)
ch2 := make(chan int)

Následně jsou spuštěny dvě gorutiny, z nichž první pošle data do prvního kanálu ch1 a druhá do druhého kanálu ch2:

go worker(ch1, 1)
go worker(ch2, 2)

Nakonec V konstrukci select-case počkáme na to, až jsou data dostupná v libovolném z těchto kanálů (teoreticky ani nemusíme jeden z workerů-gorutin spouštět):

select {
case <-ch1:
        fmt.Println("Data z kanálu 1")
case <-ch2:
        fmt.Println("Data z kanálu 2")
}

Ve chvíli, kdy jsou data přijata, je konstrukce select-case ukončena a ihned poté se ukončí i běh celé aplikace.

Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article07/06_se­lect_statement_receive.go:

package main
 
import (
        "fmt"
        "time"
)
 
func worker(channel chan int, worker int) {
        fmt.Printf("Worker %d spuštěn\n", worker)
        time.Sleep(2 * time.Second)
        channel <- 1
        fmt.Printf("Worker %d ukončen\n", worker)
}
 
func main() {
        ch1 := make(chan int)
        ch2 := make(chan int)
 
        go worker(ch1, 1)
        go worker(ch2, 2)
 
        select {
        case <-ch1:
                fmt.Println("Data z kanálu 1")
        case <-ch2:
                fmt.Println("Data z kanálu 2")
        }
}

Po spuštění tohoto příkladu můžeme vidět, že se obě gorutiny skutečně spustily, přičemž se v příkazu select náhodně vybrala druhá větev case, v níž se přečetla data z druhého kanálu, takže se příslušný worker stihl ukončit (což ovšem není nijak zaručené chování):

Worker 2 spuštěn
Worker 1 spuštěn
Worker 2 ukončen
Data z kanálu 2

9. Význam větve default v konstrukci select-case

V některých aplikacích může být výhodné, aby se v případě, že žádný kanál zpracovávaný v konstrukci select-case neobsahuje data, pokračovalo v nějakém dalším výpočtu. K tomuto účelu slouží větev default zavolaná ve chvíli, kdy není možné zpracovat ani jednu z větví case. Příklad použití této nové větve je ukázán na dalším demonstračním příkladu:

package main
 
import (
        "fmt"
        "time"
)
 
func worker(channel chan int, worker int) {
        fmt.Printf("Worker %d spuštěn\n", worker)
        time.Sleep(2 * time.Second)
        channel <- 1
        fmt.Printf("Worker %d ukončen\n", worker)
}
 
func main() {
        ch1 := make(chan int)
        ch2 := make(chan int)
 
        go worker(ch1, 1)
        go worker(ch2, 2)
 
        for true {
                select {
                case <-ch1:
                        fmt.Println("Data z kanálu 1")
                case <-ch2:
                        fmt.Println("Data z kanálu 2")
                default:
                        fmt.Println("Žádná data nejsou k dispozici")
                }
                time.Sleep(1 * time.Second)
        }
}

V příkladu je implementována nekonečná smyčka a v konstrukci select-case je umístěna i větev defalt zavolaná ve chvíli, kdy nejsou k dispozici data v žádném kontrolovaném kanálu. V tomto případě není select-case blokující, jak ostatně můžeme vidět i z výpisů:

Worker 1 spuštěn
Žádná data nejsou k dispozici
Worker 2 spuštěn
Žádná data nejsou k dispozici
Data z kanálu 1
Worker 1 ukončen
Data z kanálu 2
Worker 2 ukončen
Žádná data nejsou k dispozici
Žádná data nejsou k dispozici
Žádná data nejsou k dispozici
...
...
...

10. Použití konstrukce select-case při posílání dat

Programovou konstrukci typu select-case je možné využít nejenom pro příjem dat (s případným čekáním na okamžik, až se data objeví v kanálu), ale i pro posílání dat. Význam je přitom stejný – obě operace mohou být blokující a mnohdy potřebujeme, aby jedna gorutina posílala data většímu množství gorutin, samozřejmě opět s využitím kanálů. V následujícím demonstračním příkladu budeme posílat náhodná data (buď nulu nebo jedničku) do gorutiny reprezentované funkcí worker:

package main
 
import "fmt"
 
func worker(channel chan int) {
        for true {
                value, ok := <-channel
                if ok {
                        fmt.Printf("Přijata hodnota %d\n", value)
                } else {
                        fmt.Printf("Kanál je uzavřen\n")
                }
        }
}
 
func main() {
        ch1 := make(chan int)
 
        go worker(ch1)
 
        for i := 0; i < 10; i++ {
                select {
                case ch1 <- 0:
                        fmt.Println("Poslána nula")
                case ch1 <- 1:
                        fmt.Println("Poslána jednička")
                }
        }
}

Příklad chování tohoto příkladu po jeho spuštění:

Přijata hodnota 0
Poslána nula
Poslána jednička
Přijata hodnota 1
Přijata hodnota 0
Poslána nula
Poslána jednička
Přijata hodnota 1
Přijata hodnota 0
Poslána nula
Poslána nula
Přijata hodnota 0
Přijata hodnota 1
Poslána jednička
Poslána jednička
Přijata hodnota 1
Přijata hodnota 0
Poslána nula
Poslána jednička
Poznámka: povšimněte si, jak se promíchávají zprávy workera a hlavní gorutiny. Mohlo by se zdát, že gorutina přijímá data, která ještě nebyla poslána, to je ovšem způsobeno tím, že worker po příjmu dat ještě stihne provést tisk zprávy před přepnutím vlákna do hlavní gorutiny.

11. Blokující zápis do kanálu

Předchozí příklad je možné rozšířit o druhou gorutinu-workera. Oba workeři nyní budou sdílet jediný komunikační kanál a při zápisu hodnoty do kanálu je tedy náhodně vybrán ten worker, který je v daný okamžik k dispozici. První varianta příkladu používá kanál s kapacitou jediné položky (jedná se tedy vlastně o jednoduchý mailbox):

package main
 
import (
        "fmt"
        "time"
)
 
func worker(channel chan int, worker int) {
        for true {
                value, ok := <-channel
                if ok {
                        fmt.Printf("Worker %d přijal hodnotu %d\n", worker, value)
                } else {
                        fmt.Printf("Kanál je uzavřen pro workera %d\n", worker)
                }
                time.Sleep(1 * time.Second)
        }
}
 
func main() {
        ch1 := make(chan int)
 
        go worker(ch1, 1)
        go worker(ch1, 2)
 
        for i := 0; i < 10; i++ {
                select {
                case ch1 <- 0:
                        fmt.Println("Poslána nula")
                case ch1 <- 1:
                        fmt.Println("Poslána jednička")
                }
        }
}

Zápis do kanálu je v tomto případě blokující operace a opět se náhodně vybere jedna z větví v konstrukci select-case:

Worker 2 přijal hodnotu 1
Poslána jednička
Poslána nula
Worker 1 přijal hodnotu 0
Worker 2 přijal hodnotu 0
Poslána nula
Poslána nula
Worker 1 přijal hodnotu 0
Worker 2 přijal hodnotu 0
Poslána nula
Worker 1 přijal hodnotu 0
Poslána nula
Worker 1 přijal hodnotu 0
Poslána nula
Poslána jednička
Worker 2 přijal hodnotu 1
Worker 1 přijal hodnotu 1
Poslána jednička
Poslána nula

Pokud ovšem v příkladu změníme řádek, na němž se vytváří kanál, z:

ch1 := make(chan int)

na:

ch1 := make(chan int, 20)

stane se zápis neblokující operací, protože kanál bude mít kapacitu dvaceti položek (jedná se o frontu) a my do něj zapíšeme pouze deset položek. Chování aplikace se změní:

Poslána nula
Poslána nula
Poslána nula
Poslána nula
Poslána jednička
Poslána nula
Poslána jednička
Poslána jednička
Poslána nula
Poslána jednička

Další text se (většinou) nevypíše, a to z toho prostého důvodu, že hlavní gorutina skončí, aniž by došlo k přepnutí kontextu do prvního či druhého workera.

12. Kombinace čtení a zápisu v konstrukci select-case

V posledním demonstračním příkladu, v němž použijeme konstrukci select-case, zkombinujeme jak čtení, tak i zápis do různých kanálů. I tuto kombinaci je možné v jazyku Go použít, takže následující zápis je zcela korektní, a to jak syntakticky, tak i sémanticky:

select {
case ch1 <- 0:
        fmt.Println("Poslána nula")
case ch1 <- 1:
        fmt.Println("Poslána jednička")
case data, ok := <-ch2:
        if ok {
                fmt.Printf("Přijata data %d z kanálu 2\n", data)
        }
case data, ok := <-ch3:
        if ok {
                fmt.Printf("Přijata data %d z kanálu 3\n", data)
        }
}

V předchozí konstrukci se vybere vždy pouze jediná větev v závislosti na tom, zda jsou v prvních dvou kanálech data, popř. zda jsou druhé dva kanály prázdné či obsazené.

Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:

package main
 
import (
        "fmt"
        "time"
)
 
func receiver(channel chan int, receiver int) {
        for true {
                value, ok := <-channel
                if ok {
                        fmt.Printf("Příjemce %d přijal hodnotu %d\n", receiver, value)
                } else {
                        fmt.Printf("Kanál je pro příjemce %d uzavřen\n", receiver)
                }
                time.Sleep(2 * time.Second)
        }
}
 
func sender(channel chan int, sender int) {
        fmt.Printf("Odesílatel %d byl spuštěn\n", sender)
        for i := 1; i <= 5; i++ {
                time.Sleep(1 * time.Second)
                channel <- i
        }
        fmt.Printf("Odesílatel %d byl ukončen\n", sender)
}
 
func main() {
        ch1 := make(chan int)
        ch2 := make(chan int)
        ch3 := make(chan int)
        // ch1 := make(chan int, 20)
 
        go receiver(ch1, 1)
        go receiver(ch1, 2)
        go sender(ch2, 1)
        go sender(ch3, 2)
 
        for i := 0; i < 20; i++ {
                select {
                case ch1 <- 0:
                        fmt.Println("Poslána nula")
                case ch1 <- 1:
                        fmt.Println("Poslána jednička")
                case data, ok := <-ch2:
                        if ok {
                                fmt.Printf("Přijata data %d z kanálu 2\n", data)
                        }
                case data, ok := <-ch3:
                        if ok {
                                fmt.Printf("Přijata data %d z kanálu 3\n", data)
                        }
                }
        }
}

Příklad výsledků vypsaných tímto příkladem po jeho spuštění:

Odesílatel 1 byl spuštěn
Odesílatel 2 byl spuštěn
Poslána nula
Poslána jednička
Příjemce 1 přijal hodnotu 1
Příjemce 2 přijal hodnotu 0
Přijata data 1 z kanálu 2
Přijata data 1 z kanálu 3
Příjemce 2 přijal hodnotu 1
Poslána jednička
Přijata data 2 z kanálu 2
Poslána jednička
Příjemce 1 přijal hodnotu 1
Přijata data 2 z kanálu 3
Přijata data 3 z kanálu 2
Přijata data 3 z kanálu 3
Příjemce 2 přijal hodnotu 1
Poslána jednička
Poslána nula
Příjemce 1 přijal hodnotu 0
Přijata data 4 z kanálu 3
Přijata data 4 z kanálu 2
Odesílatel 2 byl ukončen
Přijata data 5 z kanálu 3
Přijata data 5 z kanálu 2
Odesílatel 1 byl ukončen
Příjemce 2 přijal hodnotu 0
Poslána nula
Příjemce 1 přijal hodnotu 1
Poslána jednička
Příjemce 2 přijal hodnotu 0
Poslána nula
Příjemce 1 přijal hodnotu 0
Poslána nula

13. Deklarace konstant v programovacím jazyku Go

V programovacím jazyku Go je možné deklarovat konstanty s využitím klíčového slova const, za nímž se zapíše jméno konstanty, její typ a hodnota konstanty (oddělená přiřazovacím operátorem =). V případě, že se nespecifikuje typ konstanty, bude odvozen automaticky překladačem na základě přiřazované hodnoty. Příklad deklarací konstant různých typů (povšimněte si, že již definované konstanty mohou být součástí dalších konstantních výrazů):

package main
 
import "fmt"
 
const Pi float64 = 3.1415927
const E = 2.71828
 
const z0 int = 0
const z1 = 0
 
const z2 = z0 + z1
 
func main() {
        fmt.Printf("Pi = %f\n", Pi)
        fmt.Printf("e = %f\n", E)
 
        fmt.Printf("z0 = %d\n", z0)
        fmt.Printf("z1 = %d\n", z1)
 
        fmt.Printf("z2 = %d\n", z2)
}

Výsledek po spuštění tohoto příkladu:

Pi = 3.141593
e = 2.718280
z0 = 0
z1 = 0
z2 = 0

14. Demonstrační příklad: konstanty různých typů, blok s konstantami

Poměrně často se setkáme i s uzavřením všech konstant do jediného bloku začínajícího klíčovým slovem const. Před jednotlivými konstantami se potom toto slovo již nezapisuje, což je ostatně patrné i při pohledu na další příklad:

package main
 
import "fmt"
 
const (
        Pi float64 = 3.1415927
        E          = 2.71828
 
        z0 int = 0
        z1     = 0
        z2     = z0 + z1
)
 
func main() {
        fmt.Printf("Pi = %f\n", Pi)
        fmt.Printf("e = %f\n", E)
 
        fmt.Printf("z0 = %d\n", z0)
        fmt.Printf("z1 = %d\n", z1)
 
        fmt.Printf("z2 = %d\n", z2)
}

Výsledek, který získáme po spuštění tohoto příkladu:

Pi = 3.141593
e = 2.718280
z0 = 0
z1 = 0
z2 = 0

15. Automatické generování celočíselné řady pomocí klíčového slova iota

S blokem s konstantami, který jsme si ukázali v předchozí kapitole, souvisí i speciální identifikátor iota. Jedná se o konstantu, která má při prvním použití hodnotu 0, při druhém použití hodnotu 1 atd. Jinými slovy je tento identifikátor interně realizován jako celočíselný čítač v překladači, jehož hodnota se nastavuje na nulu při každém vstupu do bloku const. Podívejme se na následující demonstrační příklad, v němž se speciální identifikátor iota používá pro realizaci (v Go neexistujícího) výčtového typu:

package main
 
import "fmt"
 
const (
        Pondeli = iota
        Utery   = iota
        Streda  = iota
        Ctvrtek = iota
        Patek   = iota
        Sobota  = iota
        Nedele  = iota
)
 
func main() {
        fmt.Printf("%d\n", Pondeli)
        fmt.Printf("%d\n", Streda)
        fmt.Printf("%d\n", Patek)
}

Po překladu a spuštění tohoto příkladu získáme tři hodnoty odpovídající dnům pondělí, středa a pátek:

0
2
4
Poznámka: opět zde můžeme vidět, že se v Go používá obecnější konstrukce, než je tomu například v jazyku C a jeho typu enum.

16. Praktické použití klíčového slova iota

Při praktickém použití klíčového slova iota se většinou tento identifikátor zapisuje pouze za první jméno konstanty v bloku const. U dalších konstant již není zapotřebí zapisovat ani typ, ani do nich explicitně přiřazovat hodnotu. Pokud není přiřazení zapsáno, automaticky se v průběhu překladu doplní text „= iota“, což je ukázáno na dalším příkladu:

package main
 
import "fmt"
 
const (
        Pondeli = iota
        Utery
        Streda
        Ctvrtek
        Patek
        Sobota
        Nedele
)
 
func main() {
        fmt.Printf("%d\n", Pondeli)
        fmt.Printf("%d\n", Streda)
        fmt.Printf("%d\n", Patek)
}

Výsledek bude stejný jako v předchozím příkladu:

0
2
4

Ve skutečnosti nám ovšem nic nebrání použít iota i poněkud netradičně v prakticky libovolných konstantních výrazech (proto je iota obecnějším konceptem):

package main
 
import "fmt"
 
const (
        a = iota + 0.5i
        b = iota + 0.5i
        c = 1.0 / iota
        d = 1 << iota
        e = iota ^ 0xff
)
 
func main() {
        fmt.Printf("%f\n", a)
        fmt.Printf("%f\n", b)
        fmt.Printf("%f\n", c)
        fmt.Printf("%d\n", d)
        fmt.Printf("%d\n", e)
}
(0.000000+0.500000i)
(1.000000+0.500000i)
0.500000
8
251

17. Funkce s variabilním počtem parametrů v programovacím jazyku Go

Poslední vlastností programovacího jazyka Go, s níž se dnes seznámíme, je koncept variadických funkcí, tj. funkcí s variabilním počtem parametrů. Tyto parametry jsou ovšem typované (nelze použít obdobu void *). Pokud funkce obsahuje jak běžné parametry, tak i nepovinné parametry, musí být běžné parametry umístěny před parametry nepovinné, tj. tak, jak je to ukázáno v dalším příkladu:

package main
 
import "fmt"
 
func f1(msg string) {
        fmt.Printf("%s\n", msg)
}
 
func f2(parts ...string) {
        for _, val := range parts {
                fmt.Printf("%s ", val)
        }
        fmt.Println()
}
 
func f3(prefix string, parts ...string) {
        fmt.Println(prefix)
        for _, val := range parts {
                fmt.Printf("%s ", val)
        }
        fmt.Println()
}
 
func main() {
        f1("Hello")
        f2("Hello", "world", "!")
        f3("Message:", "Hello", "world", "again", "!")
}

Funkce f1 akceptuje jeden parametr, funkce f2 libovolný počet parametrů (i žádný parametr) a funkce f3 akceptuje minimálně jeden parametr. Všechny parametry jsou typu řetězec:

Hello
Hello world !
Message:
Hello world again !

Nepovinné parametry ovšem mohou být uvedeny jen na konci seznamu parametrů, takže například tento příklad není korektní a nebude ho možné přeložit:

package main
 
import "fmt"
 
func f4(prefix string, parts1 ...string, parts2 ...string) {
        fmt.Println(prefix)
        for _, val := range parts1 {
                fmt.Printf("%s ", val)
        }
        fmt.Println()
        for _, val := range parts2 {
                fmt.Printf("%s ", val)
        }
        fmt.Println()
}
 
func main() {
        f4("Message:", "Hello", "world", "again", "!")
}

Při pokusu o překlad se vypíše:

./17_variadic_function_improper_usage.go:12:34: can only use ... with final parameter in list

18. Základní informace o adresářové struktuře při práci se složitějšími projekty

Již v úvodním článku jsme si řekli, že při instalaci jazyka Go je vhodné nastavit proměnnou GOPATH, například na adresář ~/home/go. Ve skutečnosti je problematika proměnné prostředí GOPATH a jejího významu poněkud složitější, protože tato proměnná by měla obsahovat absolutní (!) cestu k adresáři, který má tuto strukturu:

.
├── bin
│   ├──
│   └──
├── pkg
│   ├──
│   └──
└── src
    ├──
    ├──
    └──

Celý adresář se nazývá pracovní plocha (workspace) a obsahuje tři podadresáře pojmenované src, pkg a bin. Prozatím nás bude zajímat především podadresář src, v němž jsou typicky uloženy repositáře (repository) a v každém repositáři je libovolný počet balíčků (package). V balíčcích už nalezneme zdrojové soubory .go, pomocné skripty, datové soubory, dokumentaci atd. Workspace představovaný adresářem nazvaným go, v němž jsou umístěny tři repositáře, může vypadat následovně:

.
└── go
    ├── bin
    ├── pkg
    └── src
        ├── repository1
        │   ├── hello1
        │   │   └── hello.go
        │   └── hello2
        │       └── hello.go
        ├── repository2
        └── repository3

Vidíme, že v repositáři pojmenovaném repository1 jsou umístěny dva balíčky s názvy hello1 a hello2, přičemž v každém balíčku je jediný zdrojový soubor, který je (čistě náhodou) shodně pojmenován hello.go.

cif-tip-digitalizaceCR

package main
 
func main() {
        println("repository1: Hello world!")
}

Další informace o této adresářové struktuře a jejím významu si podrobněji popíšeme v navazujícím článku, v němž se budeme věnovat správě balíčků, jejich instalaci apod.

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Demonstrační příklad Popis Cesta
1 01_bit_operators.go všechny binární bitové operátory https://github.com/tisnik/go-fedora/blob/master/article07/01_bit_o­perators.go
       
2 02_boolean_operators.go logické operátory aplikované na pravdivostní hodnoty https://github.com/tisnik/go-fedora/blob/master/article07/02_bo­olean_operators.go
3 02A_boolean_operators_short_circuit.go zkrácené vyhodnocování logických operátorů https://github.com/tisnik/go-fedora/blob/master/article07/02A_bo­olean_operators_short_cir­cuit.go
4 02B_boolean_operators_improper_usage.go nekorektní použití logických operátorů https://github.com/tisnik/go-fedora/blob/master/article07/02B_bo­olean_operators_improper_u­sage.go
5 02C_boolean_operators_improper_usage.go nekorektní použití logických operátorů https://github.com/tisnik/go-fedora/blob/master/article07/02C_bo­olean_operators_improper_u­sage.go
       
6 inc1.c operátor ++ a ukazatele v jazyku C https://github.com/tisnik/go-fedora/blob/master/article07/inc1.c
7 inc2.c operátor ++ a ukazatele v jazyku C https://github.com/tisnik/go-fedora/blob/master/article07/inc1.c
8 03_inc_dec.go použití operací ++ a – v Go https://github.com/tisnik/go-fedora/blob/master/article07/03_in­c_dec.go
9 04_inc_dec_bad_usage.go nekorektní použití operací ++ a -- https://github.com/tisnik/go-fedora/blob/master/article07/04_in­c_dec_bad_usage.go
10 05_inc_dec_bad_usage.go nekorektní použití operací ++ a -- https://github.com/tisnik/go-fedora/blob/master/article07/05_in­c_dec_bad_usage.go
       
11 06_select_statement_receive.go použití select pro příjem dat https://github.com/tisnik/go-fedora/blob/master/article07/06_se­lect_statement_receive.go
12 07_select_statement_receive_default.go konstrukce select a větev default https://github.com/tisnik/go-fedora/blob/master/article07/07_se­lect_statement_receive_de­fault.go
13 08_select_statement_send.go posílání dat v konstrukci select https://github.com/tisnik/go-fedora/blob/master/article07/08_se­lect_statement_send.go
14 09_select_statement_send.go dvě gorutiny sdílející jediný kanál s kapacitou 1 https://github.com/tisnik/go-fedora/blob/master/article07/09_se­lect_statement_send.go
15 09B_select_statement_send.go dvě gorutiny sdílející jediný kanál s kapacitou 20 https://github.com/tisnik/go-fedora/blob/master/article07/09B_se­lect_statement_send.go
16 10_select_statement_send_receive.go čtení i zápis do kanálu https://github.com/tisnik/go-fedora/blob/master/article07/10_se­lect_statement_send_recei­ve.go
       
16 11_constants.go deklarace konstant různých typů https://github.com/tisnik/go-fedora/blob/master/article07/11_con­stants.go
17 12_constants.go deklarace konstant v bloku https://github.com/tisnik/go-fedora/blob/master/article07/12_con­stants.go
18 13_iota.go použití identifikátoru iota https://github.com/tisnik/go-fedora/blob/master/article07/13_i­ota.go
19 14_iota_better_usage.go praktické použití identifikátoru iota https://github.com/tisnik/go-fedora/blob/master/article07/14_i­ota_better_usage.go
20 15_iota_another_usage.go další příklad použití identifikátoru iota https://github.com/tisnik/go-fedora/blob/master/article07/15_i­ota_another_usage.go
21 16_variadic_function.go variadické funkce https://github.com/tisnik/go-fedora/blob/master/article07/16_va­riadic_function.go
22 17_variadic_function_improper_usage.go variadické funkce, nekorektní použití https://github.com/tisnik/go-fedora/blob/master/article07/17_va­riadic_function_improper_u­sage.go
23 gopath_directory adresářová struktura GOPATH https://github.com/tisnik/go-fedora/blob/master/article07/go­path_directory

20. Odkazy na Internetu

  1. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  2. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  3. Algorithms to Go
    https://yourbasic.org/
  4. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  5. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/
  6. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  7. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  8. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  9. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  10. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  11. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  12. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  13. The Go Programming Language (home page)
    https://golang.org/
  14. GoDoc
    https://godoc.org/
  15. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  16. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  17. The Go Programming Language Specification
    https://golang.org/ref/spec
  18. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  19. Package builtin
    https://golang.org/pkg/builtin/
  20. Package fmt
    https://golang.org/pkg/fmt/
  21. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  22. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  23. Learning Go
    https://www.miek.nl/go/
  24. Go Bootcamp
    http://www.golangbootcamp.com/
  25. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  26. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  27. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  28. The Go Blog
    https://blog.golang.org/
  29. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  30. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  31. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  32. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  33. How the Go runtime implements maps efficiently (without generics)
    https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics
  34. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  35. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  36. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  37. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  38. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  39. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  40. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  41. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  42. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  43. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  44. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  45. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  46. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  47. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  48. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  49. Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
    https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/
  50. 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
    https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd
  51. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  52. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  53. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  54. Go vs. Python
    https://www.peterbe.com/plog/govspy
  55. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  56. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  57. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  58. Go by Example: Slices
    https://gobyexample.com/slices
  59. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  60. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  61. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  62. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  63. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  64. nils In Go
    https://go101.org/article/nil.html
  65. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  66. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  67. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  68. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  69. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  70. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  71. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  72. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  73. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  74. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  75. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  76. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  77. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  78. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  79. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  80. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  81. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  82. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  83. Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
    https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06
  84. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  85. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  86. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  87. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  88. Selectors
    https://golang.org/ref/spec#Selectors
  89. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  90. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  91. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  92. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  93. Part 21: Goroutines
    https://golangbot.com/goroutines/
  94. Part 22: Channels
    https://golangbot.com/channels/
  95. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  96. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  97. Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
    https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/
  98. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  99. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  100. Control Structures
    https://www.golang-book.com/books/intro/5
  101. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  102. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  103. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  104. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  105. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  106. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  107. Effective Go
    https://golang.org/doc/ef­fective_go.html
  108. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  109. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation