Konstrukce pro řízení běhu programu v jazyce Go (dokončení)

3. 1. 2019
Doba čtení: 38 minut

Sdílet

 Autor: Go Lang
Po popisu řídicích struktur používaných pro klasické strukturované programování (podmínky a smyčky) si dnes představíme dva příkazy pro „nestrukturované“ ovlivnění běhu programu. Jedná se o příkazy goto a defer.

Obsah

1. Konstrukce pro řízení běhu programu v jazyce Go (dokončení)

2. Další možnosti použití příkazu goto: výskok z vnořených smyček a výskok ze struktury switch

3. Příklady nekorektního použití příkazu goto

4. Jak často se goto používá v reálných programech?

5. Výsledky analýz zdrojových kódů

6. Příkaz defer

7. Volání funkce s parametry v bloku defer

8. Pořadí volání funkcí zaregistrovaných s využitím příkazu defer

9. Vyhodnocení parametrů funkcí zaregistrovaných příkazem defer

10. Defer a příkaz return

11. Praktické použití příkazu defer

12. Ovlivnění návratové hodnoty funkce v bloku defer

13. Funkce panic

14. Funkce recover

15. Výrazy a operátory v programovacím jazyce Go

16. Unární operátory

17. Relační operátory

18. Dělení, zbytek po dělení a bitové posuny

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

20. Odkazy na Internetu

1. Konstrukce pro řízení běhu programu v jazyce Go (dokončení)

V pořadí již šestý článek o programovacím jazyce Go je rozdělen do tří částí. V úvodní části se seznámíme s příkazem goto a zejména se situacemi, v nichž se tento příkaz nemá používat, protože Go v mnoha případech programátorům nabízí lepší výrazové prostředky. Uvidíme také, že použití příkazu goto v reálných zdrojových kódech je ve skutečnosti dosti nízké. Ve druhé části článku se budeme věnovat důležité problematice, a tou je řešení potenciálních chybových stavů v aplikacích. V programovacím jazyce Go se chyby musí testovat explicitně, ovšem pro případné uvolnění prostředků a zotavení se používá zvláštní konstrukce nazvaná defer (nalezneme ji ovšem například i v jazyce Swift atd.) společně s funkcemi panic() a recover(). Závěrečná část článku je věnována problematice použití operátorů v programovacím jazyce Go.

Podívejme se nejdříve na příkaz goto. S tímto příkazem se můžeme velmi často setkat v těch programovacích jazycích, které goto využívají namísto skutečných strukturovaných příkazů. Mezi tyto jazyky patří především starší dialekty BASICu; viz též články, v nichž jsme se těmito dnes již vlastně prehistorickými jazyky zabývali [1] [2] [3] [4]. V dalších programovacích jazycích, které strukturované konstrukce obsahují, se goto (pokud vůbec existuje) používá méně často, například při výskoku z vnořených smyček, při obsluze chybových stavů, při implementaci programových smyček s testem uprostřed či při implementaci konečného automatu.

Poznámka: příkaz goto je možné doimplementovat například i do Pythonu, pro nějž dokonce vznikla i podpůrná knihovna https://github.com/snoack/python-goto/. Příkaz goto se objevuje i v programovacím jazyku Lua.

Příkaz goto se ve své nejjednodušší (a nutno říci, že i nejzbytečnější variantě) může napsat následujícím způsobem:

package main
 
import "fmt"
 
func main() {
        i := 10
Next_i:
        fmt.Printf("%2d\n", i)
        i--
        if i >= 0 {
                goto Next_i
        }
}

V tomto příkladu je ukázána realizace počítané programové smyčky stylem, jaký možná pamatujete z osmibitových BASICů:

10
 9
 8
 7
 6
 5
 4
 3
 2
 1
 0

Povšimněte si, že se v Go používají pojmenovaná návěští (label), na rozdíl od již zmíněných starších BASICů či Pascalu, kde byla návěští jen celočíselná (což ještě více znepříjemňuje praktické použití goto).

2. Další možnosti použití příkazu goto: výskok z vnořených smyček a výskok ze struktury switch

V programovacím jazyce Go je možné příkaz goto použít pro výskok z vnořených smyček, popř. (s čímž se setkáme častěji) pro výskok z konstrukce switch. S prvním zmíněným použitím se můžeme setkat s mnohem menší frekvencí, než je tomu v C, protože Go podporuje (jak již víme) příkaz break návěští. Pokud budete chtít přesto goto pro tento účel, což není obecně doporučováno, bude celá struktura vypadat následovně:

package main
 
import "fmt"
 
func main() {
        for i := 1; i <= 10; i++ {
                for j := 1; j <= 10; j++ {
                        fmt.Printf("%3d ", i*j)
                        if i*j == 42 {
                                fmt.Println("\nodpověď nalezena!\n")
                                goto Exit
                        }
                }
                fmt.Println()
        }
Exit:
}

Výsledek by měl po spuštění tohoto příkladu vypadat takto:

  1   2   3   4   5   6   7   8   9  10
  2   4   6   8  10  12  14  16  18  20
  3   6   9  12  15  18  21  24  27  30
  4   8  12  16  20  24  28  32  36  40
  5  10  15  20  25  30  35  40  45  50
  6  12  18  24  30  36  42
odpověď nalezena!

Výskok (dokonce několikanásobný) z konstrukce switch lze příkazem goto realizovat následujícím způsobem:

package main
 
func classify(x int) string {
        switch x {
        case 0:
                return "nula"
        case 2, 4, 6, 8:
                goto SudeCislo
        case 1, 3, 5, 7, 9:
                goto LicheCislo
        default:
                goto JineCislo
        }
JineCislo:
        return "?"
SudeCislo:
        return "sudé číslo"
LicheCislo:
        return "liché číslo"
}
 
func main() {
        for x := 0; x <= 10; x++ {
                println(x, classify(x))
        }
}

Po spuštění tohoto příkladu by se na terminálu měly objevit následující řádky:

0 nula
1 liché číslo
2 sudé číslo
3 liché číslo
4 sudé číslo
5 liché číslo
6 sudé číslo
7 liché číslo
8 sudé číslo
9 liché číslo
10 ?

3. Příklady nekorektního použití příkazu goto

V této kapitole si ukážeme příklady, v nichž se naopak příkaz goto nesmí použít, protože by případný skok přeskočil některou důležitou část programu.

Příkazem goto se nesmí přeskočit deklarace proměnné, kterou následně použijeme. Následující demonstrační příklad tedy nepůjde přeložit:

package main
 
import "fmt"
 
func main() {
        goto Next
        i := 10
Next:
        fmt.Printf("%2d\n", i)
}

