Hlavní navigace

Tvorba balíčků a pokročilejší operace s kanály v jazyce Go

Pavel Tišnovský

Dnes dokončíme popis některých témat, kterým jsme se již věnovali minule. Ukážeme si vytváření vlastních balíčků a import balíčků. Poté se budeme věnovat implementaci datového typu „výčet“ a povíme si více o práci s kanály.

Doba čtení: 32 minut

11. Převod zvolených celočíselných konstant na řetězec

12. Využití struktury/záznamu pro typově zabezpečené operace s výčtem

13. Kanály s předdefinovanou maximální kapacitou fronty

14. Použití konstrukce range při čtení dat z kanálu

15. Chování uzavřeného kanálu při čtení hodnot

16. Uzavření kanálu jako součást synchronizační konstrukce

17. Konstrukce select s definicí maximální doby čekání na data

18. Definice čekání na data a větev default

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

20. Odkazy na Internetu

1. Všechna klíčová slova jazyka Go s odkazy na podrobnější popis

V předchozím článku jsme prakticky dokončili popis všech důležitých vlastností programovacího jazyka Go (minimálně všech syntaktických konstrukcí dostupných v současné stabilní verzi), takže si pro úplnost ještě jednou ukažme tabulku se všemi klíčovými slovy tohoto jazyka společně s odkazy na články a kapitoly, ve kterých jsme se popisu těchto klíčových slov věnovali podobněji:

2. Další standardní identifikátory, jména datových typů a funkcí

Kromě těchto klíčových slov se v Go setkáme s několika identifikátory, které mají pevný význam. Typicky se jedná o konstanty, v jednom případě o „automaticky měněnou konstantu“ a o pojmenování standardních datových typů. Jedná se o následující slova:

Identifikátor Typ Stručný popis
true konstanta pravdivostní hodnota
false konstanta pravdivostní hodnota
iota konstanta celočíselný automaticky zvyšovaný čítač
nil konstanta prázdná hodnota, prázdné rozhraní
   
bool datový typ logický/pravdivostní typ
byte datový typ alias pro typ uint8
int datový typ odpovídá buď typu int32 nebo int64
int8 datový typ osmibitové celé číslo se znaménkem
int16 datový typ šestnáctibitové celé číslo se znaménkem
int32 datový typ 32bitové celé číslo se znaménkem
int64 datový typ 64bitové celé číslo se znaménkem
uint datový typ odpovídá buď typu uint32 nebo uint64
uint8 datový typ osmibitové celé číslo bez znaménka
uint16 datový typ 16bitové celé číslo bez znaménka
uint32 datový typ 32bitové celé číslo bez znaménka
uint64 datový typ 64bitové celé číslo bez znaménka
float32 datový typ číslo s jednoduchou přesností podle IEEE 754
float64 datový typ číslo s dvojitou přesností podle IEEE 754
complex64 datový typ dvojice hodnot s jednoduchou přesností
complex128 datový typ dvojice hodnot s dvojitou přesností
error datový typ rozhraní s předpisem metody Error
rune datový typ alias pro typ int32
string datový typ
uintptr datový typ používáno pro uložení adresy (ukazatele)

Dále mají všechny moduly vytvářené v programovacím jazyku Go k dispozici několik standardních funkcí, které není zapotřebí žádným způsobem importovat. Jména těchto funkcí sice nejsou striktně rezervována, ovšem většinou není vhodné deklarovat funkci stejného jména s odlišným chováním:

Funkce Funkce Funkce
append cap close
complex copy delete
imag len make
new panic print
println real recover

3. Klíčové slovo package

S klíčovým slovem package jsme se setkali již v úvodní části tohoto seriálu. Toto slovo musí být použito v každém zdrojovém kódu, protože slouží ke specifikaci balíčku, ke kterému daný zdrojový soubor přísluší. Za tímto klíčovým slovem se zapisuje název balíčku, pro nějž platí prakticky stejné jmenné konvence, jako pro jakýkoli jiný identifikátor (teoreticky lze použít znaky z Unicode, prakticky se využívá jen podmnožina ASCII), ovšem s jednou malou výjimkou – nelze zde použít prázdný identifikátor představovaný podtržítkem „_“. Další striktní požadavky nejsou na názvy balíčků kladeny, ovšem s ohledem na konzistentnost se doporučuje, aby název balíčku byl tvořen jediným podstatným jménem zapisovaným malými písmeny. Vzhledem k tomu, že by v názvu balíčku mělo být jen jediné slovo, není zapotřebí řešit další pravidla – zda použít camelCase, snake_case atd.

