Obsah
1. Problematika nulových hodnot v Go, aneb proč nil != nil
2. Význam nulových hodnot v jazyce Go
3. Koncept nil v programovacích jazycích
4. Chyba v návrhu, která nás stála miliardy dolarů?
5. nil není rezervované klíčové slovo
9. Prohlížení obsahu řezů v debuggeru
11. Identifikátor nil a kanály
12. Čtení a zápis do nulového kanálu
13. Identifikátor nil a rozhraní
14. Proč někdy platí nil != nil?
15. Porovnání hodnot nil různých typů
16. Datový typ Option v programovacím jazyku Rust
17. Datový typ Result, opět v Rustu
18. Nebylo by tedy výhodnější použít obdobu typu Option?
19. Repositář s demonstračními příklady
1. Problematika nulových hodnot v Go, aneb proč nil != nil
V úvodních částech seriálu o programovacím jazyku Go jsme se seznámili s prakticky všemi vlastnostmi tohoto jazyka, ať již se jednalo o jeho základní syntaxi, tak i sémantiku, využití gorutin a kanálů atd. Mj. jsme si, prozatím alespoň ve stručnosti, popsali i koncept takzvaných „nulových hodnot“ (zero values), který nepřímo vychází z toho, že se v jazyku Go nepoužívají klasické konstruktory. Dále jsme si řekli, jak se definují a používají rozhraní (interface), včetně možnosti využití prázdných rozhraní definovaných takto:
type I interface {}
Ovšem právě existence takzvaných nulových hodnot a (prázdných) rozhraní nás dovádí ke specifickému typovému systému programovacího jazyka Go, který se pravděpodobněji nejčastěji projeví (v negativním slova smyslu) ve chvíli, kdy porovnáváme dvě zdánlivě shodné nulové hodnoty nil a výsledkem porovnání na ekvivalenci je překvapivě hodnota false. V navazujících kapitolách si vysvětlíme proč tomu tak je, a jak se ve skutečnosti nil používá v různých datových typech, především právě u rozhraní.
2. Význam nulových hodnot v jazyce Go
Popis konceptu nulových hodnot v programovacím jazyku Go začneme u základních primitivních datových typů. V Go je možné, jak již ostatně víme z úvodních částí seriálu, deklarovat proměnnou, popř. její deklaraci doplnit i o její inicializaci. Přitom platí, že pokud inicializaci explicitně neprovedeme, bude proměnná automaticky inicializována nulovou hodnotou (zero value), přičemž se ovšem (pochopitelně) liší význam slova „nulová“ pro jednotlivé datové typy. V následující tabulce jsou uvedeny implicitní nulové hodnoty pro většinu standardních datových typů jazyka Go:
# | Datový typ | Nulová hodnota |
---|---|---|
1 | bool | false |
2 | int (a varianty) | 0 |
3 | float (obě varianty) | 0.0 |
4 | complex (obě varianty) | 0.0+0.0i |
5 | string | "" |
6 | pointer | nil |
7 | slice | nil |
8 | map | nil |
9 | channel | nil |
10 | function | nil |
11 | interface | nil |
12 | struct | prvky s nulovými hodnotami |
Pro úplnost si vyzkoušíme, jestli jsou vlastnosti datových typů vypsané v předchozí tabulce skutečně pravdivé.
Nejjednodušší je to pochopitelně v případě primitivních datových typů, například u pravdivostního typu bool:
package main func main() { var b bool println(b) }
Výsledkem běhu programu bude výpis „nulové hodnoty“ datového typu bool:
false
Podobně existují velmi dobře nadefinované nulové hodnoty i pro všechny numerické datové typy, tj. jak pro typy celočíselné, tak i pro typy s plovoucí řádovou čárkou i pro komplexní čísla:
package main func main() { var i1 int8 var i2 int32 var u1 uint8 var u2 uint32 var f1 float32 var f2 float64 var c1 complex64 var c2 complex128 println(i1) println(i2) println(u1) println(u2) println(f1) println(f2) println(c1) println(c2) }
Výsledky běhu tohoto demonstračního příkladu by opět neměly být překvapující:
0 0 0 0 +0.000000e+000 +0.000000e+000 (+0.000000e+000+0.000000e+000i) (+0.000000e+000+0.000000e+000i)
Podobně tomu bude v případě „nulové hodnoty“ řetězce; touto hodnotou je prázdný řetězec:
package main func main() { var s string println(s) }
Při spuštění příkladu by se měl zobrazit pouze jeden prázdný řádek (ten není pravda příliš viditelný):
Z tabulky, kterou jsme si uvedli na začátku této kapitoly, již víme, že pro ostatní datové typy je „nulová hodnota“ zapisována pomocí identifikátoru nil. Ovšem při výpisu hodnot nil (pokaždé jiného typu) se chování systému může lišit podle toho, jakou funkci pro výpis použijeme. Ukažme si nejprve použití vestavěné funkce println:
package main func main() { var p *int var s []int var m map[string]int var c chan int var f func() var i interface{} println(p) println(s) println(m) println(c) println(f) println(i) }
Tato funkce vypíše pro všech šest hodnot nil nějakou formu nuly, popř. u řezů a rozhraní několik nul:
0x0 [0/0]0x0 0x0 0x0 0x0 (0x0,0x0)
Lepší je použít funkci fmt.Println(), mezi jejíž přednosti patří fakt, že dokáže vytisknout i obsah polí, což vestavěná funkce println() nepodporuje:
package main import "fmt" func main() { var a [10]complex64 var p *int var s []int var m map[string]int var c chan int var f func() var i interface{} fmt.Println(a) fmt.Println(p) fmt.Println(s) fmt.Println(m) fmt.Println(c) fmt.Println(f) fmt.Println(i) }
Výsledek běhu tohoto demonstračního příkladu je již odlišný a čitelnější (na druhou stranu je „vysokoúrovňový“ například v tom smyslu, že nevypíše podrobnější informace o řezu ani o rozhraní:
[(0+0i) (0+0i) (0+0i) (0+0i) (0+0i) (0+0i) (0+0i) (0+0i) (0+0i) (0+0i)] <nil> [] map[] <nil> <nil> <nil>
A nakonec se podívejme, jaká je „nulová hodnota“ struktury neboli záznamu:
package main import "fmt" func main() { var s struct { a int b bool c chan int d []int } fmt.Println(s) }
Z výpisu je patrné, že záznam je sice vytvořen, ovšem všechny jeho prvky obsahují „nulové hodnoty“ tak, jak jsou definovány pro jednotlivé základní datové typy:
{0 false <nil> []}
var s struct { a int s2 struct { a int b int } s3 struct { a byte b []int } } fmt.Println(s)
S výsledkem:
{0 {0 0} {0 []}}
3. Koncept nil v programovacích jazycích
V předchozím textu při zmínce o „nulových hodnotách“ a vlastně i v mnoha demonstračních příkladech jsme se již několikrát setkali s identifikátorem nil. Tento identifikátor se v programovacím jazyku Go používá pro reprezentaci „nulové hodnoty“ u ukazatelů i u většiny složených datových typů. Nejedná se vlastně o žádnou novinku, protože nil najdeme i v dalších programovacích jazycích. Poprvé se, i když v poněkud jiném významu, objevil v Lispu a jazycích od něho odvozených (Scheme, Clojure); najdeme ho i v programovacím jazyce Lua či v klasickém Pascalu. V některých dalších programovacích jazycích se objevují jiná označení pro neznámou či nulovou hodnotu; typicky se jedná o identifikátory NULL, null a taktéž None. V následující tabulce se pro zajímavost můžete podívat, jaký je vznik a význam všech těchto názvů:
Slovo | Původ | Význam |
---|---|---|
null | latina | ne+ullus, žádný |
nil | latina | nihil, nic (též zkratka „Not In List“) |
none | stará angličtina | ne+an, ani jeden |
Jak jsme si již napsali v předchozím odstavci, můžeme se s identifikátorem nil setkat hned v několika programovacích jazycích; ve skutečnosti se ovšem samotný význam nil/null/None může lišit a nemusí přesně odpovídat významu daného slova. Příkladem může být klasický LISP, ve kterém se nil používá pro označení prázdného seznamu, neznámé hodnoty a taktéž logické nepravdy (ve Scheme se namísto toho setkáme s #f pro nepravdu). V Pascalu se naproti tomu nil používá pouze jako nulová hodnota ukazatele (pointer) popř. dynamicky generovaných polí a tříd; nikde jinde nelze použít.
Programovací jazyk Go používá identifikátor nil taktéž v několika významech. Především se jím označují, jak již víme, nulové hodnoty několika datových typů, ovšem samotný způsob uložení nulové hodnoty je obecně dosti rozdílný (nejvíc patrné je to u řezů a rozhraní). Dále má ovšem nil i další sémantický význam, protože se používá tehdy, pokud potřebujeme specifikovat neexistenci nějaké hodnoty. Typickým příkladem použití jsou druhé návratové hodnoty těch funkcí, které vrací informaci o tom, zda při jejich běhu došlo k chybě. Druhá návratová hodnota obsahuje nil v případě, že k chybě nedošlo a jinou hodnotu (nějakou strukturu implementující standardní vestavěné rozhraní error) ve chvíli, kdy při běhu funkce byla detekována nějaká chyba. Způsob vracení dvou hodnot z funkce je v Go jednoduchý:
func copyFile(srcName, dstName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return 0, err } defer src.Close() dst, err := os.Create(dstName) if err != nil { return 0, err } defer dst.Close() return io.Copy(dst, src) }
Detekce a zpracování chyby je založena na testování druhé návratové hodnoty na nil:
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() }
Zajímavé je, že nil je v programovacím jazyku Go naprosto běžný identifikátor a nikoli klíčové slovo.
4. Chyba v návrhu, která nás stála miliardy dolarů?
„I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.“
Sir C.A.R. Hoare
V souvislosti s hodnotami nil/null/None se hovoří o miliardové chybě v návrhu programovacích jazyků. Jedná se o nadsázku, o které rád hovoří spolutvůrce tohoto konceptu Sir C.A.R Hoare, viz například následující video: Null References: The Billion Dollar Mistake. V Go může použití nil vést k podobným chybám, a to u ukazatelů, rozhraní a map (teoreticky i u funkcí, to však bude méně častý případ); řešení tohoto problému přitom v Go prozatím neexistuje, na rozdíl od programovacího jazyka Rust (Fixing the Billion Dollar Mistake in Go by Borrowing from Rust), popř. jazyků s podporou anotací a dekorátorů.
5. nil není rezervované klíčové slovo
V předchozích částech tohoto seriálu jsme si kromě dalších věcí vysvětlili i význam všech klíčových slov tohoto jazyka. Pro upřesnění si tato rezervovaná slova ještě naposledy uvedeme:
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 |
Za povšimnutí stojí především fakt, že slovo nil se v tomto seznamu vůbec nevyskytuje, ostatně podobně jako slova true a false. To není opomenutí autora tohoto článku, protože se skutečně (minimálně v Go) nejedná o klíčová slova. Ostatně se o tom můžeme velmi snadno přesvědčit, jelikož následující program naprogramovaných v Go je korektní a běžně spustitelný (i když případným čtenářům pochopitelně radost neudělá):
package main import "fmt" func main() { fmt.Println(nil) nil := 42 fmt.Println(nil) }
Po spuštění zjistíme, že se hodnota nil skutečně změnila a je pro celou druhou polovinu funkce main odlišná od zbytku (rozumné části) vesmíru:
<nil> 42
Mimochodem, naprosto stejným způsobem si můžeme předefinovat hodnoty true a false, protože ani ty nejsou v programovacím jazyku Go neměnitelné ani rezervované:
package main import "fmt" func main() { fmt.Println(true) fmt.Println(false) x := true true := false false := x fmt.Println("oh no...") fmt.Println(true) fmt.Println(false) }
Příklad výstupu:
true false oh no... false true
Tento program napsaný v Clojure není korektní a nepůjde spustit:
(println nil) (def nil 42) (println nil)
Ani v programovacím jazyce Lua není předefinování nil přípustné:
print(nil) nil = 42 print(nil)
V Pascalu tento program nepůjde ani přeložit:
program Riddle; var nil:integer; begin writeln('oh no...'); end.
Totéž platí pro pokus o spuštění následujícího skriptu napsaného v Pythonu:
print(None) None = 42 print(None)
A pochopitelně i:
print(True) True = False print(True)
6. Typ identifikátoru nil
V následujících kapitolách si podrobněji vysvětlíme, jakým způsobem se vlastně používají nulové hodnoty u těch datových typů, u nichž se nulová hodnota zapisuje s využitím identifikátoru nil. Uvidíme, že vnitřní reprezentace nil je ve skutečnosti dosti různorodá a mnohdy se nejedná o pouhé číslo 0 uložené do operační paměti.
Nejprve si ukažme demonstrační příklad, v němž se snažíme hodnotu nil přiřadit proměnné současně s deklarací této proměnné s automatickým odvozením jejího datového typu (můžeme zde s výhodou využít operátor „:=“). Programovací jazyk Go nám samozřejmě umožňuje zapsat například:
x := 42
nebo:
s := "Go...go...go!"
Tyto zápisy jsou pro překladač jazyka Go zcela jednoznačné, protože na základě přiřazované hodnoty dokáže překladač odvodit i typ proměnné. Ovšem samotný identifikátor nil vlastně žádnou hodnotu (a tudíž ani její typ) nereprezentuje a tudíž následující příklad nebude možné přeložit:
package main import "fmt" func main() { v := nil fmt.Println(v) }
Při pokusu o překlad se zobrazí chybová zpráva:
# command-line-arguments ./09_nil_value.go:6:4: use of untyped nil
x = None
7. nil a ukazatele
Podobně jako například v Pascalu či v céčku (NULL) je možné identifikátor nil použít při inicializaci ukazatele na „nulovou hodnotu“. V takovém případě je ovšem zaručeno, že jakýkoli přístup do paměti přes tento ukazatel skončí s běhovou chybou. To platí pro pokus o čtení z paměti:
package main import "fmt" func main() { var v *int = nil fmt.Println(v) fmt.Println(*v) }
S chybou:
<nil> panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x484bf8] goroutine 1 [running]: main.main() /home/tester/temp/go-root/article_26/10B_nil_pointer.go:10 +0x68
I pro pokus o zápis do paměti:
package main import "fmt" func main() { var v *int = nil fmt.Println(v) *v = 42 }
Se stejnou chybou:
<nil> panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x484b6c] goroutine 1 [running]: main.main() /home/tester/go-root/article_26/10_nil_pointer.go:10 +0x5c exit status 2
package main import "fmt" func main() { var v *int = 1234 fmt.Println(v) *v = 42 }
Tento program nepůjde přeložit, protože do ukazatele není možné přiřadit libovolnou hodnotu:
# command-line-arguments ./11_other_pointer.go:6:6: cannot use 1234 (type int) as type *int in assignment
> main.main() ./t.go:13 (PC: 0x4a0bb8) 8: var m map[string]int 9: var c chan int 10: var f func() 11: var i interface{} 12: => 13: fmt.Println(p) 14: fmt.Println(s) 15: fmt.Println(m) 16: fmt.Println(c) 17: fmt.Println(f) 18: fmt.Println(i) (dlv) p p *int nil
8. nil a řezy (slices)
Zatímco nil reprezentující nulový ukazatel se v mnoha ohledech podobá nulovému ukazateli v céčku (NULL) popř. neexistující referenci v Javě (null), má „nulová hodnota“ nil použitá v kontextu řezů (slices) zcela odlišnou povahu. Připomeňme si, že řez je interně reprezentován trojicí hodnot:
- Ukazatelem (referencí) na zvolený prvek pole s daty, ke kterým přes řez přistupujeme. Toto pole se deklaruje explicitně popř. se vytváří a realokuje automaticky funkcí append.
- Délky řezu, tj. počtu prvků, který je aktuálně řezem reprezentován.
- Kapacity řezu (do jaké míry může řez narůstat v důsledku přidávání dalších prvků); kapacita je buď stejná nebo větší, než délka řezu.
Tato interní struktura řezů s sebou přináší několik zajímavých důsledků. Je totiž možné, aby existovalo větší množství řezů ukazujících na obecně různé prvky jediného pole. Pokud nyní změníme prvek v jednom řezu, znamená to, že se vlastně modifikuje obsah původního pole a i ostatní řezy nový prvek uvidí:
var a [10]int slice := a[:] fmt.Printf("Pole před modifikací: %v\n", a) fmt.Printf("Řez před modifikací: %v\n", slice) for i := 0; i < len(a); i++ { a[i] = i * 2 } fmt.Printf("Pole po modifikací: %v\n", a) fmt.Printf("Řez po modifikaci: %v\n", slice) for i := 0; i < len(slice); i++ { slice[i] = 42 } fmt.Printf("Pole po modifikací: %v\n", a) fmt.Printf("Řez po modifikaci: %v\n", slice)
Co je však většinou ještě užitečnější – s řezy jako s datovým typem se velmi snadno pracuje; řezy mohou být předávány do funkcí, vráceny z funkcí atd.
Otázka ovšem je, co se stane ve chvíli, kdy v programu nadeklarujeme proměnnou typu řez, ovšem nepřiřadíme jí žádnou hodnotu:
var s []int
víme již, že řezu bude v takovém případě přiřazena nulová hodnota nil. Ve skutečnosti se i v tomto případě vytvoří výše zmíněná trojice, která bude obsahovat následující konkrétní hodnoty:
Prvek | Hodnota |
---|---|
Ukazatel na pole | nil |
Délka řezu | 0 |
Kapacita řezu | 0 |
Právě takto vytvořený řez považuje runtime programovacího jazyka Go za nulový a je tedy představován identifikátorem nil, i když se ve skutečnosti jedná o datovou strukturu obsahující tři hodnoty.
9. Prohlížení obsahu řezů v debuggeru
Podívejme se nyní, jak se interně odlišuje „nulový řez“ od řezu s nulovou kapacitou a délkou i od řezu s kapacitou a délkou nastavenou na hodnotu 10 prvků:
package main import "fmt" func main() { var s0 []int s1 := []int{} s2 := make([]int, 0) s3 := make([]int, 10) fmt.Println(s0) fmt.Println(s1) fmt.Println(s2) fmt.Println(s3) }
Po nastavení breakpointu na první řádek s příkazem fmt.Println() si můžeme prohlédnout obsah všech čtyř lokálních proměnných. Již minule jsme si řekli, že se pro tento účel používá příkaz print, který můžeme zkrátit na p:
(gdb) p s0 $1 = {array = 0x0, len = 0, cap = 0} (gdb) p s1 $2 = {array = 0x593080 <runtime.zerobase>, len = 0, cap = 0} (gdb) p s2 $3 = {array = 0x593080 <runtime.zerobase>, len = 0, cap = 0} (gdb) p s3 $4 = {array = 0xc0000a4000, len = 10, cap = 10}
Můžeme si dokonce zobrazit obsah paměti, v níž je řez uložen. Vzhledem k tomu, že řez je představován trojicí hodnot (každá konkrétně bude mít šířku 64bitů na mém testovacím počítači), zkusíme si u každého řezu zobrazit šest 32bitových slov příkazem x/6×w, kterému předáme adresu řezu:
(gdb) x/6xw &s0 0xc00008cf10: 0x00000000 0x00000000 0x00000000 0x00000000 0xc00008cf20: 0x00000000 0x00000000 (gdb) x/6xw &s1 0xc00008cef8: 0x00593080 0x00000000 0x00000000 0x00000000 0xc00008cf08: 0x00000000 0x00000000 (gdb) x/6xw &s2 0xc00008cee0: 0x00593080 0x00000000 0x00000000 0x00000000 0xc00008cef0: 0x00000000 0x00000000 (gdb) x/6xw &s3 0xc00008cec8: 0x000a4000 0x000000c0 0x0000000a 0x00000000 0xc00008ced8: 0x0000000a 0x00000000
Výsledky jsou jednoznačné: první řez je skutečně nulovým řezem (tedy je reprezentován identifikátorem nil), další dva řezy obsahují ukazatel na pole nulové délky (a řez má tedy nulovou kapacitu) a čtvrtý řez má kapacitu rovnu 0×0a=10 prvkům, délku taktéž 0×0a=10 prvků a obsahuje ukazatel na pole s příslušnou délkou.
Chování funkce append však nezávisí na tom, zda se jedná o nulový řez či nikoli:
package main import "fmt" func main() { var s0 []int s1 := []int{} s2 := make([]int, 0) s3 := make([]int, 10) fmt.Println(s0) fmt.Println(s1) fmt.Println(s2) fmt.Println(s3) fmt.Println() s0 = append(s0, 1, 2, 3) s1 = append(s1, 1, 2, 3) s2 = append(s2, 1, 2, 3) s3 = append(s3, 1, 2, 3) fmt.Println(s0) fmt.Println(s1) fmt.Println(s2) fmt.Println(s3) }
Výsledky:
[] [] [] [0 0 0 0 0 0 0 0 0 0] [1 2 3] [1 2 3] [1 2 3] [0 0 0 0 0 0 0 0 0 0 1 2 3]
10. Identifikátor nil a mapy
Dalším datovým typem inicializovaným na nulovou hodnotu zapisovanou identifikátorem nil jsou mapy (maps). V programovacím jazyce Go můžeme vytvořit novou mapu, která bude „nulová“ a nebude do ní možné přidávat další prvky. Podívejme se nyní na dva příklady deklarace takových map:
package main import "fmt" func main() { var m1 map[string]int = nil var m2 map[string]int fmt.Printf("%v\n", m1) fmt.Printf("%v\n", m2) }
Po spuštění tohoto příkladu se vypíšou dvě shodné hodnoty naznačující, že explicitní inicializace proměnné typu map na nil vede k vytvoření proměnné se stejným obsahem, jako když se žádná inicializace neprovede a překladač tedy použije implicitní „nulovou hodnotu“ pro tento datový typ:
map[] map[]
O tom, že prázdná mapa je skutečně ekvivalentní nil se můžeme velmi snadno přesvědčit:
package main import "fmt" func main() { var m1 map[string]int = nil var m2 map[string]int fmt.Printf("%v %v\n", m1, m1 == nil) fmt.Printf("%v %v\n", m2, m2 == nil) }
S výsledkem:
map[] true map[] true
Problém je, že do takové mapy není možné přidávat další dvojice klíč-hodnota. Pokud se o tuto operaci pokusíme, dojde k běhové chybě:
package main import "fmt" func main() { var m1 map[string]int = nil fmt.Printf("%v %v\n", m1, m1 == nil) m1["foo"] = 3 }
Pokus o spuštění tohoto příkladu skutečně skončí běhovou chybou:
map[] true panic: assignment to entry in nil map goroutine 1 [running]: main.main() /home/tisnik/temp/t3.go:9 +0xb7 exit status 2
Korektní zápis již vyžaduje použití interní funkce make, která zde vystupuje v roli konstruktoru mapy:
package main import "fmt" func main() { m1 := make(map[string]int) fmt.Printf("%v %v\n", m1, m1 == nil) m1["foo"] = 3 fmt.Printf("%v %v\n", m1, m1 == nil) }
Nyní již bude možné program spustit, a to bez chyby:
map[] false map[foo:3] false
11. Identifikátor nil a kanály
Programovací jazyk Go podporuje „nulové hodnoty“ i u kanálů. Můžeme si vyzkoušet, jakým způsobem se takové kanály vytvoří; princip je prakticky stejný, jako u map:
package main import "fmt" func main() { var c1 chan int = nil var c2 chan int fmt.Printf("%v %v\n", c1, c1 == nil) fmt.Printf("%v %v\n", c2, c2 == nil) fmt.Printf("%v\n", c1 == c2) }
Po spuštění programu získáme očekávané výsledky:
<nil> true <nil> true true
Rozdíl mezi nulovým a nenulovým kanálem je interně dosti podstatný, což nám odhalí debugger na následujícím příkladu:
package main import "fmt" func main() { var c1 chan int = nil var c2 chan int = make(chan int) fmt.Printf("%v %v\n", c1, c1 == nil) fmt.Printf("%v %v\n", c2, c2 == nil)
V debuggeru Delve si můžeme zobrazit, jak se liší nulový a nenulový kanál:
4: 5: func main() { 6: var c1 chan int = nil 7: var c2 chan int = make(chan int) 8: => 9: fmt.Printf("%v %v\n", c1, c1 == nil) 10: fmt.Printf("%v %v\n", c2, c2 == nil) 11: } (dlv) p c1 chan int {} (dlv) p c2 chan int { qcount: 0, dataqsiz: 0, buf: *[0]int [], elemsize: 8, closed: 0, elemtype: *runtime._type { size: 8, ptrdata: 0, hash: 4149441018, tflag: tflagUncommon|tflagExtraStar|tflagNamed (7), align: 8, fieldalign: 8, kind: 130, alg: *(*runtime.typeAlg)(0x577df0), gcdata: *1, str: 1051, ptrToThis: 47456,}, sendx: 0, recvx: 0, recvq: waitq { first: *sudog nil, last: *sudog nil,}, sendq: waitq { first: *sudog nil, last: *sudog nil,}, lock: runtime.mutex {key: 0},}
12. Čtení a zápis do nulového kanálu
Při práci s kanály je nutné brát do úvahy následující čtyři speciální případy:
- zápis do zavřeného kanálu vyvolá panic
- čtení ze zavřeného kanálu vrátí „nulovou hodnotu“, a to ihned (bez čekání na zápis, který nemůže být proveden)
- zápis do nulového kanálu zablokuje příslušnou gorutinu
- čtení z nulového kanálu taktéž zablokuje příslušnou gorutinu
Poslední dva body si můžeme snadno vyzkoušet. Nejdříve zápis do nulového kanálu:
package main import "fmt" func main() { var c1 chan int = nil fmt.Printf("%v %v\n", c1, c1 == nil) c1 <- 10 }
Výsledkem je detekce deadlocku:
<nil> true fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send (nil chan)]: main.main() /home/tester/go-root/article_26/24_nil_channel_write.go:10 +0xab exit status 2
Čtení z nulového kanálu:
package main import "fmt" func main() { var c1 chan int = nil fmt.Printf("%v %v\n", c1, c1 == nil) fmt.Printf("%d\n", <-c1) }
Výsledkem je opět detekce deadlocku:
<nil> true fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive (nil chan)]: main.main() /home/tester/go-root/article_26/25_nil_channel_read.go:10 +0xb5 exit status 2
13. Identifikátor nil a rozhraní
Konečně se dostáváme k datovému typu interface. I pro rozhraní existuje „nulová hodnota“, která je proměnným automaticky přiřazena ve chvíli, kdy se nespecifikuje jiná hodnota:
package main import "fmt" func main() { var i1 interface{} fmt.Printf("%v %v\n", i1, i1 == nil) }
Po spuštění tohoto demonstračního příkladu se vypíše:
<nil> true
Ve skutečnosti nezáleží na tom, zda jsou v rozhraní specifikovány nějaké metody, protože i následující příklad vypíše hodnoty nil:
package main import "fmt" func main() { var i1 interface{} fmt.Printf("%v %v\n", i1, i1 == nil) var i2 interface{Foo()} fmt.Printf("%v %v\n", i2, i2 == nil) fmt.Printf("%v\n", i1 == i2) }
S výsledkem:
<nil> true <nil> true true
Vidíme, že výchozí nulová hodnota datového typu interface je taktéž nil, který je dokonce shodný pro všechna nulová rozhraní. Ovšem právě zde se projevují jedna méně známá vlastnost typového systému programovacího jazyka Go, protože u typu interface se v runtime pamatuje jak definovaný typ proměnné (takzvaný statický typ), tak i datový typ hodnoty, která je do proměnné přiřazena (takzvaný dynamický typ). Proč tomu tak je? V Go je možné do proměnné typu interface přiřadit jakoukoli instanci typu, který toto rozhraní implementuje. A konkrétně prázdné rozhraní interface {} je automaticky implementováno všemi datovými typy. Podrobnosti jsou vysvětleny v další kapitole.
14. Proč někdy platí nil != nil?
Zkusme si nyní přeložit následující příklad, v němž je definován uživatelský datový typ T a jsou vytvořeny dvě proměnné. Proměnná i1 je typu interface{} a obsahuje hodnotu nil:
var i1 interface{}
Proměnná i2 je typu T a taktéž obsahuje hodnotu nil:
var i2 T = nil
Kód příkladu vypadá následovně:
package main import "fmt" type T *int func main() { var i1 interface{} fmt.Printf("%v %v\n", i1, i1 == nil) var i2 T = nil fmt.Printf("%v %v\n", i2, i2 == nil) fmt.Printf("%v\n", i1 == i2) }
Po spuštění tohoto příkladu se vypíše:
<nil> true <nil> true false
Platí tedy současně:
i1 == nil i2 == nil i1 != i2
Obě hodnoty jsou porovnatelné, protože typ T zcela jistě implementuje prázdné rozhraní, ovšem hodnoty nil se nerovnají. Je tomu tak z toho důvodu, že se liší jejich dynamické typy, protože porovnání dvou rozhraní vrátí hodnotu true za předpokladu:
- Obě rozhraní jsou nulové (nil)
- Dynamický typ obou rozhraní je porovnatelný (int s int například) a shodný a současně jsou i dynamické hodnoty shodné.
Informaci o typu lze relativně snadno získat a vytisknout:
package main import "fmt" type T *int func main() { var i1 interface{} var i2 T = nil fmt.Printf("%T\n", i1) fmt.Printf("%T\n", i2) fmt.Printf("%v\n", i1 == i2) }
S výsledkem:
<nil> main.T false
Příklad porovnání dvou proměnných typu rozhraní, které se rovnají na základě pravidla číslo 2:
package main import "fmt" type T int func main() { var i1 interface{} = "abcd" var i2 interface{} = "a" + "b" + "c" + "d" fmt.Printf("%T\n", i1) fmt.Printf("%T\n", i2) fmt.Printf("%v\n", i1 == i2) }
A konečně si ukažme příklad, v němž je porušeno pravidlo číslo 2:
package main import "fmt" type T int func main() { var p1 *int = nil var p2 *string = nil var i1 interface{} = nil var i2 interface{} = p1 var i3 interface{} = p2 fmt.Printf("%T\t%v\n", i1, i1) fmt.Printf("%T\t%v\n", i2, i2) fmt.Printf("%T\t%v\n", i3, i3) fmt.Println() fmt.Printf("%v\n", i1==i2) fmt.Printf("%v\n", i1==i3) fmt.Printf("%v\n", i2==i3) }
Výsledky:
<nil> <nil> *int <nil> *string <nil> false false false
15. Porovnání hodnot nil různých typů
V programovacím jazyce Go existují striktní pravidla pro porovnávání hodnot různých typů, což se pochopitelně týká i speciálních nulových hodnot. Porovnat je například možné dva ukazatele stejného typu:
package main import "fmt" func main() { var p1 *int var p2 *int fmt.Printf("%v %v\n", p1, p1 == nil) fmt.Printf("%v %v\n", p2, p2 == nil) fmt.Printf("%v\n", p1 == p2) }
Ovšem například porovnání ukazatelů různých typů již není povoleno a povede k detekci chyby při překladu:
package main import "fmt" func main() { var i1 *int var i2 *int32 fmt.Printf("%v %v\n", i1, i1 == nil) fmt.Printf("%v %v\n", i2, i2 == nil) fmt.Printf("%v\n", i1 == i2) }
Zajímavé je, že můžeme porovnat ukazatel s rozhraním:
package main import "fmt" func main() { var i1 interface{} fmt.Printf("%v %v\n", i1, i1 == nil) var i2 *int fmt.Printf("%v %v\n", i2, i2 == nil) fmt.Printf("%v\n", i1 == i2) }
V tomto konkrétním případě se vypíše:
<nil> true <nil> true false
To znamená, že (poněkud neintuitivně) současně platí:
i1 == nil i2 == nil i1 != i2
16. Datový typ Option v programovacím jazyku Rust
V předchozím textu jsme si řekli, že nil se v programovacím jazyku Go používá například i tehdy, pokud potřebujeme volající funkci sdělit, zda došlo či naopak nedošlo k chybě. Současně se může nil použít pro reprezentaci neznámé či nezjistitelné hodnoty. To je poměrně problematický přístup, který je v dalších programovacích jazycích vyřešen odlišným způsobem. Velmi dobrým příkladem může být programovací jazyk Rust, v němž existují dva důležité datové typy Option a Result.
V programovacím jazyku Rust se poměrně často používá datový typ Option, a to ve chvílích, kdy je zapotřebí reprezentovat neznámou hodnotu, vytvořit funkci s volitelnými parametry či vytvořit typově bezpečnou obdobu odkazu typu null či nil.
Deklarace datového typu Option je ve skutečnosti velmi přímočará:
enum Option<T> { None, Some(T), }
Vidíme, že se jedná o výčtový typ s pouhými dvěma hodnotami None a Some, přičemž Some „obaluje“ vlastní hodnotu typu T, se kterou chceme pracovat (může se jednat o prakticky libovolný typ Rustu, pochopitelně včetně uživatelských typů).
Ukažme si příklad použití ve chvíli, kdy logika aplikace požaduje, aby byl výsledek dělení 0/0 nedefinovaný, ovšem aby se nejednalo o chybu:
fn div(x: i32, y: i32) -> Option<i32> { if y != 0 { Some(x/y) } else { None } }
Nejjednodušší způsob volání uživatelsky definované funkce div vypadá následovně (je nutné použít {:?}):
fn main() { let z1 = div(2, 1); println!("{:?}", z1); let z2 = div(2, 0); println!("{:?}", z2); }
S následujícím výsledkem:
Some(2) None
Jednou z největších předností datového typu Option je fakt, že jeho používání je v programovacím jazyku Rust do značné míry standardní a navíc idiomatické, takže programátoři nemusí hledat, která „magická konstanta“ je pro danou funkci použita. Dále je zaručeno, že pokud budeme chtít získat zabalenou hodnotu přes pattern matching, bude nutné explicitně použít i druhou větev pracující s výsledkem None:
fn div_and_print(x: i32, y :i32) { let result = div(x, y); println!("{:?}", result); match result { None => println!("Divide by zero"), Some(val) => println!("{} / {} = {}", x, y, val), } println!(""); }
Pro hodnoty typu Option je navíc možné volat různé více či méně užitečné metody, například is_none(), is_some(), expect(), unwrap(), and_then(), or_else() a různé varianty funkce vyššího řádu map(). Mimochodem – tato struktura se používá i v případě, že potřebujeme pracovat s referencemi, které v některých situacích nemusí existovat (což nám jinak Rust nedovolí).
17. Datový typ Result, opět v Rustu
V mnoha případech však nemusí být použití datového typu Option tím nejlepším řešením, popř. se nemusí jednat o řešení idiomatické. Pro příklad nemusíme chodit daleko – předpokládejme, že budeme chtít, aby naše funkce pro dělení celých čísel vracela v případě pokusu o dělení nulou chybové hlášení a nikoli nicneříkající hodnotu None. K tomuto účelu se v programovacím jazyku Rust používá datová struktura nazvaná příhodně Result. Tato datová struktura se podobá výše popsané struktuře Option, ovšem s tím podstatným rozdílem, že obaluje buď výsledek (třeba návratovou hodnotu volané funkce) nebo informaci o chybě. Deklarace struktury Result z tohoto důvodu vypadá následovně:
enum Result<T, E> { Ok(T), Err(E), }
což se liší od deklarace typu Option:
enum Option<T> { None, Some(T), }
Zkusme si nyní upravit naši funkci určenou pro dělení dvou celých čísel takovým způsobem, aby se v případě dělení nulou namísto hodnoty None vracelo plnohodnotné chybové hlášení ve formě řetězce. Úprava je velmi snadná a může vypadat následovně:
fn div(x: i32, y: i32) -> Result<i32, &'static str> { if y != 0 { Ok(x/y) } else { Err("Divide by zero!") } }
Prozatím si vypočtené hodnoty vytiskneme jednoduše makrem println!() a formátovacím příkazem „?:“:
fn main() { let z1 = div(2, 1); println!("{:?}", z1); let z2 = div(2, 0); println!("{:?}", z2); }
Po spuštění tohoto příkladu se na prvním řádku vypíše vypočtená hodnota obalená do „Ok“ a na řádku druhém pak chybové hlášení, tentokrát obalené do „Err“:
Ok(2) Err("Divide by zero!")
Ve skutečnosti se často namísto predikátů a čtení zabalené hodnoty či chybové zprávy používá pattern matching. Další příklad se nápadně podobá příkladu, který již známe z předchozí kapitoly:
fn print_div_result(result: Result<i32, &'static str>) { match result { Ok(value) => println!("value: {}", value), Err(error) => println!("error: {}", error) } }
18. Nebylo by tedy výhodnější použít obdobu typu Option?
Řešení nabízené programovacím jazykem Rust pro rozlišení mezi skutečnou hodnotou a chybou, popř. mezi skutečnou hodnotou a nedefinovaným výsledkem, je v několika ohledech lepší, než například řešení jazyka Go (a většinou i lepší, než pouhé vrácení hodnoty s tím, že případná chyba povede k vyhození výjimky). Velkou předností Rustu je v tomto ohledu ta skutečnost, že si překladač sám hlídá, zda programátor správně testuje všechny stavy, které mohou v aplikaci nastat. Pokud bude nějaká větev v řídicí struktuře match chybět, program se nepřeloží:
fn div_and_print(x: i32, y :i32) { let result = div(x, y); match result { Some(val) => println!("{} / {} = {}", x, y, val), } println!(""); }
Ani tento kód není korektní a nepřeloží se:
fn print_div_result(result: Result<i32, &'static str>) { match result { Ok(value) => println!("value: {}", value), } }
V programovacím jazyce Go není tato situace hlídána – funkce pouze (v typických případech) vrací dvojici hodnot a záleží jen na programátorovi, zda druhou (chybovou) hodnotu nějak zpracuje či nikoli:
func testCopyFile(srcName, dstName string) { copied, _ := copyFile(srcName, dstName) fmt.Printf("Copied %d bytes\n", copied) }
Základ problému pravděpodobně spočívá v tom, že by zavedení typu Option a Result vyžadovalo úpravu samotného programovacího jazyka takovým způsobem, aby podporoval generické datové typy. Tato vlastnost je však prozatím pouze ve fázi návrhu; případné změny a rozšíření se případně promítnou až do Go 2.0. Prozatím je tedy nutné pracovat s hodnotami nil a nezapomenou do programů zapisovat podmínky pro otestování tohoto (většinou) chybového či jinak výjimečného stavu. A pochopitelně si hlavně dát pozor na to, že není nil jako nil.
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitý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á přibližně dva megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - 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 - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - 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 - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - 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/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go