Při pokusu o překlad dostaneme toto chybové hlášení:

./04_goto_bad_usage_A.go:13:7: goto Next jumps over declaration of i at ./04_goto_bad_usage_A.go:14:4

Taktéž není možné skočit do programové smyčky, popř. do větve strukturovaných příkazů if a switch. Z tohoto důvodu nebude možné přeložit ani další program:

package main
 
import "fmt"
 
func main() {
        i := 10
        goto IntoIf
        if i > 0 {
        IntoIf:
                fmt.Printf("%2d\n", i)
        }
}

Překlad opět skončí s chybou:

./05_goto_bad_usage_B.go:14:7: goto IntoIf jumps into block starting at ./05_goto_bad_usage_B.go:15:11

A ani třetí program, který se snaží o skok do programové smyčky, není korektní. Namísto bezproblémového překladu se totiž objeví stejné chybové hlášení jako v příkladu předchozím:

package main
 
import "fmt"
 
func main() {
        goto IntoLoop
 
        for i := 0; i < 10; i++ {
        IntoLoop:
                fmt.Printf("%2d\n", i)
        }
}

Chybové hlášení překladače:

./06_goto_bad_usage_C.go:13:7: goto IntoLoop jumps into block starting at ./06_goto_bad_usage_C.go:15:26

Už vůbec není dovoleno skočit do jiné funkce:

package main
 
func a() {
        goto FuncB
}
 
func b() {
FuncB:
}
 
func main() {
}
package main
 
func a() {
        goto FuncB
}
 
func b() {
FuncB:
}
 
func main() {
}

Chybové hlášení překladače dokonce ukazuje na dvě chyby (nepoužité a neexistující návěští – ta jsou totiž „izolována“ v rámci jedné funkce):

./06_goto_bad_usage_D.go:11:7: label FuncB not defined
./06_goto_bad_usage_D.go:15:1: label FuncB defined and not used

Příklad izolace návěští v rámci jednotlivých funkcí:

package main
 
func a() {
        goto Label
Label:
}
 
func b() {
        goto Label
Label:
}
 
func main() {
}

4. Jak často se goto používá v reálných programech?

V mnoha studijních materiálech se můžeme dočíst, že použití příkazu goto je známkou špatného programátorského stylu. Tento názor, který je reprezentován známým a velmi často citovaným článkem Go To Statement Considered Harmful vznikl v dobách, kdy mnohé programovací jazyky (a nejedná se zdaleka pouze o zmíněný BASIC) neobsahovaly všechny konstrukce nutné pro strukturované programování. Na druhou stranu se ovšem nezmiňují ty případy, kdy má použití goto smysl, tj. zejména řešení algoritmů typu implementace konečného automatu, řešení některých podmínek diskutovaných například v kódu kernelu apod.

Příkaz goto má dnes význam pouze tam, kde již nedostačují další konstrukce jazyka – což je ovšem v Go relativně malé množství případů, určitě menší než například v programovacím jazyku C (vzhledem k chybějící konstrukci pro výskok z více smyček a taktéž kvůli chybějícímu defer).

Ostatně si toto tvrzení můžeme relativně snadno ověřit, a to konkrétně na zdrojových kódech, které jsou součástí samotné instalace Go. Tyto kódy nalezneme v podadresáři go. Následující skript je sice poměrně primitivní a ne zcela korektní (nerozlišuje víceřádkové poznámky), ovšem dokáže velmi rychle zjistit frekvenci použití jednotlivých klíčových slov programovacího jazyka Go ve všech zdrojových kódech:

#!/bin/bash
 
KEYWORDS="
break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var"
 
for keyword in $KEYWORDS
do
    grep -h -v "//" `find . -name *.go` | grep "\b$keyword\b" | wc -l | tr '\n' ' '
    echo $keyword
done | sort -n
Poznámka: povšimněte si, že ze zdrojových kódu odstraňujeme pouze jednořádkové komentáře (a to ještě špatně ve chvíli, kdy budou součástí řetězce). Ovšem skript je skutečně dostatečně rychlý a jak uvidíme v další kapitole, nedává zcela špatné výsledky.