Na tomto místě se asi ptáte, jak a zda vůbec je možné toto pravidlo dodržet i pro rozsáhlejší aplikace s desítkami či stovkami balíčků – zde přece musí dojít k nějaké kolizi jmen. Ve skutečnosti je ovšem název balíčku (většinou ono jediné podstatné jméno) spojeno s cestou (path) k balíčku, která slouží k přesnějšímu rozlišení. Příkladem může být například standardní balíček nazvaný httputil, jehož zdrojové kódy naleznete na stránce https://golang.org/src/net/http/httpu­til/. Ve zdrojových kódech, do nichž se tento balíček importuje, budeme psát cestu k balíčku a teprve po ní jeho název, což nám již umožní lepší návrh aplikace a dodržení jmenných konvencí jazyka Go. Jako oddělovač je použito lomítko:

import "net/http/httputil"

Navíc to znamená, že jméno balíčku nemusí být unikátní; samozřejmě ovšem není možné mít stejně pojmenovaný balíček se stejnou cestou. Příklad balíčků se shodným jménem, ale s odlišnou cestou (a s odlišnou funkcionalitou):

  • github.com/Workiva/go-datastructures/queue
  • github.com/eapache/queue
  • github.com/openfaas/faas/gateway/queue
  • github.com/jaegertracing/ja­eger/pkg/queue

atd. (stejně pojmenovaných balíčků je ve skutečnosti několik desítek).

Další informace o jmenných konvencích při pojmenování balíčků naleznete na stránkách:

  1. Style guideline for Go packages
    https://rakyll.org/style-packages/
  2. The Go Blog: Package names
    https://blog.golang.org/package-names
  3. Seznam standardních balíčků
    https://golang.org/pkg/
  4. Seznam dalších (vybraných) balíčků pro Go
    https://github.com/avelino/awesome-go
  5. Vyhledávač balíčků pro jazyk Go
    https://go-search.org/
  6. Everything you need to know about Packages in Go
    https://medium.com/rungo/everything-you-need-to-know-about-packages-in-go-b8bac62b74cc
Poznámka: balíček nemusí být tvořen jediným souborem. Je tomu spíše naopak – je obvyklé (a je to i doporučované), aby byly datové typy, rozhraní, funkce i metody implementované v rámci jednoho balíčku rozděleny do několika zdrojových souborů. Pokud se vrátíme k již zmíněnému balíčku httputil, je tento balíček rozdělen do čtyř souborů httputil.go, persist.go, reverseproxy.go a dump.go, které jsou navíc doplněny o několik testů (ve skutečnosti má jeden z testů deklarován odlišný balíček httputil_test).

4. Klíčové slovo import

S balíčky úzce souvisí i další klíčové slovo nazvané import. Jak již název tohoto slova naznačuje, slouží k importu balíčků do zdrojového kódu, který je právě vyvíjen a který vyžaduje použití konstant, proměnných, datových typů, funkcí či metod z tohoto balíčku. Za klíčovým slovem import je možné v tom nejjednodušším případě uvést cestu k balíčku s jeho názvem. V programovacím jazyce Go se přitom nejedná o nějaký speciální identifikátor, ale o řetězec:

import "fmt"

Taktéž je možné provést import několika balíčků současně. V tomto případě se názvy těchto balíčků zapisují do kulatých závorek za slovem import, přičemž by se měl dodržet následující formát zápisu (namísto mezer se ovšem v reálných zdrojových kódech používají taby).

Jeden balíček:

import (
        "fmt"
)

Více balíčků:

import (
        "fmt"
        "time"
)

Pokud jsou balíčky importovány výše uvedeným způsobem, je přístup k importovaným objektům (konstanty, …) provádět s využitím tečkové notace, například:

fmt.Printf("Reservation for %d\n", day)
time.Sleep(1 * time.Second)

Alternativně je ovšem možné před jméno importovaného balíčku zapsat tečku:

import . "fmt"

V takovém případě se tečková notace nepoužívá a objekty z balíčku se zapisují pouze svým jménem:

Printf("Reservation for %d\n", day)

Tento způsob je vhodné používat s rozvahou, protože se nám vlastně pomíchají lokální identifikátory objektů s identifikátory objektů naimportovaných, což může způsobit pozdější zmatky.

Poznámka: tento typ importu se používá například v testech, kdy jsou testovací scénáře uloženy do vlastního balíčku a provádí importy testovaných balíčků, aniž by došlo k cyklickým závislostem. Mimochodem – u balíčků pro testy se používají jména xyzzy_test, takže se vlastně porušují výše zmíněná pravidla pro pojmenování.

Další možností je vytvoření aliasu jména balíčku:

import formatter "fmt"

resp.:

import (
        formatter "fmt"
)

Ve zdrojových kódech se v tomto případě použije alias:

formatter.Printf("Reservation for %d\n", day)

Zajímavé je, že alias není zapisován jako řetězec, ale jako běžný identifikátor. Důvod, proč tomu tak je, si řekneme v navazující kapitole.

