Hlavní navigace

Problematika nulových hodnot v Go, aneb proč nil != nil

Pavel Tišnovský

Dnes si podrobněji vysvětlíme koncept takzvaných nulových hodnot a taktéž typového systému jazyka Go. Na první pohled se sice může jednat o triviální problematiku, ovšem vlastní řešení typového systému může vývojáře překvapit.

Doba čtení: 45 minut

Sdílet

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

20. Odkazy na Internetu

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í.

Poznámka: to, že nil má v jazyku Go doslova „polymorfní“ vlastnosti (v původním smyslu tohoto slova) je dosti zajímavé a vlastně i výjimečné, protože naprostá většina ostatních konstrukcí tohoto programovacího jazyka je navržena takovým způsobem, aby bylo jejich použití jednoznačné a ideálně i kontrolovatelné překladačem. V případě nil tomu tak není, o čemž ostatně svědčí i relativně časté dotazy začátečníků, proč v jejich aplikacích nastala situace naznačená v titulku dnešního článku: nil != nil.

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)
Poznámka: funkce println() je poměrně nízkoúrovňová (nesnaží se správně interpretovat všechny typy hodnot) a nedoporučuje se ji používat v produkčním kódu, protože se její chování může v budoucích verzích programovacího jazyka Go změnit.

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> []}
Poznámka: tato pravidla platí i pro strukturu ve struktuře:
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()
}
Poznámka: pokud má funkce vracet více než jednu hodnotu a jednu chybovou hodnotu, je podle zaběhaných konvencí vhodné, aby byla chybová hodnota vždy vrácena jako poslední.

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
Poznámka: z pohledu tvorby rozsáhlejších projektů se jedná o potenciálně problematickou vlastnost samotného jazyka Go. V jiných programovacích jazycích tuto zvláštnost většinou nenajdeme (ovšem jednou z výjimek je céčko, které řeší konstanty typu NULL formou maker), o čemž se můžeme poměrně snadno přesvědčit.

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)
Poznámka: v Javě nejsou null, true a false klíčovými slovy, ale patří mezi rezervovaná slova.

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
Poznámka: tímto chováním se jazyk Go odlišuje od čistě dynamicky typovaných jazyků, mezi něž patří například Python. V Pythonu je totiž typ svázaný přímo s hodnotou a nikoli s proměnnou. Proto je v Pythonu možné bez problému napsat například:
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
Poznámka: na tomto místě je ovšem nutné poznamenat, že přístup přes nil ukazatel je v naprosté většině případů jediným důvodem, proč dojde k segmentation violation. Na rozdíl od programovacího jazyka C totiž není v Go možné do ukazatele přímo uložit jinou konstantu než zmíněný nil, takže přístup na náhodnou adresu není možný (a zbylé problémy typu „přístup na uvolněnou paměť“vyřeší GC):
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
Poznámka2: to, že po deklaraci nové proměnné typu ukazatel je do ní přiřazena právě hodnota nil, lze snadno zjistit i z debuggeru příkazem p/print:
> 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:

  1. 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.
  2. Délky řezu, tj. počtu prvků, který je aktuálně řezem reprezentován.
  3. 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.

Poznámka: způsob uložení řezu v paměti: 64bitů ukazatel na pole, 64bitů délka řezu, 64bitů kapacita řezu.

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
Poznámka: v tomto případě nemůžeme dvě mapy porovnat operátorem ==, protože mapy se v jazyku Go mohou porovnávat pouze s hodnotou nil!

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
Poznámka: můžeme zde vidět zásadní rozdíl mezi „nulovou mapou“ a „prázdnou mapou“. Prakticky nulovou mapu v běžných programech nepoužijeme, takže je nutné ve většině případů mapy vytvářet pomocí funkce make.

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:

  1. zápis do zavřeného kanálu vyvolá panic
  2. č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)
  3. zápis do nulového kanálu zablokuje příslušnou gorutinu
  4. č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:

  1. Obě rozhraní jsou nulové (nil)
  2. 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),
}
Poznámka: povšimněte si, že se u datové struktury Result specifikují dva typy – typ návratové hodnoty T a typ hodnoty reprezentující chybu E. To je poměrně užitečná vlastnost, protože se programátoři mohou sami rozhodnout, jakým způsobem budou reprezentovat chybu – zda se bude jednat o jednoduchý řetězec či o složitější datovou strukturu, ve které může být například uloženo jméno otevíraného souboru a současně chybové hlášení systému při pokusu o jeho otevření.

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:

# Soubor Popis Cesta
1 01_boolean_type.go „nulová hodnota“ pro typ bool https://github.com/tisnik/go-root/blob/master/article26/01_bo­olean_type.go
2 02_number_types.go „nulové hodnoty“ pro všechny numerické datové typy https://github.com/tisnik/go-root/blob/master/article26/02_num­ber_types.go
3 03_string_type.go „nulová hodnota“ pro řetězec https://github.com/tisnik/go-root/blob/master/article26/03_strin­g_type.go
4 04_other_types.go „nulové hodnoty“ pro ostatní standardní typy https://github.com/tisnik/go-root/blob/master/article26/04_ot­her_types.go
5 05_other_types_better_solution.go vylepšení předchozího příkladu https://github.com/tisnik/go-root/blob/master/article26/05_ot­her_types_better_solution­.go
6 06_structs.go „nulové hodnoty“ pro struktury/záznamy https://github.com/tisnik/go-root/blob/master/article26/06_struc­ts.go
7 07_nil_as_variable.go nil použitý jako jméno proměnné https://github.com/tisnik/go-root/blob/master/article26/07_nil_as_va­riable.go
8 08_true_false_vars.go true a false použité jako jméno proměnné https://github.com/tisnik/go-root/blob/master/article26/08_tru­e_false_vars.go
9 09_nil_value.go nil ve funkci hodnoty s dále nespecifikovaným datovým typem https://github.com/tisnik/go-root/blob/master/article26/09_nil_va­lue.go
10 10_nil_pointer.go ukazatel s implicitní hodnotou nil a jeho chování v runtime https://github.com/tisnik/go-root/blob/master/article26/10_nil_po­inter.go
11 11_other_pointer.go běžný ukazatel, který se snažíme nastavit na určitou hodnotu https://github.com/tisnik/go-root/blob/master/article26/11_ot­her_pointer.go
12 12_nil_slice.go „nulová hodnota“ řezu a další typy řezů https://github.com/tisnik/go-root/blob/master/article26/12_nil_sli­ce.go
13 13_nil_map.go „nulová hodnota“ mapy https://github.com/tisnik/go-root/blob/master/article26/13_nil_map­.go
14 14_nil_map_is_really_nil.go test mapy na hodnotu nil https://github.com/tisnik/go-root/blob/master/article26/14_nil_map_is_re­ally_nil.go
15 15_add_into_nil_map.go pokus o přidání prvku do nulové mapy https://github.com/tisnik/go-root/blob/master/article26/15_ad­d_into_nil_map.go
16 16_empty_map.go pokus o přidání prvku do prázdné mapy https://github.com/tisnik/go-root/blob/master/article26/16_em­pty_map.go
17 17_nil_interface.go „nulová hodnota“ pro rozhraní https://github.com/tisnik/go-root/blob/master/article26/17_nil_in­terface.go
18 18_two_interfaces.go dvě rozdílná rozhraní https://github.com/tisnik/go-root/blob/master/article26/18_two_in­terfaces.go
19 19_nil_is_not_nil.go příklad, v němž se dvě hodnoty nil nerovnají https://github.com/tisnik/go-root/blob/master/article26/19_nil_is_not_nil­.go
20 20_two_nil_pointers.go porovnání dvou ukazatelů https://github.com/tisnik/go-root/blob/master/article26/20_two_nil_po­inters.go
21 21_comparing_interface_to_pointer.go porovnání hodnoty typu ukazatel a rozhraní https://github.com/tisnik/go-root/blob/master/article26/21_com­paring_interface_to_pointer­.go
22 22_compare_two_different_po­inter_types.go pokus o porovnání dvou ukazatelů rozdílného typu https://github.com/tisnik/go-root/blob/master/article26/22_com­pare_two_different_pointer_ty­pes.go