Druhý skript, tentokrát naprogramovaný v Pythonu, je korektní, protože využívá tokenizaci podporovanou knihovnou Pygments (viz též příslušné články na Rootu: [1] [2]. Ovšem právě kvůli tokenizaci je běh tohoto skriptu mnohem pomalejší:

import os
from glob import iglob
from collections import defaultdict
 
from pygments.lexer import *
from pygments.token import *
from pygments.lexers import get_lexer_by_name
 
 
# slovnik s citacem frekvenci vsech klicovych slov
keywords_freq = defaultdict(lambda: 0)
 
# lexer pro programovaci jazyk Go
lexer = get_lexer_by_name('go')
 
 
def all_sources(path, pattern):
    """Ziskani seznamu souboru na PATH a v rekurzivne i v podadresarich."""
    path = os.path.join(path, "**/", pattern)
    sources = [filename for filename in iglob(path, recursive=True)]
    return sources
 
 
def keywords_for_file(filename):
    """Ziskani seznamu klicovych slov nalezenych ve specifikovanem souboru."""
    with open(filename) as fin:
        source = fin.read()
        tokens = lexer.get_tokens(source)
 
    keywords = []
    for token in tokens:
        t_type = token[0]
        if t_type is Token.Keyword or t_type in Token.Keyword.Namespace or t_type in Token.Keyword.Declaration:
            keywords.append(token[1])
    return keywords
 
 
def print_frequency_table(keywords_freq):
    """Tisk tabulky s frekvenci klicovych slov."""
    # sort tridi n-tice nejprve podle prvniho prvku,
    # takze musime slovnik prevest na sekvenci n-tic (frekvence, klicove_slovo)
    swapped = [(v, k) for k, v in keywords_freq.items()]
    swapped.sort()  # reverse=True pro opacne trideni
 
    for counter, keyword in swapped:
        print(counter, keyword)
 
 
for source in all_sources("src/", "*.go"):
    print(source)
    for keyword in keywords_for_file(source):
        keywords_freq[keyword] += 1
 
print("\n\n")
 
print_frequency_table(keywords_freq)

5. Výsledky analýz zdrojových kódů

Podívejme se nyní na výsledky analýz všech zdrojových kódů nalezených v podadresáři src. V první tabulce jsou uvedeny výsledky získané prvním skriptem, které jsou vzestupně setříděné podle frekvence jednotlivých klíčových slov:

Počet použití Klíčové slovo
189 fallthrough
550 goto
605 select
1711 chan
2346 interface
2412 default
3128 map
3443 continue
3831 import
3946 defer
4433 switch
4709 go
4929 const
5308 package
7125 else
9418 range
9885 struct
12313 type
19094 var
22073 case
29449 break
29736 for
57261 func
77351 return
111163 if

Můžeme vidět, že nejméně používané je klíčové slovo fallthrough, které je v jazyce Go používáno ve chvíli, kdy mají být spojeny dvě větve case v příkazu switch. To mimochodem dokazuje, že tvůrci Go udělali dobře, když změnili sémantiku konstrukce switch (těla větví na sebe ve výchozím stavu nenavazují na rozdíl od C/C++/Javy atd.).

Klíčové slovo goto se zde umístilo na pěkném předposledním místě.

Poznámka: mimochodem je zajímavé, že se ve vybrané skupině zdrojových kódů příliš nepoužívá klíčové slovo go, i když se jedná o jeden z nejužitečnějších prvků tohoto programovacího jazyka. Ovšem nic nám nebrání použít stejné skripty pro získání statistik zdrojových kódů serverů a dalších aplikací, v nichž se gorutiny používají častěji.

Druhý skript (tentokrát psaný v Pythonu) získal nepatrně odlišné hodnoty, ovšem goto stále patří mezi nejméně používaná klíčová slova:

Počet použití Klíčové slovo
186 fallthrough
480 select
547 goto
1268 go
1617 chan
2162 interface
2184 default
3018 map
3105 import
3456 continue
3937 package
3942 defer
4343 switch
4781 const
6899 else
8887 range
9406 struct
10419 type
18208 var
22310 case
27455 for
29444 break
57573 func
76955 return
110707 if

Můžeme vidět, že se příkaz goto použije průměrně pouze jedenkrát na každých cca 200 příkazů if (a to se navíc jedná o nízkoúrovňové zdrojové kódy, v nichž se mnohdy dává přednost optimálnímu kódu před kódem čitelným a udržovatelným).

Grafické znázornění frekvence výskytu jednotlivých klíčových slov ve zdrojových kódech Go.

Poznámka: v první tabulce je frekvence jednotlivých klíčových slov nepatrně vyšší právě z toho důvodu, že jsou slova hledána i ve víceřádkových komentářích a v řetězcích.

6. Příkaz defer

Dalším příkazem, který slouží k ovlivnění toku programu (control flow) je příkaz reprezentovaný klíčovým slovem defer. Tímto příkazem je možné vložit požadavek na volání nějaké funkce do seznamu, jehož obsah se vykoná při odchodu z té funkce, v níž je příkaz defer použit. Předchozí věta sice může vypadat poměrně složitě, zvláště pokud jste se s tímto konceptem nesetkali, ale samotné použití defer je poměrně přímočaré. Podívejme se nejdříve na zdrojový kód následujícího demonstračního příkladu:

package main
 
import "fmt"
 
func on_finish() {
        fmt.Println("Finished")
}
 
func main() {
        defer on_finish()
 
        for i := 10; i >= 0; i-- {
                fmt.Printf("%2d\n", i)
        }
        fmt.Println("Finishing main() function")
}

Vidíme, že ve funkci main je použit příkaz defer, kterým požadujeme, aby se při ukončování funkce main zavolala jiná funkce, konkrétně funkce on_finish.

Po spuštění aplikace se nejdříve desetkrát vypíše obsah počitadla i a následně se vykoná poslední příkaz funkce main:

        fmt.Println("Finishing main() function")

Až do této chvíle tedy neměl příkaz defer na běh programu žádný vliv, ovšem poté, co se vykoná poslední explicitně zapsaný příkaz v main, se zavolá funkce on_finish:

10
 9
 8
 7
 6
 5
 4
 3
 2
 1
 0
Finishing main() function
Finished

Vzhledem k tomu, že se v praxi setkáme s poměrně krátkým kódem, který se má na konci nějaké funkce zavolat, se často používá příkaz defer spojený s deklarací funkce:

package main
 
import "fmt"
 
func main() {
        defer (func() { fmt.Println("Finished") })()
 
        for i := 10; i >= 0; i-- {
                fmt.Printf("%2d\n", i)
        }
        fmt.Println("Finishing main() function")
}
Poznámka: kulaté závorky na konci jsou nutné, protože v defer přímo zapisujeme volání funkce, nikoli pouze její deklaraci.

Běh tohoto programu je naprosto stejný, jako v předchozím případě:

10
 9
 8
 7
 6
 5
 4
 3
 2
 1
 0
Finishing main() function
Finished

Nyní tedy víme (alespoň ve stručnosti), jak se příkaz defer používá, ale musíme si samozřejmě říct i proč vůbec v jazyku Go existuje. V mnoha programech je nutné nějakým způsobem a za jakýchkoli okolností uzavírat nějaké prostředky (připojení k databázi, otevřené připojení ke klientovi, otevřený soubor atd.), a právě toto uzavírání lze realizovat v bloku či v blocích defer. Jedná se vlastně o zobecnění bloku finally používaného v programové konstrukci try-catch-finally, kterou jazyk Go (alespoň v současné stabilní verzi) nepoužívá.

Poznámka: defer s funkcí pro uzavření nějakého prostředku (souboru, …) do zdrojového kódu zapište na nejbližší logické místo, například ihned po příkazu, který soubor otevírá. Tak dáte případným čtenářům najevo, jakým způsobem ošetřujete chyby – toto s klasickými bloky try-catch-finally obecně nelze provést.

7. Volání funkce s parametry v bloku defer

Při specifikaci volání funkce, která se spustit v rámci bloku defer, je samozřejmě možné této funkci předat nějaké parametry. V dalším demonstračním příkladu je ukázáno, jak se změní zápis:

defer on_finish("Finished")

Vidíme, že ze syntaktického hlediska se před běžné volání funkce pouze přidá klíčové slovo defer. Je to podobně jednoduché jako zavolání funkce v rámci nově vytvořené gorutiny s využitím klíčového slova go.

Úplný zdrojový kód příkladu bude vypadat takto:

package main
 
import "fmt"
 
func on_finish(message string) {
        fmt.Println(message)
}
 
func main() {
        defer on_finish("Finished")
 
        for i := 10; i >= 0; i-- {
                fmt.Printf("%2d\n", i)
        }
        fmt.Println("Finishing main() function")
}

Po spuštění se nejdříve vypíše obsah počitadla smyčky for, následně se vykoná poslední příkaz ve funkci main a poté se zavolá funkce on_finish:

10
 9
 8
 7
 6
 5
 4
 3
 2
 1
 0
Finishing main() function
Finished

8. Pořadí volání funkcí zaregistrovaných s využitím příkazu defer

Nic nám nebrání použít v rámci jedné funkce hned několik příkazů defer. Zajímavé bude ovšem zjistit, jak se bude tato funkce chovat při spuštění programu, tj. v runtime. Můžeme si to snadno otestovat, protože již víme, že v bloku defer je možné funkci (či obecně kódu zde zapsaném) předat parametry. To mj. znamená, že můžeme defer použít například v programové smyčce. Viz zdrojový kód dnešního dalšího demonstračního příkladu s deseti deklaracemi defer:

package main
 
import "fmt"
 
func on_finish(i int) {
        fmt.Printf("Defer #%2d\n", i)
}
 
func main() {
        for i := 0; i <= 10; i++ {
                defer on_finish(i)
        }
        fmt.Println("Finishing main() function")
}

Po ukončení posledního příkazu zapsaného do funkce main se desetkrát zavolá funkce on_finish, ovšem v opačném pořadí (nejprve s parametrem 10, poté s parametrem 9 atd.). Je tomu tak z toho důvodu, že se příkazy specifikované v defer ukládají do pomyslného zásobníku:

Finishing main() function
Defer #10
Defer # 9
Defer # 8
Defer # 7
Defer # 6
Defer # 5
Defer # 4
Defer # 3
Defer # 2
Defer # 1
Defer # 0
Poznámka: toto chování není samoúčelné, ale má svůj smysl v případě, že v rámci defer budeme chtít uzavírat soubory, připojení k databázi, vytvoření session v databázi atd., a to v opačném pořadí, než jak došlo k jejich otevření.

9. Vyhodnocení parametrů funkcí zaregistrovaných příkazem defer

Nyní již víme, že v bloku defer lze funkcím předávat parametry a že se kód zapsaný ve více deklaracích defer nakonec zavolá v opačném pořadí. Je tedy namístě se zamyslet nad tím, v jakém okamžiku se vlastně vyhodnocují parametry použité v defer. Chování jazyka Go si opět můžeme ověřit na jednoduchém příkladu, v němž je příkaz defer použit dvakrát – pokaždé se v něm volá stejná funkce a pokaždé se této funkci předává hodnota lokální proměnné x. Zajímat nás bude, jaká hodnota x se vlastně použije. Nejprve si ukažme chování pro proměnnou typu int:

package main
 
import "fmt"
 
func function(i int) {
        fmt.Printf("Defer %2d\n", i)
}
 
func main() {
        x := 0
 
        fmt.Printf("Current x value = %2d\n", x)
        defer function(x)
 
        x++
 
        fmt.Printf("Current x value = %2d\n", x)
        defer function(x)
 
        x++
        fmt.Printf("Current x value = %2d\n", x)
}

Po spuštění programu dostaneme tyto výsledky:

Current x value =  0
Current x value =  1
Current x value =  2
Defer  1
Defer  0

Mohlo by se tedy zdát, že se vždy použije aktuální hodnota proměnné x ve chvíli, kdy je příkaz defer zavolán. Ve skutečnosti to ovšem není vždy tak jednoduché, protože musíme počítat s tím, že se v programovacím jazyce Go používají i reference (odkazy). V nepatrně upraveném příkladu použijeme řez polem (interně řez obsahuje referenci na bázové pole):

package main
 
import "fmt"
 
func function(a []int) {
        fmt.Printf("Defer %v\n", a)
}
 
func main() {
        var x = []int{1, 2, 3}
 
        fmt.Printf("Current x value = %v\n", x)
        defer function(x)
 
        x[0] = 0
 
        fmt.Printf("Current x value = %v\n", x)
        defer function(x)
 
        x[1] = 0
 
        fmt.Printf("Current x value = %v\n", x)
        defer function(x)
 
        x[2] = 0
 
        fmt.Printf("Current x value = %v\n", x)
}

Po spuštění zjistíme, že funkce volané v rámci bloku defer vidí až výsledný obsah pole. To je sice pochopitelné chování, ale v praxi si na něj musíme dát pozor:

Current x value = [1 2 3]
Current x value = [0 2 3]
Current x value = [0 0 3]
Current x value = [0 0 0]
Defer [0 0 0]
Defer [0 0 0]
Defer [0 0 0]

10. Defer a příkaz return

Funkce volaná v rámci bloku defer se pochopitelně zavolá i tehdy, pokud použijeme výskok z funkce příkazem return; nezávisle na tom, kolik výskoků funkce ve skutečnosti obsahuje. Příklad, s nímž jsme se seznámili v předchozím článku, je možné nepatrně upravit přidáním příkazu defer do funkce classify:

package main
 
import "fmt"
 
func function(x int) {
        fmt.Printf("Defer %d\n", x)
}
 
func classify(x int) string {
        defer function(x)
        switch x {
        case 0:
                return "nula"
        case 2, 4, 6, 8:
                return "sudé číslo"
        case 1, 3, 5, 7, 9:
                return "liché číslo"
        default:
                return "?"
        }
}
 
func main() {
        for x := 0; x <= 10; x++ {
                println(x, classify(x))
        }
}

Po spuštění tohoto příkladu je patrné, že se blok defer skutečně použil ve všech možných případech:

Defer 0
0 nula
Defer 1
1 liché číslo
Defer 2
2 sudé číslo
Defer 3
3 liché číslo
Defer 4
4 sudé číslo
Defer 5
5 liché číslo
Defer 6
6 sudé číslo
Defer 7
7 liché číslo
Defer 8
8 sudé číslo
Defer 9
9 liché číslo
Defer 10
10 ?

11. Praktické použití příkazu defer

Nyní si ukažme typický příklad použití příkazu defer, který naleznete i v dalších tutoriálech (není divu – používají se v něm obecně známé koncepty). V příkladu je deklarována funkce nazvaná copyFile, která slouží ke kopii obsahu jednoho souboru do souboru druhého. Navíc tato funkce vrátí dvojici hodnot: počet zkopírovaných (zapsaných) bajtů (nikoli znaků) a taktéž případnou chybu, která může nastat. Pokud žádná chyba nenastane, bude druhá vrácená hodnota obsahovat nil. Příklad je napsán takovým způsobem, aby vypisoval všechny informace o prováděných operacích; v praxi by byl kratší:

package main
 
import (
        "fmt"
        "io"
        "os"
)
 
func closeFile(file *os.File) {
        fmt.Printf("Closing file '%s'\n", file.Name())
        file.Close()
}
 
func copyFile(srcName, dstName string) (written int64, err error) {
        src, err := os.Open(srcName)
        if err != nil {
                fmt.Printf("Cannot open file '%s' for reading\n", srcName)
                return
        } else {
                fmt.Printf("File '%s' opened for reading\n", srcName)
        }
        defer closeFile(src)
 
        dst, err := os.Create(dstName)
        if err != nil {
                fmt.Printf("Cannot create destination file '%s'\n", dstName)
                return
        } else {
                fmt.Printf("File '%s' opened for writing\n", dstName)
        }
        defer closeFile(dst)
 
        return io.Copy(dst, src)
}
 
func testCopyFile(srcName, dstName string) {
        copied, err := copyFile(srcName, dstName)
        if err != nil {
                fmt.Printf("copyFile('%s', '%s') failed!!!\n", srcName, dstName)
        } else {
                fmt.Printf("Copied %d bytes\n", copied)
        }
        fmt.Println()
}
 
func main() {
        testCopyFile("14_defer_practical_usage.go", "new.go")
        testCopyFile("tento_soubor_neexistuje", "new.go")
        testCopyFile("14_defer_practical_usage.go", "")
        testCopyFile("14_defer_practical_usage.go", "/dev/full")
        testCopyFile("/dev/null", "new2.go")
}

Za povšimnutí stojí zejména tyto „triky“:

  1. Využíváme toho, že written má i bez přiřazení známou implicitní hodnotu.
  2. Použití samotného return s tím, že návratová hodnota err je explicitně nastavena předchozím příkazem.
  3. Zdrojový a cílový soubor se snažíme zavřít i v případě chyby. Pokus o uzavření nastane v každém případě, a to i při výskoku z funkce příkazem return.

Zkusme si tento příkaz spustit:

File '14_defer_practical_usage.go' opened for reading
File 'new.go' opened for writing
Closing file 'new.go'
Closing file '14_defer_practical_usage.go'
Copied 1319 bytes
 
Cannot open file 'tento_soubor_neexistuje' for reading
copyFile('tento_soubor_neexistuje', 'new.go') failed!!!
 
File '14_defer_practical_usage.go' opened for reading
Cannot create destination file ''
Closing file '14_defer_practical_usage.go'
copyFile('14_defer_practical_usage.go', '') failed!!!
 
File '14_defer_practical_usage.go' opened for reading
File '/dev/full' opened for writing
Closing file '/dev/full'
Closing file '14_defer_practical_usage.go'
copyFile('14_defer_practical_usage.go', '/dev/full') failed!!!
 
File '/dev/null' opened for reading
File 'new2.go' opened for writing
Closing file 'new2.go'
Closing file '/dev/null'
Copied 0 bytes

Můžeme vidět, že se aplikace vyrovnala jak s neexistujícími soubory, tak i se soubory, které nelze vytvořit, popř. do nich nelze provést zápis (/dev/full).

V praxi by byla celá funkce pro kopii obsahu souboru kratší a nevolala by se v ní další uživatelská funkce closeFile. Namísto ní by se v defer přímo zapsalo:

defer src.Close()
defer dst.Close()

Příklad by tedy mohl vypadat takto:

package main
 
import (
        "fmt"
        "io"
        "os"
)
 
func copyFile(srcName, dstName string) (written int64, err error) {
        src, err := os.Open(srcName)
        if err != nil {
                return
        }
        defer src.Close()
 
        dst, err := os.Create(dstName)
        if err != nil {
                return
        }
        defer dst.Close()
 
        return io.Copy(dst, src)
}
 
func testCopyFile(srcName, dstName string) {
        copied, err := copyFile(srcName, dstName)
        if err != nil {
                fmt.Printf("copyFile('%s', '%s') failed!!!\n", srcName, dstName)
        } else {
                fmt.Printf("Copied %d bytes\n", copied)
        }
        fmt.Println()
}
 
func main() {
        testCopyFile("14_defer_practical_usage.go", "new.go")
        testCopyFile("tento_soubor_neexistuje", "new.go")
        testCopyFile("14_defer_practical_usage.go", "")
        testCopyFile("14_defer_practical_usage.go", "/dev/full")
        testCopyFile("/dev/null", "new2.go")
}

12. Ovlivnění návratové hodnoty funkce v bloku defer

Nyní si ukážeme, jakým způsobem je možné propojit dvě vlastnosti programovacího jazyka Go, konkrétně příkaz defer a změnu návratové hodnoty funkce prostým přiřazením do pojmenované proměnné představující jednu z návratových hodnot. Připomeňme si, že následující zápis je v Go zcela legální a představuje funkci s návratovou hodnotou typu int, která je pojmenována i:

func funkce1() (i int) {
        i = 1
        return
}

S touto návratovou hodnotou lze manipulovat i v bloku defer a ovlivnit tím, jaká hodnota se z funkce skutečně vrátí! To může být velmi užitečné při zpracování výjimečných stavů v aplikaci:

package main
 
import "fmt"
 
func funkce1() (i int) {
        i = 1
        return
}
 
func funkce2() (i int) {
        defer func() { i = 2 }()
        return 1
}
 
func funkce3() (i int) {
        defer func() { i += 2 }()
        return 1
}
 
func main() {
        fmt.Printf("Návratová hodnota funkce1: %d\n", funkce1())
        fmt.Printf("Návratová hodnota funkce2: %d\n", funkce2())
        fmt.Printf("Návratová hodnota funkce3: %d\n", funkce3())
}

Z vypsaných výsledků je pravděpodobně nejzajímavější poslední řádek, který ukazuje, že v defer se původní hodnota vrácená příkazem return ještě upravila (modifikovala):

Návratová hodnota funkce1: 1
Návratová hodnota funkce2: 2
Návratová hodnota funkce3: 3

13. Funkce panic

V souvislosti se zpracováním potenciálně chybových stavů aplikace se často setkáme s funkcí panic. Tato funkce je přímo součástí základní knihovny jazyka Go, takže ji (resp. její balíček) nemusíme žádným způsobem importovat. Pokud je funkce panic zavolána, je aktuálně prováděná funkce ihned ukončena a jsou samozřejmě provedena případná volání funkcí specifikovaných v příkazech defer. Poté se řízení toku programu vrátí do volajícího kódu, který se bude chovat stejným způsobem, jakoby se funkce panic volala v něm – v praxi tedy bude docházet k postupnému „probublávání“ až do té chvíle, kdy je dosaženo první funkce v aktuálně spuštěné gorutině (v hlavní gorutině jde o funkci main). V této chvíli celý program zhavaruje. Můžeme si to vyzkoušet, například přidáním následujícího kódu zavolaného po otevření souboru:

if err != nil {
        panic(err)
}

Celý příklad pro kopii souboru, který jsme si již popsali, je tedy možné přepsat například následujícím způsobem:

package main
 
import (
        "fmt"
        "io"
        "os"
)
 
func closeFile(file *os.File) {
        fmt.Printf("Closing file '%s'\n", file.Name())
        file.Close()
}
 
func copyFile(srcName, dstName string) (written int64, err error) {
        src, err := os.Open(srcName)
        if err != nil {
                panic(err)
        }
        defer closeFile(src)
 
        dst, err := os.Create(dstName)
        if err != nil {
                panic(err)
        }
        defer closeFile(dst)
 
        return io.Copy(dst, src)
}
 
func testCopyFile(srcName, dstName string) {
        copied, err := copyFile(srcName, dstName)
        if err != nil {
                fmt.Printf("copyFile('%s', '%s') failed!!!\n", srcName, dstName)
        } else {
                fmt.Printf("Copied %d bytes\n", copied)
        }
        fmt.Println()
}
 
func main() {
        testCopyFile("14_defer_practical_usage.go", "new.go")
        // testCopyFile("tento_soubor_neexistuje", "new.go")
        testCopyFile("new.go", "")
        testCopyFile("14_defer_practical_usage.go", "/dev/full")
        testCopyFile("/dev/null", "new2.go")
}

Chování po spuštění:

Closing file 'new.go'
Closing file '14_defer_practical_usage.go'
Copied 1319 bytes
 
Closing file 'new.go'
panic: open : no such file or directory
 
goroutine 1 [running]:
main.copyFile(0x4b87d6, 0x6, 0x0, 0x0, 0x0, 0x4cc760, 0xc000060150)
        /home/tester/temp/out/go-root/article_06/17_panic.go:30 +0x1ad
main.testCopyFile(0x4b87d6, 0x6, 0x0, 0x0)
        /home/tester/temp/out/go-root/article_06/17_panic.go:38 +0x67
main.main()
        /home/tester/temp/out/go-root/article_06/17_panic.go:50 +0x70
exit status 2
Poznámka: zavolání panic má tedy podobný efekt, jako vyhození výjimky příkazem throw v těch jazycích, které s výjimkami přímo pracují (což opět není případ jazyka Go, alespoň ne v jeho stabilní verzi).

14. Funkce recover

Ve chvíli, kdy funkce panic probublává zásobníkovými rámci aktuální gorutiny, ji můžeme zachytit funkcí recover. Otázkou ovšem zůstává, kam vlastně volání funkce recover umístit. Typicky se setkáme s tím, že se recover použije v bloku defer, takže je zaručeno, že se funkce recover zavolá vždy a za jasně daných podmínek:

defer func() {
        if rec := recover(); rec != nil {
                fmt.Println("Recovered in copyFile", rec)
        }
}()

Tímto způsobem jsme vlastně implementovali zobecněnou podobu bloku catch/expect. Ukažme si to opět na praktickém příkladu – nám již známé kopii souboru:

package main
 
import (
        "fmt"
        "io"
        "os"
)
 
func closeFile(file *os.File) {
        fmt.Printf("Closing file '%s'\n", file.Name())
        file.Close()
}
 
func copyFile(srcName, dstName string) (written int64, err error) {
        defer func() {
                if rec := recover(); rec != nil {
                        fmt.Println("Recovered in copyFile", rec)
                }
        }()
        src, err := os.Open(srcName)
        if err != nil {
                panic(err)
        }
        defer closeFile(src)
 
        dst, err := os.Create(dstName)
        if err != nil {
                panic(err)
        }
        defer closeFile(dst)
 
        return io.Copy(dst, src)
}
 
func testCopyFile(srcName, dstName string) {
        copied, err := copyFile(srcName, dstName)
        if err != nil {
                fmt.Printf("copyFile('%s', '%s') failed!!!\n", srcName, dstName)
        } else {
                fmt.Printf("Copied %d bytes\n", copied)
        }
        fmt.Println()
}
 
func main() {
        testCopyFile("14_defer_practical_usage.go", "new.go")
        // testCopyFile("tento_soubor_neexistuje", "new.go")
        testCopyFile("new.go", "")
        testCopyFile("14_defer_practical_usage.go", "/dev/full")
        testCopyFile("/dev/null", "new2.go")
}

Můžeme vidět, že se recover zavolá vždy a navíc se zavolá až za všemi dalšími bloky defer, tj. jako poslední příkaz v rámci ukončované funkce copyFile.

15. Výrazy a operátory v programovacím jazyce Go

Ve třetí části článku se budeme zabývat problematikou operátorů. Pro čtenáře tohoto seriálu pravděpodobně nebude žádnou novinkou, že operátory určené pro manipulaci s číselnými, pravdivostními, řetězcovými aj. hodnotami tvoří podstatnou část syntaxe v prakticky všech mainstreamových jazycích (výjimkou jsou lispovské jazyky či Forth, které ovšem nepatří mezi mainstream). V programovacím jazyku Go nalezneme celkem 26 základních operátorů, k nimž navíc ještě musíme připočíst operátory spojené s přiřazením:

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 <- :=        
Poznámka: ve skutečnosti má operátor + ještě další význam; slouží totiž pro spojení (konkatenaci) řetězců, nikoli pouze pro realizaci aritmetických operací.

Zajímavá je tabulka s prioritami operátorů. Ta je totiž poměrně jednoduchá, zejména v porovnání s tabulkami, které mnozí čtenáři pravděpodobně znají z jazyků C, C++ či Javy. Nejvyšší prioritu mají unární operátory (s jediným operandem) a následně existuje pouze pět priorit, které si můžete zapamatovat s využitím mnemotechnické pomůcky MACAO:

Úroveň Operátory Mnemotechnická pomůcka
1 Multiplicative
2 Additive
3 Comparison
4 And
5 Or

V navazujících kapitolách a taktéž v další části článku si jednotlivé operátory postupně popíšeme.

Poznámka: pravděpodobně jste si všimli, že v předchozích tabulkách nebyly uvedeny operátory ++ a --, protože ty v programovacím jazyku Go spadají do zvláštní kategorie operátorů tvořících příkazy. Jaký to má dopad na způsob použití těchto operátorů si ukážeme v demonstračních příkladech.

16. Unární operátory

Nejprve se podívejme na použití unárních operátorů. Ty mají, jak již víme, nejvyšší prioritu a proto se téměř nikdy nesetkáme s nutností uzavírat tyto operátory do kulatých závorek, které (jako v jiných jazycích i v matematice) slouží ke změně priority. V Go najdeme tyto unární operátory:

Operátor Význam
+ nemění znaménko výsledku (opak dalšího operátoru)
mění znaménko výsledku
! logická negace
^ negace bit po bitu (podobně jako ~ v C)
* přístup na adresu přes ukazatel (referenci)
& získání adresy operandu
<- přečtení hodnoty z kanálu

Ukažme si nyní, jak se jednotlivé unární operátory používají. Povšimněte si, že se všechny zmíněné operátory vždy zapisují před operand:

package main
 
import "fmt"
 
func message(channel chan int) {
        code, status := <-channel
 
        fmt.Printf("received code: %d and status: %t\n", code, status)
}
 
func main() {
        // unární operátory + a -
        i := 42
        fmt.Println(+i)
        fmt.Println(-i)
 
        // unární operátor ^
        i = 0
        fmt.Println(^i)
        i++
        fmt.Println(^i)
 
        // unární operátor !
        b := false
        fmt.Println(!b)
 
        // unární operátory & a *
        fmt.Println(&i)
 
        p_i := &i
        fmt.Println(*p_i)
 
        // unární operátor <-
        channel := make(chan int)
        go message(channel)
        <channel 
}

Tento příklad je jen ilustrační, protože skončí s chybou při pokusu o čtení hodnoty z kanálu channel s čekáním:

42
-42
-1
-2
true
0xc0000140e0
1
fatal error: all goroutines are asleep - deadlock!

17. Relační operátory

Nabídka relačních operátorů v programovacím jazyce Go by nás neměla ničím nepřekvapit: k dispozici je všech šest základních operátorů pro test na rovnost, nerovnost, větší než, menší než a kombinací větší nebo rovno a menší nebo rovno. Těchto šest operátorů lze použít pro všechny celočíselné datové typy i pro datové typy s plovoucí řádovou čárkou. Kromě toho ovšem můžeme porovnávat i řetězce.

U komplexních čísel, tj. u datových typů complex64 a complex128 je ovšem situace odlišná, protože tyto hodnoty můžeme porovnávat jen na rovnost a nerovnost (tvůrci programovacího jazyka Go se tak vyhnuli problémům, jak vlastně definovat relaci mezi dvojicí komplexních čísel).

Zajímavé je, že hodnoty typu boolean, tj. hodnoty true a false je možné v programovacím jazyce Go porovnávat pouze na rovnost a nerovnost. Zbylé čtyři relační operátory nelze použít, tj. v jazyce Go nelze rozhodnout, zda je true větší či menší než false (naproti tomu například v Pascalu je false < true).

Opět si ukažme příklad použití všech relačních operátorů pro různé datové typy:

package main
 
import "fmt"
 
func main() {
        x := 42
        y := 0
 
        fmt.Println(x < y)
        fmt.Println(x <= y)
        fmt.Println(x == y)
        fmt.Println(x >= y)
        fmt.Println(x > y)
        fmt.Println(x != y)
 
        fmt.Println()
 
        fx := 1e10
        fy := -2.3e56
 
        fmt.Println(fx < fy)
        fmt.Println(fx <= fy)
        fmt.Println(fx == fy)
        fmt.Println(fx >= fy)
        fmt.Println(fx > fy)
        fmt.Println(fx != fy)
 
        fmt.Println()
 
        bx := true
        by := false
 
        fmt.Println(bx == by)
        fmt.Println(bx != by)
 
        fmt.Println()
 
        cx := 1 + 1i
        cy := 0 + 0i
 
        fmt.Println(cx == cy)
        fmt.Println(cx != cy)
 
        fmt.Println()
 
        sx := "Hello"
        sy := "World"
 
        fmt.Println(sx < sy)
        fmt.Println(sx <= sy)
        fmt.Println(sx == sy)
        fmt.Println(sx >= sy)
        fmt.Println(sx > sy)
        fmt.Println(sx != sy)
 
        fmt.Println()
}

Po překladu a spuštění předchozího demonstračního příkladu získáme tyto (očekávatelné) výsledky:

false
false
false
true
true
true
 
false
false
false
true
true
true
 
false
true
 
false
true
 
true
true
false
false
false
true

18. Dělení, zbytek po dělení a bitové posuny

V programovacím jazyce Go se pro zápis podílu používá operátor / a pro výpočet zbytku po dělení operátor %, ostatně podobně je tomu i ve všech jazycích více či méně odvozených od jazyka C. Vyzkoušejme si nyní, jak vlastně dělení a výpočet zbytku probíhá pro kladné i záporné dělence a dělitele. Pomůže nám tento demonstrační příklad:

package main
 
import "fmt"
 
func compute_div_mod(x, y int) {
        fmt.Printf("%3d / %2d = %3d   %3d %% %2d = %3d\n", x, y, x/y, x, y, x%y)
}
 
func main() {
        compute_div_mod(10, 3)
        compute_div_mod(-10, 3)
        compute_div_mod(10, -3)
        compute_div_mod(-10, -3)
 
        fmt.Println()
 
        for i := 1; i <= 10; i++ {
                compute_div_mod(100, i)
        }
}

V jazyku Go je znaménko zbytku odvozeno od znaménka dělence. Toto chování je zachováno na všech platformách (zatímco v C a C++ je implementačně závislé):

 10 /  3 =   3    10 %  3 =   1
-10 /  3 =  -3   -10 %  3 =  -1
 10 / -3 =  -3    10 % -3 =   1
-10 / -3 =   3   -10 % -3 =  -1

Z toho vyplývají i další vlastnosti, například to, že se při celočíselném dělení kladných čísel zaokrouhluje výsledek směrem dolů (protože zbytek musí být buď nulový nebo kladný):

100 /  1 = 100   100 %  1 =   0
100 /  2 =  50   100 %  2 =   0
100 /  3 =  33   100 %  3 =   1
100 /  4 =  25   100 %  4 =   0
100 /  5 =  20   100 %  5 =   0
100 /  6 =  16   100 %  6 =   4
100 /  7 =  14   100 %  7 =   2
100 /  8 =  12   100 %  8 =   4
100 /  9 =  11   100 %  9 =   1
100 / 10 =  10   100 % 10 =   0

Při dělení nulou se interně zavolá panic():

package main
 
import "fmt"
 
func compute_div_mod(x, y int) {
        fmt.Printf("%3d / %2d = %3d   %3d %% %2d = %3d\n", x, y, x/y, x, y, x%y)
}
 
func main() {
        compute_div_mod(10, 0)
}

Z chybového hlášení a výpisu volaných funkcí snadno zjistíme, na kterém místě v programu chyba nastala:

panic: runtime error: integer divide by zero
 
goroutine 1 [running]:
main.compute_div_mod(0xa, 0x0)
        /home/tester/temp/out/go-root/article_06/22_div_by_zero.go:13 +0x1fd
main.main()
        /home/tester/temp/out/go-root/article_06/22_div_by_zero.go:17 +0x33

Dále si ukažme použití bitových posunů. U těchto operátorů je nutné dodržet jedno pravidlo – samotný posun musí být reprezentován kladným číslem (konstantou), popř. proměnnou typu celé kladné číslo. Z tohoto důvodu nebude možné přeložit následující kód, protože proměnná i je typu int:

package main
 
import "fmt"
 
func main() {
        x := 1
 
        for i := 0; i <= 10; i++ {
                fmt.Printf("%d << %2d == %4d\n", x, i, x<<i)
        }
 
        fmt.Println()
 
        x = 10000000
 
        for i := 0; i <= 10; i++ {
                fmt.Printf("%d >> %2d == %4d\n", x, i, x>>i)
        }
 
}

Chybové hlášení:

./23_bit_shift_negative_shift.go:16:43: invalid operation: x << i (shift count type int, must be unsigned integer)
./23_bit_shift_negative_shift.go:24:43: invalid operation: x >> i (shift count type int, must be unsigned integer)

Úprava programu je snadná – použijeme přetypování s využitím funkce uint(), čímž změníme hodnotu proměnné i:

bitcoin_skoleni

package main
 
import "fmt"
 
func main() {
        x := 1
 
        for i := uint(0); i <= 10; i++ {
                fmt.Printf("%d << %2d == %4d\n", x, i, x<<i)
        }
 
        fmt.Println()
 
        x = 10000000
 
        for i := uint(0); i <= 10; i++ {
                fmt.Printf("%d >> %2d == %4d\n", x, i, x>>i)
        }
 
}

Nyní již bude možné výsledek spočítat:

1 <<  0 ==    1
1 <<  1 ==    2
1 <<  2 ==    4
1 <<  3 ==    8
1 <<  4 ==   16
1 <<  5 ==   32
1 <<  6 ==   64
1 <<  7 ==  128
1 <<  8 ==  256
1 <<  9 ==  512
1 << 10 == 1024
 
10000000 >>  0 == 10000000
10000000 >>  1 == 5000000
10000000 >>  2 == 2500000
10000000 >>  3 == 1250000
10000000 >>  4 == 625000
10000000 >>  5 == 312500
10000000 >>  6 == 156250
10000000 >>  7 == 78125
10000000 >>  8 == 39062
10000000 >>  9 == 19531
10000000 >> 10 == 9765

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_goto.go základní použití příkazu goto https://github.com/tisnik/go-fedora/blob/master/article06/01_go­to.go
2 02_goto_from_inner_loop.go výskok z vnořených smyček https://github.com/tisnik/go-fedora/blob/master/article06/02_go­to_from_inner_loop.go
3 03_goto_from_switch.go výskok z konstrukce switch https://github.com/tisnik/go-fedora/blob/master/article06/03_go­to_from_switch.go
4 04_goto_bad_usage_A.go nekorektní použití goto https://github.com/tisnik/go-fedora/blob/master/article06/04_go­to_bad_usage_A.go
5 05_goto_bad_usage_B.go další nekorektní použití goto https://github.com/tisnik/go-fedora/blob/master/article06/05_go­to_bad_usage_B.go
6 06_goto_bad_usage_C.go další nekorektní použití goto https://github.com/tisnik/go-fedora/blob/master/article06/06_go­to_bad_usage_C.go
7 07_defer.go příkaz defer https://github.com/tisnik/go-fedora/blob/master/article06/07_de­fer.go
8 08_defer_func.go funkce v příkazu defer https://github.com/tisnik/go-fedora/blob/master/article06/08_de­fer_func.go
9 09_defer_with_parameters.go parametry předané do defer https://github.com/tisnik/go-fedora/blob/master/article06/09_de­fer_with_parameters.go
10 10_more_defers.go použití více příkazů defer https://github.com/tisnik/go-fedora/blob/master/article06/10_mo­re_defers.go
11 11_defer_arguments_evaluation.go čas vyhodnocování argumentů v defer https://github.com/tisnik/go-fedora/blob/master/article06/11_de­fer_arguments_evaluation.go
12 12_defer_arguments_evaluation.go čas vyhodnocování argumentů v defer https://github.com/tisnik/go-fedora/blob/master/article06/12_de­fer_arguments_evaluation.go
13 13_defer_on_all_returns.go defer a příkaz return https://github.com/tisnik/go-fedora/blob/master/article06/13_de­fer_on_all_returns.go
14 14_defer_practical_usage.go praktické použití příkazu defer https://github.com/tisnik/go-fedora/blob/master/article06/14_de­fer_practical_usage.go
15 15_defer_practical_usage.go praktické použití příkazu defer https://github.com/tisnik/go-fedora/blob/master/article06/15_de­fer_practical_usage.go
16 16_defer_return_values.go modifikace návratové hodnoty funkce https://github.com/tisnik/go-fedora/blob/master/article06/16_de­fer_return_values.go
17 17_panic.go funkce panic https://github.com/tisnik/go-fedora/blob/master/article06/17_pa­nic.go
18 18_panic_recover.go kooperace panic+recover https://github.com/tisnik/go-fedora/blob/master/article06/18_pa­nic_recover.go
19 19_unary_operators.go použití unárních operátorů https://github.com/tisnik/go-fedora/blob/master/article06/19_u­nary_operators.go
20 20_relational_operators.go všechny relační operátory https://github.com/tisnik/go-fedora/blob/master/article06/20_re­lational_operators.go
21 21_div_mod.go dělení a zbytek po dělení https://github.com/tisnik/go-fedora/blob/master/article06/21_div_mod­.go
22 22_div_by_zero.go dělení nulou https://github.com/tisnik/go-fedora/blob/master/article06/22_div_by_ze­ro.go
23 23_bit_shift_negative_shift.go bitové posuny https://github.com/tisnik/go-fedora/blob/master/article06/23_bit_shif­t_negative_shift.go
24 24_bit_shifts.go bitové posuny https://github.com/tisnik/go-fedora/blob/master/article06/24_bit_shif­ts.go
25 25_bit_shift_assignment.go bitové posuny s přiřazením https://github.com/tisnik/go-fedora/blob/master/article06/25_bit_shif­t_assignment.go
26 26_bit_operators.go bitové operátory https://github.com/tisnik/go-fedora/blob/master/article06/26_bit_o­perators.go

20. Odkazy na Internetu

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

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.