Poznámka: existuje ještě jeden způsob zápisu, kdy se namísto aliasu použije podtržítko „_“. To využijeme v případě, že potřebujeme balíček inicializovat (konkrétně volat jeho funkci init), ovšem nechystáme se použít žádný objekt, který je v balíčku deklarován. V takovém případě ovšem nelze použít běžný import, protože by překladač jazyka Go správně upozornil na chybu.

5. Vytvoření vlastního balíčku, export objektů

Nyní si ukažme, jakým způsobem je možné vytvořit vlastní balíček, který pojmenujeme hello1. Je to snadné – do adresáře ~/go/src, který jsme si vytvořili už na začátku seriálu (a na který ukazuje proměnná GOPATH), přidáme podadresář nazvaný „hello1“ a vytvoříme v něm soubor nazvaný „hello1.go“ s následujícím obsahem:

package hello1
 
func Hello_world() {
        println("Hello world!")
}

V jakémkoli jiném adresáři nyní můžeme tento balíček naimportovat a zavolat z něho funkci Hello_world:

package main
 
import (
        "hello1"
)
 
func main() {
        hello.Hello_world()
}

Nyní vytvořme nový balíček se jménem hello2, ovšem tentokrát bude následující zdrojový kód uložen v souboru ~/go/src/hello2/hello1.go (ne – nespletli jsme se, skutečně zde použijeme jedničku a nikoli dvojku):

package hello2
 
func Hello_world() {
        println("Hello world #2!")
}

Takový balíček bude stále bez potíží použitelný! V jazyku Go je totiž možné mít v jednom balíčku více zdrojových souborů, přičemž důležitý je název adresáře a řádek package, nikoli jména souborů:

package main
 
import (
        "hello2"
)
 
func main() {
        hello2.Hello_world()
}

Třetí balíček obsahuje deklaraci funkce hello_world, jejíž název začíná malým písmenem:

package hello3
 
func hello_world() {
        println("Hello world #3!")
}

Tato zdánlivě nepatrná změna způsobí, že funkci nebude možné z jiného balíčku/modulu zavolat, protože symboly začínající malým písmenem by neměly být viditelné (přesněji řečeno – nejsou exportovány):

package main
 
import (
        "hello3"
)
 
func main() {
        hello3.hello_world()
}

O této vlastnosti programovacího jazyka Go nás přesvědčí pokus o překlad:

./21_use_package_hello3.go:15:2: cannot refer to unexported name hello3.hello_world
./21_use_package_hello3.go:15:2: undefined: hello3.hello_world

6. Inicializace balíčků

V případě, že je v balíčku deklarována funkce init(), bude tato funkce automaticky zavolána při importu balíčků. Totéž rekurzivně platí i pro případné balíčky importované do našeho balíčku atd. atd. Zkusme si nyní vytvořit nový balíček nazvaný hello4, který bude umístěn do adresáře ~/go/src/hello4:

package hello4
 
func init() {
        println("hello4.init() called")
}
 
func Hello_world() {
        println("Hello world #4!")
}

Tento balíček běžným způsobem naimportujeme a zavoláme jeho funkci:

package main
 
import (
        "hello4"
)
 
func main() {
        hello4.Hello_world()
}

Po překladu a spuštění příkladu by se měla nejdříve zobrazit zpráva o zavolání funkce init() a teprve poté zpráva z funkce main:

hello4.init() called
Hello world #4!

V některých situacích může být užitečné zavolat pouze funkci init() a balíček posléze nepoužít. To je problematické, protože init() není exportována (začíná malým písmenem) a současně není možné importovat nepoužívaný balíček. Řešení spočívá v použití již zmíněného podtržítka, které nám dovolí import balíčku bez jeho dalšího použití, což nám ovšem nevadí, protože init() se zavolá při importu:

package main
 
import (
        _ "hello4"
)
 
func main() {
}

7. Adresářová struktura, na níž ukazuje proměnná GOPATH

Vraťme se nyní k závěrečné části předchozího článku, v níž jsme se věnovali problematice adresářové struktury, na niž by měla ukazovat proměnná prostředí GOPATH. Připomeňme si, že se tento adresář (může se jmenovat jakkoli) nazývá workspace a měl by obsahovat tři podadresáře pojmenované bin, pkg a src. V těchto adresářích se postupně budou vytvářet další podadresáře, ale základní struktura by zpočátku měla vypadat následovně:

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

Obsah podadresáře src vlastně již do značné míry známe, protože ten obsahuje zdrojové kódy jednotlivých balíčků, přičemž každý balíček je umístěn ve vlastním podadresáři. Vzhledem k operacím, které jsme provedli v rámci předchozích kapitol by tedy aktuální adresářová struktura workspace, na níž ukazuje proměnná GOPATH, měla vypadat takto:

.
├── bin
├── pkg
└── src
    ├── hello1
    │   └── hello1.go
    ├── hello2
    │   └── hello1.go
    ├── hello3
    │   └── hello1.go
    └── hello4
        └── hello4.go

Zajímavá je funkce ostatních dvou podadresářů bin a pkg. Jejich použití je vysvětleno v navazující kapitole.

8. Balíčky se spustitelným kódem

Nyní v adresáři src vytvoříme podadresář s balíčkem nazvaným say_hello1. Tento balíček bude obsahovat jediný soubor hello.go s tímto obsahem:

package main
 
func main() {
        println("Hello world #1!")
}

Povšimněte si, že v tomto případě je na prvním řádku zapsáno package main a nikoli package say_hello1. Je tomu tak z toho důvodu, že tento balíček ve skutečnosti použijeme pro vytvoření spustitelného souboru a nebudeme ho používat jako knihovnu.

V tento okamžik můžeme provést instalaci balíčku

go install say_hello_1

Balíček by se měl přeložit a jeho spustitelná nativní podoba by měla být uložena do adresáře bin:

.
├── bin
│   └── say_hello_1
├── pkg
└── src
    ├── hello1
    │   └── hello1.go
    ├── hello2
    │   └── hello1.go
    ├── hello3
    │   └── hello1.go
    ├── hello4
    │   └── hello4.go
    └── say_hello_1
        └── hello.go

Podobně můžeme vytvořit balíček say_hello2, opět se souborem hello.go. To, že se soubory v obou balíčcích jmenují stejně, vůbec nevadí, protože výsledná spustitelná aplikace bude pojmenována stejně jako balíček:

├── bin
│   ├── say_hello_1
│   └── say_hello_2
├── pkg
└── src
    ├── hello1
    │   └── hello1.go
    ├── hello2
    │   └── hello1.go
    ├── hello3
    │   └── hello1.go
    ├── hello4
    │   └── hello4.go
    ├── say_hello_1
    │   └── hello.go
    └── say_hello_2
        └── hello.go

Další možnosti si ukážeme příště při popisu práce s repositáři, instalací externích knihoven apod.

9. Chybějící datový typ enum a způsob jeho náhrady jinou konstrukcí

V předchozím článku jsme si ukázali způsob použití identifikátoru iota. Při té příležitosti jsme si řekli, že současná verze programovacího jazyka Go neobsahuje plnohodnotný datový typ „výčet“ neboli enumeration resp. enum. Ve skutečnosti však máme k dispozici několik způsobů, jak se k výčtovému typu alespoň přiblížit. Jednotlivé způsoby si postupně popíšeme.

Nejvíce přímočará je pouhá deklarace celočíselných konstant v bloku const s využitím identifikátoru iota, který zde vystupuje v roli celočíselného čítače:

const (
        Pondeli = iota
        Utery
        Streda
        Ctvrtek
        Patek
        Sobota
        Nedele
)

Při takto přímočarém použití však nastávají minimálně dva problémy:

  1. Konstanty jsou typu int a takto se tedy musí předávat do funkcí.
  2. Může se stát, že některé operace změní hodnotu, která má představovat pořadí dne v týdnu, na neplatnou hodnotu.

Ostatně si můžeme vyzkoušet, jak se bude chovat následující demonstrační příklad po svém překladu a spuštění:

package main
 
import "fmt"
 
const (
        Pondeli = iota
        Utery
        Streda
        Ctvrtek
        Patek
        Sobota
        Nedele
)
 
func reservation(day int) {
        fmt.Printf("Reservation for %d\n", day)
}
 
func main() {
        reservation(Pondeli)
        reservation(Sobota)
        reservation(Nedele)
 
        reservation(3)
 
        day := Pondeli
        day--
        reservation(day)
}

Ze zpráv vypsaných na terminál je patrné, že můžeme použít obyčejnou celočíselnou konstantu (3) namísto jména dne (Ctvrtek) a navíc se operací day– získala hodnota, která neleží v očekávaném rozsahu:

Reservation for 0
Reservation for 5
Reservation for 6
Reservation for 3
Reservation for -1

Toto řešení je tedy nejvíce přímočaré, ale nemusí nám vyhovovat.

10. První krok: nový celočíselný typ použitý s identifikátorem iota

Existuje jeden velmi jednoduchý a elegantní způsob, jak zabránit tomu, aby celočíselné konstanty vytvořené identifikátorem iota byly typu int. Nejprve nadeklarujeme nový datový typ odvozený od int:

type Enum int

Posléze všechny konstanty vytvoříme, ovšem u první konstanty explicitně zapíšeme její datový typ:

const (
        Pondeli Enum = iota
        Utery
        Streda
        Ctvrtek
        Patek
        Sobota
        Nedele
)

