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ů
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
11. Praktické použití příkazu defer
12. Ovlivnění návratové hodnoty funkce v bloku defer
15. Výrazy a operátory v programovacím jazyce Go
18. Dělení, zbytek po dělení a bitové posuny
19. Repositář s demonstračními příklady
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.
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
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ě.
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.
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") }
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á.
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
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“:
- Využíváme toho, že written má i bez přiřazení známou implicitní hodnotu.
- Použití samotného return s tím, že návratová hodnota err je explicitně nastavena předchozím příkazem.
- 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
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 | <- | := |
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.
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:
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:
20. Odkazy na Internetu
- Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - 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/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - 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 - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - 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/ - 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 - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - 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 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - 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/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/