20. Odkazy na Internetu

  1. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  2. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  3. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  4. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  5. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  6. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  7. 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/
  8. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  9. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  10. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  11. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  12. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  13. The LLDB Debugger
    http://lldb.llvm.org/
  14. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  15. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  16. 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
  17. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  18. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  19. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  20. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  21. 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/
  22. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  23. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  24. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  25. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  26. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  27. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  28. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  29. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  30. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  31. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  32. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  33. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  34. go-cron
    https://github.com/rk/go-cron
  35. gocron
    https://github.com/jasonlvhit/gocron
  36. clockwork
    https://github.com/whiteShtef/cloc­kwork
  37. clockwerk
    https://github.com/onatm/clockwerk
  38. JobRunner
    https://github.com/bamzi/jobrunner
  39. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  40. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  41. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  42. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  43. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  44. go-prompt
    https://github.com/c-bata/go-prompt
  45. readline
    https://github.com/chzyer/readline
  46. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  47. go-readline
    https://github.com/fiorix/go-readline
  48. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  49. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  50. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  51. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  52. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  53. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  54. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  55. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  56. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  57. Editline Library (libedit)
    http://thrysoee.dk/editline/
  58. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  59. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  60. WinEditLine
    http://mingweditline.sourceforge.net/
  61. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  62. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  63. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  64. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  65. history(3) – Linux man page
    https://linux.die.net/man/3/history
  66. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  67. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  68. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  69. Balíček ogletest
    https://github.com/jacobsa/ogletest
  70. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  71. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  72. package testing
    https://golang.org/pkg/testing/
  73. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  74. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  75. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  76. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  77. GoConvey
    http://goconvey.co/
  78. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  79. 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
  80. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  81. package gg
    https://godoc.org/github.com/fo­gleman/gg
  82. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  83. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  84. The Go image package
    https://blog.golang.org/go-image-package
  85. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  86. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  87. YAML
    https://yaml.org/
  88. edn
    https://github.com/edn-format/edn
  89. Smile
    https://github.com/FasterXML/smile-format-specification
  90. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  91. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  92. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  93. Introducing JSON
    http://json.org/
  94. Package json
    https://golang.org/pkg/encoding/json/
  95. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  96. Go by Example: JSON
    https://gobyexample.com/json
  97. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  98. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  99. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  100. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  101. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  102. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  103. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  104. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  105. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  106. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  107. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  108. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  109. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  110. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  111. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  112. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  113. Algorithms to Go
    https://yourbasic.org/
  114. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  115. 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/
  116. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  117. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  118. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  119. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  120. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  121. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  122. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  123. The Go Programming Language (home page)
    https://golang.org/
  124. GoDoc
    https://godoc.org/
  125. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  126. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  127. The Go Programming Language Specification
    https://golang.org/ref/spec
  128. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  129. Package builtin
    https://golang.org/pkg/builtin/
  130. Package fmt
    https://golang.org/pkg/fmt/
  131. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  132. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  133. Learning Go
    https://www.miek.nl/go/
  134. Go Bootcamp
    http://www.golangbootcamp.com/
  135. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  136. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  137. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  138. The Go Blog
    https://blog.golang.org/
  139. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  140. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  141. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  142. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  143. 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
  144. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  145. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  146. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  147. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  148. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  149. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  150. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  151. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  152. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  153. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  154. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  155. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  156. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  157. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  158. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  159. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  160. 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/
  161. 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
  162. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  163. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  164. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  165. Go vs. Python
    https://www.peterbe.com/plog/govspy
  166. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  167. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  168. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  169. Go by Example: Slices
    https://gobyexample.com/slices
  170. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  171. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  172. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  173. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  174. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  175. nils In Go
    https://go101.org/article/nil.html
  176. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  177. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  178. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  179. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  180. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  181. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  182. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  183. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  184. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  185. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  186. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  187. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  188. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  189. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  190. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  191. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  192. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  193. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  194. 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
  195. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  196. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  197. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  198. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  199. Selectors
    https://golang.org/ref/spec#Selectors
  200. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  201. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  202. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  203. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  204. Part 21: Goroutines
    https://golangbot.com/goroutines/
  205. Part 22: Channels
    https://golangbot.com/channels/
  206. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  207. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  208. 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/
  209. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  210. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  211. Control Structures
    https://www.golang-book.com/books/intro/5
  212. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  213. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  214. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  215. Different ways to pass channels as arguments in function in go (golang)
    https://stackoverflow.com/qu­estions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang
  216. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  217. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  218. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  219. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  220. Effective Go
    https://golang.org/doc/ef­fective_go.html
  221. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  222. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  223. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  224. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  225. Nils in Go
    https://www.doxsey.net/blog/nils-in-go