Výsledkem bude, že se opět vytvoří sedm konstant s hodnotami 0, 1, 2, atd., ovšem tyto konstanty budou typu Enum a nikoli typu int. Interně se sice jedná o naprosto stejnou reprezentaci (běžné celé číslo), ovšem z předchozích článků již víme, že Go striktně vyžaduje explicitní konverze. To znamená, že následující funkci:

func reservation(day int) {
        fmt.Printf("Reservation for %d\n", day)
}

již nebude možné předat přímo nějakou konstantu:

reservation(Pondeli)

Můžeme se o tom snadno přesvědčit při pokusu o překlad tohoto příkladu:

package main
 
import "fmt"
 
type Enum int
 
const (
        Pondeli Enum = iota
        Utery
        Streda
        Ctvrtek
        Patek
        Sobota
        Nedele
)
 
func reservation(day int) {
        fmt.Printf("Reservation for %d\n", day)
}
 
func main() {
        reservation(Pondeli)
        reservation(Sobota)
        reservation(Nedele)
 
        reservation(3)
 
        day := Pondeli
        day--
        reservation(day)
}

Překladač jazyka Go vypíše chyby při pokusu o volání funkce reservartion s konstantou či proměnnou špatného typu:

./02_enum_with_iota_type_check.go:29:13: cannot use Pondeli (type Enum) as type int in argument to reservation
./02_enum_with_iota_type_check.go:30:13: cannot use Sobota (type Enum) as type int in argument to reservation
./02_enum_with_iota_type_check.go:31:13: cannot use Nedele (type Enum) as type int in argument to reservation
./02_enum_with_iota_type_check.go:37:13: cannot use day (type Enum) as type int in argument to reservation

Úprava příkladu je snadná, pouze přepíšeme hlavičku funkce:

func reservation(day Enum) {
        fmt.Printf("Reservation for %d\n", day)
}
package main
 
import "fmt"
 
type Enum int
 
const (
        Pondeli Enum = iota
        Utery
        Streda
        Ctvrtek
        Patek
        Sobota
        Nedele
)
 
func reservation(day Enum) {
        fmt.Printf("Reservation for %d\n", day)
}
 
func main() {
        reservation(Pondeli)
        reservation(Sobota)
        reservation(Nedele)
 
        reservation(3)
 
        day := Pondeli
        day--
        reservation(day)
}

S výsledky:

Reservation for 0
Reservation for 5
Reservation for 6
Reservation for 3
Reservation for -1

Zde můžeme vidět, že se nám sice zdrojový kód podařilo nepatrně vylepšit, ale stále je možné proměnnou typu Enum změnit (například operací –) tak, že nebude obsahovat korektní hodnotu, takže se o typově bezpečný datový typ výčet stále nejedná.

11. Převod zvolených celočíselných konstant na řetězec

V některých případech, například pro účely logování, je zapotřebí převádět konstanty představující hodnoty výčtového typu na řetězce. K tomuto účelu samozřejmě můžeme použít „obyčejnou“ funkci, ovšem elegantnější je deklarace metody:

func (day Enum) String() string {
        days := []string{
                "Pondeli",
                "Utery",
                "Streda",
                "Ctvrtek",
                "Patek",
                "Sobota",
                "Nedele"}
        if day < Pondeli || day > Nedele {
                return "Unknown day"
        }
        return days[day]
}
Poznámka: připomeňme si, že z funkce se stane metoda ve chvíli, kdy mezi klíčové slovo func a jméno metody vložíme tzv. příjemce, v našem případě argument typu Enum.

Opět si ukažme úplný zdrojový kód tohoto příkladu, v němž se provádí základní kontroly, zda předaná hodnota leží v očekávaném rozsahu:

package main
 
import "fmt"
 
type Enum int
 
const (
        Pondeli Enum = iota
        Utery
        Streda
        Ctvrtek
        Patek
        Sobota
        Nedele
)
 
func (day Enum) String() string {
        days := []string{
                "Pondeli",
                "Utery",
                "Streda",
                "Ctvrtek",
                "Patek",
                "Sobota",
                "Nedele"}
        if day < Pondeli || day > Nedele {
                return "Unknown day"
        }
        return days[day]
}
 
func reservation(day Enum) {
        fmt.Printf("Reservation for %s\n", day.String())
}
 
func main() {
        reservation(Pondeli)
        reservation(Sobota)
        reservation(Nedele)
 
        reservation(3)
 
        day := Pondeli
        day--
        reservation(day)
}

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

Reservation for Pondeli
Reservation for Sobota
Reservation for Nedele
Reservation for Ctvrtek
Reservation for Unknown day

12. Využití struktury/záznamu pro typově zabezpečené operace s výčtem

Při tvorbě datového typu podobného typu výčtovému můžeme jít ještě o jeden krok dále a použít tuto deklaraci:

type Enum int
 
type Den struct {
        X Enum
}

Touto deklarací vlastně říkáme, že typ Den je datová struktura s jediným členem typu Enum pojmenovaným X. Díky tomu se vlastně interní hodnoty „uschovají“ do struktury, což je dobře, protože zápisy typu:

day := Den{Pondeli}
day++

přestanou být korektní a nebude možné použít jiné hodnoty než definované (pokud přímo nepřistoupíme k prvku struktury).

Opět se podívejme na upravený demonstrační příklad, který nyní vypadá následovně:

package main
 
import "fmt"
 
type Enum int
 
const (
        Pondeli Enum = iota
        Utery
        Streda
        Ctvrtek
        Patek
        Sobota
        Nedele
)
 
type Den struct {
        X Enum
}
 
func (day Enum) String() string {
        days := []string{
                "Pondeli",
                "Utery",
                "Streda",
                "Ctvrtek",
                "Patek",
                "Sobota",
                "Nedele"}
        if day < Pondeli || day > Nedele {
                return "Unknown day"
        }
        return days[day]
}
 
func reservation(day Den) {
        fmt.Printf("Reservation for %s\n", day)
}
 
func main() {
        reservation(Den{Pondeli})
        reservation(Den{Sobota})
        reservation(Den{Nedele})
 
        day := Den{Pondeli}
        reservation(day)
}

Příklad nyní pracuje správně (případné složené závorky lze snadno odstranit – což ponechám váženému čtenáři za domácí úkol):

Reservation for {Pondeli}
Reservation for {Sobota}
Reservation for {Nedele}
Reservation for {Pondeli}

Nyní si otestujeme, že skutečně nebude možné měnit hodnotu našeho výčtového typu:

package main
 
import "fmt"
 
type Enum int
 
const (
        Pondeli Enum = iota
        Utery
        Streda
        Ctvrtek
        Patek
        Sobota
        Nedele
)
 
type Den struct {
        X Enum
}
 
func (day Enum) String() string {
        days := []string{
                "Pondeli",
                "Utery",
                "Streda",
                "Ctvrtek",
                "Patek",
                "Sobota",
                "Nedele"}
        if day < Pondeli || day > Nedele {
                return "Unknown day"
        }
        return days[day]
}
 
func reservation(day Den) {
        fmt.Printf("Reservation for %s\n", day)
}
 
func main() {
        reservation(Den{Pondeli})
        reservation(Den{Sobota})
        reservation(Den{Nedele})
 
        day := Den{Pondeli}
        day++
        reservation(day)
}

Při pokusu o překlad day++ překladač jazyka Go zahlásí chybu:

./06_enum_as_type.go:53:5: invalid operation: day++ (non-numeric type Den)

13. Kanály s předdefinovanou maximální kapacitou fronty

Dalším doplňujícím tématem dnešního článku je zmínka o kanálech s nakonfigurovanou kapacitou fronty (FIFO, v kontextu kanálů se mluví o bufferu). Připomeňme si, že pokud je kanál vytvořen následujícím způsobem:

channel := make(chan rune)

bude mít jeho fronta/buffer nulovou kapacitu a tudíž se kanál bude chovat podobně jako mailbox. Ovšem kapacitu kanálu (resp. jeho FIFA) můžeme zvýšit zápisem druhého (nepovinného) parametru do volání funkce make:

channel := make(chan rune, 1234)

Teoreticky je kapacita omezena tím, že je reprezentována typem int, prakticky však pochopitelně narazíme na limity, které nejvíce souvisí s maximálním množstvím paměti, která může být procesu přidělena.

Podívejme se na příklad, v němž je vytvořen kanál s kapacitou bufferu/FIFA pro tři znaky. V tomto příkladu se nevytváří ani nevolá žádná nová gorutina – všechny operace s kanálem jsou součástí jediné (hlavní) gorutiny, která používá kanál pro implementaci fronty (queue):

channel := make(chan rune)
package main
 
import (
        "fmt"
)
 
func main() {
        channel := make(chan rune, 3)
        channel <- 'A'
        channel <- 'B'
        channel <- 'C'
 
        for i := 0; i < 3; i++ {
                fmt.Printf("%c ", <-channel)
        }
}

Po překladu a spuštění tohoto příkladu by se měl vypsat tento řádek:

A B C
Poznámka: tento způsob použití kanálu nemusí být příliš efektivní; ostatně si sami můžete pomocí profileru vyzkoušet a porovnat tento příklad s jinou implementací datového typu fronta.

14. Použití konstrukce range při čtení dat z kanálu

Dalším užitečným trikem, který je možné využít při práci s kanály, je čtení hodnot z kanálu v programové smyčce for s konstrukcí range. Celý zápis vypadá následovně:

for msg := range channel {
        fmt.Printf("%c ", msg)
}

Tato programová smyčka skončí až ve chvíli, kdy dojde k uzavření kanálu! Pokud kanál není uzavřen a jeho fronta je prázdná, smyčka bude buď čekat na data nebo runtime programovacího jazyka Go bude detekovat stav, kdy neběží žádná gorutina a program bude násilně ukončen. Tuto vlastnost si opět můžeme velmi snadno odzkoušet:

package main
 
import (
        "fmt"
)
 
func fill_in_channel(channel chan rune) {
        channel <- 'A'
        channel <- 'B'
        channel <- 'C'
}
 
func main() {
        channel := make(chan rune, 3)
 
        go fill_in_channel(channel)
 
        for msg := range channel {
                fmt.Printf("%c ", msg)
        }
}

Pokud tento program spustíme, dojde k postupnému naplnění kanálu třemi prvky, které budou (opět postupně) čteny v programové smyčce. Následně runtime jazyka Go zjistí, že existuje jen jediná gorutina (hlavní) a ta čeká na další data – ty evidentně nemá kde získat a proto bude program násilně ukončen:

A B C fatal error: all goroutines are asleep - deadlock!
 
goroutine 1 [chan receive]:
main.main()
        /home/tester/temp/out/go-root/article_08/08_channel_and_range.go:25 +0xe7
exit status 2

Úprava je ve skutečnosti velmi snadná – v námi vytvořené gorutině (v ní se volá funkce fill_in_channel) nejprve do kanálu pošleme tři prvky a posléze kanál uzavřeme voláním:

close(channel)

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

package main
 
import (
        "fmt"
)
 
func fill_in_channel(channel chan rune) {
        channel <- 'A'
        channel <- 'B'
        channel <- 'C'
        close(channel)
}
 
func main() {
        channel := make(chan rune, 3)
 
        go fill_in_channel(channel)
 
        for msg := range channel {
                fmt.Printf("%c ", msg)
        }
}

Po spuštění zjistíme, že se program skutečně chová tak, jak je očekáváno: k žádným pádům nedochází a hlavní gorutina je po příjmu tří prvků korektně ukončena:

A B C

15. Chování uzavřeného kanálu při čtení hodnot

Pro úplnost si ukažme, jak se chová uzavřený kanál ve chvíli, kdy z něj čteme další hodnoty. V tomto případě se nejedná o blokující operace (kanál je uzavřen, žádná data do něj již nelze poslat), proto je chování upraveno takovým způsobem, že pokus o přečtení dat z uzavřeného kanálu vrátí výchozí hodnotu platnou pro ten datový typ, jaký odpovídá typu prvků kanálu. V praxi to znamená, že pro uzavřený kanál chan int získáme po přečtení sadu nul:

package main
 
import (
        "fmt"
)
 
func main() {
        channel := make(chan int, 3)
        close(channel)
 
        fmt.Printf("%d\n", <-channel)
        fmt.Printf("%d\n", <-channel)
        fmt.Printf("%d\n", <-channel)
}

Chování příkladu po spuštění:

0
0
0
Poznámka: k detekci tohoto stavu slouží druhá hodnota vrácená operátorem ← . Pokud je tato druhá hodnota rovna false, je kanál uzavřen.

16. Uzavření kanálu jako součást synchronizační konstrukce

Výše popsané chování:

  1. Čtení z prázdného kanálu je blokující operace.
  2. Čtení z uzavřeného kanálu skončí ihned (a vrátí se nula)

je možné využít k tomu, aby kanál sloužil jako synchronizační konstrukce, a to bez toho, aby se do něj provedl byť i jediný zápis. V následujícím příkladu je vytvořena nová gorutina představovaná anonymní funkcí. Na dokončení běhu této gorutiny čekáme příkazem:

<-done

Vzhledem k tomu, že do kanálu nebyl proveden zápis, je výše uvedený příkaz blokován až do doby, kdy je kanál uzavřen:

package main
 
func main() {
        done := make(chan bool)
 
        go func() {
                println("async block")
                close(done)
        }()
 
        println("wait for async block")
        <-done
}

Po spuštění příkladu je patrné, že se skutečně čeká na dokončení gorutiny (v opačném případě je program ukončen již při opuštění funkce main):

wait for async block
async block

17. Konstrukce select s definicí maximální doby čekání na data

Další užitečnou konstrukcí, která souvisí s kanály a komunikací mezi gorutinami, je čekání na data v příkazu select. Pokud čekáme pouze na data z libovolného množství kanálů, jedná se o blokující operaci, která může trvat libovolně dlouho:

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

V případě, že potřebujeme, aby byla celá programová konstrukce select po nějaké době ukončena, nezávisle na tom, zda jsme data přečetli či nikoli, použijeme tuto větev (přidanou k ostatním větvím):

case <-time.After(2 * time.Second):
        fmt.Println("Timeout!")
}

Ve skutečnosti se nejedná o žádnou magii, protože funkce time.After() vrací kanál typu chan Time, do něhož je proveden jediný zápis, a to po uběhnutí specifikované doby.

Celý příklad, v němž se snažíme o přečtení dat z kanálu ch1 nebo ch2, ovšem s definovanou maximální dobou čekání, může vypadat následovně:

package main
 
import (
        "fmt"
        "time"
)
 
func worker(channel chan int, worker int) {
        fmt.Printf("Worker %d spuštěn\n", worker)
        time.Sleep(1 * 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")
        case <-time.After(2 * time.Second):
                fmt.Println("Timeout!")
        }
}

Chování po spuštění – timeout nebyl použit:

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

Nepatrnou úpravou zdrojového kódu můžeme dosáhnout toho, aby byla větev s timeoutem skutečně použita; postačuje pouze zvýšit čas ve volání time.Sleep() v implementaci gorutiny:

package main
 
import (
        "fmt"
        "time"
)
 
func worker(channel chan int, worker int) {
        fmt.Printf("Worker %d spuštěn\n", worker)
        time.Sleep(5 * 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")
        case <-time.After(2 * time.Second):
                fmt.Println("Timeout!")
        }
}

Nyní bude chování příkladu po jeho spuštění odlišné:

Worker 1 spuštěn
Worker 2 spuštěn
Timeout!

18. Definice čekání na data a větev default

Jen pro úplnost si ukažme nesmyslný příklad (syntakticky je správně, sémanticky už nikoli), v němž v programové konstrukci select použijeme jak větev se čtením z kanálu zkonstruovaného pomocí time.After(), tak i větev default. V tomto případě se větev time.After() nikdy nepoužije, protože se ve skutečnosti neprovádí žádné čekání, pouze výběr případných dat z jednoho dostupného kanálu. V případě, že data k dispozici nejsou, provede se ihned kód ve větvi default:

package main
 
import (
        "fmt"
        "time"
)
 
func worker(channel chan int, worker int) {
        fmt.Printf("Worker %d spuštěn\n", worker)
        time.Sleep(5 * 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")
        case <-time.After(2 * time.Second):
                fmt.Println("Timeout!")
        default:
                fmt.Println("No data!")
        }
}
Worker 1 spuštěn
No data!

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_enum_with_iota.go první (nedostatečná) implementace výčtového typu https://github.com/tisnik/go-fedora/blob/master/article08/01_e­num_with_iota.go
2 02_enum_with_iota_type_check.go typové kontroly u celočíselných konstant (nekorektní použití) https://github.com/tisnik/go-fedora/blob/master/article08/02_e­num_with_iota_type_check.go
3 03_enum_with_iota.go typová kontrola a její korektní použití https://github.com/tisnik/go-fedora/blob/master/article08/03_e­num_with_iota.go
4 04_enum_with_iota.go převod výčtového typu na řetězec https://github.com/tisnik/go-fedora/blob/master/article08/04_e­num_with_iota.go
5 05_enum_as_type.go náhrada výčtového typu strukturou https://github.com/tisnik/go-fedora/blob/master/article08/05_e­num_as_type.go
6 06_enum_as_type.go náhrada výčtového typu strukturou, pokus o provedení nepovolených operací https://github.com/tisnik/go-fedora/blob/master/article08/06_e­num_as_type.go
7 07_channel_as_fifo.go použití kanálu ve funkci FIFO https://github.com/tisnik/go-fedora/blob/master/article08/07_chan­nel_as_fifo.go
8 08_channel_and_range.go kanály a klíčové slovo range https://github.com/tisnik/go-fedora/blob/master/article08/08_chan­nel_and_range.go
9 09_channel_and_range.go kanály a klíčové slovo range https://github.com/tisnik/go-fedora/blob/master/article08/09_chan­nel_and_range.go
10 10_read_from_closed_channel.go pokus o čtení ze zavřeného kanálu https://github.com/tisnik/go-fedora/blob/master/article08/10_re­ad_from_closed_channel.go
11 11_sync_with_channel.go synchronizace pomocí uzavření kanálu https://github.com/tisnik/go-fedora/blob/master/article08/11_syn­c_with_channel.go
12 12_select_statement_receive.go specifikace maximální doby čekání na data https://github.com/tisnik/go-fedora/blob/master/article08/12_se­lect_statement_receive.go
13 13_select_statement_receive.go specifikace maximální doby čekání na data https://github.com/tisnik/go-fedora/blob/master/article08/13_se­lect_statement_receive.go
14 14_select_statement_receive.go specifikace timeoutu v select + větev default https://github.com/tisnik/go-fedora/blob/master/article08/14_se­lect_statement_receive.go

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
Našli jste v článku chybu?