Hlavní navigace

Skriptovací jazyk Lua v aplikacích naprogramovaných v Go

Pavel Tišnovský

Dnes se seznámíme s tím, jak je možné aplikace naprogramované v Go ovládat (skriptovat) s využitím skriptovacího jazyka Lua, který je pro tyto účely, tj. pro vestavění do jiných aplikací, poměrně často používán.

Doba čtení: 46 minut

Sdílet

11. Zpracování návratové hodnoty

12. Konverze návratových hodnot na typy kompatibilní s jazykem Go

13. Datový typ boolean v programovacím jazyce Lua

14. Datový typ nil v programovacím jazyce Lua

15. Zpracování většího množství návratových hodnot ze skriptu naprogramovaného v jazyce Lua

16. Volání funkcí naprogramovaných v Go z Lua skriptu

17. Registrace funkce, která se má z Lua skriptů zavolat

18. Zpracování parametrů a návratových hodnot

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

20. Odkazy na Internetu

1. Využití jazyka Lua v aplikacích naprogramovaných v Go

Programovací jazyk Lua, jemuž jsme se věnovali v samostatném seriálu, patří do poměrně rozsáhlé a stále častěji používané skupiny vysokoúrovňových skriptovacích jazyků, do níž můžeme zařadit například dnes velmi populární Python, jazyk Perl, Ruby, JavaScript popř. TypeScript či dnes již poněkud méně populární skriptovací jazyk Tcl. Tyto programovací jazyky nabízí vývojářům jednoduchou práci se strukturovanými daty (většinou je použita nějaká forma asociativního pole), dynamicky typované proměnné, automatickou správu paměti (garbage collector) a mnohé další vysokoúrovňové techniky zjednodušující a především do značné míry zrychlující vývoj, ať již prototypů, či výsledných aplikací. Jazyk Lua má navíc velmi jednoduchou – a pro mnoho vývojářů důvěrně známou – syntaxi inspirovanou Modulou a Pascalem, zatímco sémantika jazyka se v mnohém podobá spíše moderním verzím JavaScriptu a Pythonu.

Někteří programátoři si jazyk Lua oblíbili právě kvůli jeho syntaxi, která zbytečně nepřináší žádné nové prvky (snad jen zápis relačního operátoru nerovnosti pomocí ~= je přinejmenším podivný a neobvyklý; někoho zpočátku může mást i indexování prvků od jedničky). Naopak se snaží programátorům ulehčit život, například možností zápisu vícenásobného přiřazení, přístupu k položkám asociativního pole jak pomocí „tečkové“ notace, tak i s využitím hranatých závorek apod. Jednoduše použitelná syntaxe a současně i velká vyjadřovací schopnost jazyka Lua by však pravděpodobně ve velké konkurenci v žádném případě nedostačovala pro jeho masovější rozšíření. Důvodem, proč jsou některé hry, například Escape from Monkey Island, Grim Fandango, Fish Fillets, Neverwinter Nights či MDK2 z menší či větší části naprogramované právě v Lue, spočívá v tom, že kombinace nízkoúrovňového a skriptovacího jazyka umožňuje soustředit se při vývoji na podstatné věci – herní engine vytvořit co nejefektivnější s využitím všech možností nízkoúrovňového jazyka a naopak herní scénář a logiku hry naskriptovat s co největším urychlením cyklu oprava–překlad–spuštění.

V mnoha případech se také využívá další užitečné vlastnosti jazyka Lua – celý překladač i interpret vygenerovaného bajtkódu (popř. pouze interpret – všechny pojmy jsou vysvětleny dále) je možné velmi snadno zabudovat do jiné aplikace, přičemž se výsledná velikost spustitelného souboru této aplikace zvětší o cca 70 kB (popř. lze volat dynamickou knihovnu o řádově stejné velikosti), což není mnoho, když si uvědomíme, že dostáváme k dispozici plnohodnotný vysokoúrovňový programovací jazyk (ostatně Lua se díky své malé velikosti používá i pro „pouhé“ zpracování konfiguračních souborů, které díky tomu mohou obsahovat různé konstanty, výrazy, podmínky atd.). Mnozí programátoři, mezi jinými i John Walker (jeden z vývojářů AutoCADu) se netají tím, že právě zabudování programovacího (skriptovacího) jazyka do jejich aplikací mělo velký význam pro jejich úspěch, protože to umožnilo mnoha dalším vývojářům rozšiřovat funkčnost původní aplikace a tím zvýšit její atraktivitu pro další uživatele.

2. Programovací jazyk Lua „vestavitelný“ do dalších aplikací

Jeden z dalších důvodů relativně velké oblíbenosti programovacího jazyka Lua mezi vývojáři spočívá v tom, že její překladač i interpret je velmi snadno „vestavitelný“ do dalších aplikací, což znamená, že do prakticky libovolného programu je možné zabudovat buď plnohodnotný překladač tohoto jazyka, nebo pouze tu část, která se stará o běh přeloženého bajtkódu (častěji se však setkáme s prvním způsobem integrace). V některých typech aplikací, například počítačových hrách, totiž nemusí být nutné překládat nové zdrojové kódy, ale pouze spouštět bajtkód přeložený přímo výrobcem hry; další aplikace naopak mohou těžit z toho, že jsou uživatelsky skriptovatelné (viz většina moderních „Office“, mnohé programy typu CAD, grafické a textové editory a mnoho dalších programů). Samozřejmě se nejedná o unikátní vlastnost, protože i mnoho interpretů dalších programovacích jazyků lze vestavět do jiných aplikací – v poslední době se stává populární především JavaScript vedle již zavedeného Pythonu (LibreOffice a samozřejmě i OpenOffice.org, GIMP), Scheme (opět GIMP), Lispu (výše zmíněný AutoCAD, Emacs) či Visual Basicu (MS Office a další aplikace podporující VBA neboli Visual Basic for Applications).

Ovšem v případě programovacího jazyka Lua je její vestavění do aplikace skutečně snadné – z pohledu programátora (především pokud programuje v céčku či C++), který ve své aplikaci potřebuje použít nějaký skriptovací jazyk, se jedná o pouhých několik programových řádků s následným slinkováním s objektovým kódem uloženým v archivu liblua.a. Vložením celého překladače a interpretu jazyka Lua včetně jeho podpůrného běhového prostředí (základní funkce, garbage collector aj.) se zvětší velikost výsledného spustitelného souboru o již zmíněných 70 kB, což není nijak závratná hodnota, především při porovnání velikostí interpretů dalších programovacích jazyků. Lua se právě z tohoto důvodu dokonce používá i na mikrořadičích s poměrně malou operační pamětí a pamětí ROM (v jedné z aplikací využívajících Lua byl použit mikrořadič s 64 kB RAM a 256 kB EEPROM). V tomto případě se většinou využívá pouze ta část interpretu, která se stará o běh přeloženého bajtkódu, v některých situacích se také mění základní numerický datový typ na šestnáctibitové či třicetidvoubitové hodnoty namísto hodnot uložených ve formátu plovoucí tečky (viz soubor luaconf.h, především definice LUA_NUMBER).

Vestavěný interpret programovacího jazyka Lua do jisté míry řeší taktéž otázku bezpečnosti skriptů, aby se zabránilo šíření makrovirů, které byly tak „populární“ mezi uživateli jednoho nejmenovaného rozšířeného kancelářského balíku. Problém bezpečnosti je řešen především prakticky úplnou izolací běhového prostředí skriptů od ostatního systému. Pouze přímo programátor aplikace, která má obsahovat překladač a interpret Lua, může (explicitně zapsaným importem příslušné knihovny) skriptům povolit například možnost práce se soubory, spouštění dalších programů přes volání os.execute() apod. Bez importu těchto knihoven je skriptu povoleno se svým okolím komunikovat pouze pomocí volání zaregistrovaných funkcí. Pro předávání parametrů se navíc používá zvláštní zásobník, ne standardní zásobníkové rámce (na klasický zásobníkový rámec se ukládá pouze jeden ukazatel), takže skripty vlastně ani nemají možnost manipulovat se zásobníkem procesu pod kterým běží (tím se eliminují útoky typu stack overflow). Interpret provádí i základní kontrolu korektnosti předaného bajtkódu.

3. Vestavění jazyka Lua do vlastních aplikací naprogramovaných v Go

Vzhledem k tomu, že interpret skriptovacího jazyka Lua je navržen takovým způsobem, aby byl poměrně snadno implementovatelný, asi nebude větším překvapením, že jeho zdrojové kódy byly portovány i do programovacího jazyka Go. To znamená, že se jedná o novou „go-nativní“ implementaci interpretru Luy, která nevyžaduje, aby se z runtime jazyka Go volaly funkce z původní céčkovské knihovny. Touto problematikou jsme se sice prozatím do všech podrobností nezabývali, ovšem ve stručnosti lze říct, že kooperace mezi runtime jazyka Go a nativními céčkovými knihovnami může v praxi způsobovat problémy a není z pohledu programátorů zcela transparentní. Samotné vložení interpretru jazyka Lua do aplikace psané v Go je ovšem velmi snadné – programátor jen musí naimportovat příslušný balíček, inicializovat virtuální stroj jazyka Lua a spustit nějaký skript, jenž může být reprezentován řetězcem nebo může být načten ze souboru. Skript poté může volat předem vybrané funkce naprogramované v jazyku Go a naopak – z Go lze volat libovolnou funkci z Lua skriptu.

Poznámka: ve skutečnosti existuje hned několik projektů, které programátorům používajícím jazyk Go zpřístupňují interpret skriptovacího jazyka Lua. Jedná se o projekty Gopher-Lua, go-lua a taktéž golua (interface k původnímu céčkovému interpretru). Rozšířením dnešní knihovny je Binder.

Dnes se budeme zabývat projektem nazvaným Gopher-Lua. Ten se instaluje velmi snadno – stejně jako jakýkoli jiný balíček určený pro Go:

$ go get github.com/yuin/gopher-lua

Výsledkem bude běžný balíček, nikoli spustitelný binární soubor – Gopher-Lua předpokládá, že se jazyk Lua skutečně bude vkládat (embed) do jiných aplikací.

Poznámka: počítejte s tím, že Lua implementovaná v Go není v žádném případě pojata tak minimalisticky, jako je tomu u původní céčkové varianty. Výsledný binární soubor po přidání balíčku Gopher-Lua naroste o cca 1,7 MB (před případným strip-em), což je již velké číslo. Samotný přeložený balíček gopher-lua.a má na 64bitovém systému velikost 2043996 bajtů.

4. Načtení a spuštění Lua skriptu uloženého v řetězci

V této kapitole si ukážeme, jakým způsobem se vlastně přímo z aplikace naprogramované v Go inicializuje virtuální stroj skriptovacího jazyka Lua a jak se v něm spustí nějaký skript.

Poznámka: pod pojmem „virtuální stroj“ si nepředstavujte nějaký obrovský a „všemocný“ nástroj typu JRE, protože v případě jazyka Lua se jedná o poměrně minimalistický projekt.

V aplikaci, která chce uživatelům nabídnout možnosti skriptování s využitím jazyka Lua, je nejprve nutné naimportovat balíček s implementací interpretru a VM Luy:

import "github.com/yuin/gopher-lua"

Jednotlivé instance VM (může jich být totiž větší množství) se inicializují zavoláním funkce . Výsledkem je struktura, přes kterou se s vytvořeným virtuálním strojem komunikuje:

luaVM := lua.NewState()

Inicializovaný virtuální stroj by se měl před ukončením aplikace korektně zastavit a uvolnit všechny své prostředky. V Go je možné tento požadavek splnit velmi snadno s využitím konstrukce defer:

defer luaVM.Close()

A nakonec můžeme spustit libovolný skript či jeho část. V tom nejjednodušším případě je skript reprezentován řetězcem a při pokusu o jeho spuštění můžeme zjistit, zda nedošlo k nějaké chybě:

err := luaVM.DoString(`print("Hello from Lua")`)
if err != nil {
        log.Fatal(err)
}

Úplný zdrojový kód dnešního prvního demonstračního příkladu, který po svém spuštění provede inicializaci virtuálního stroje jazyka Lua a spustí v něm jednořádkový skript, vypadá následovně:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
func main() {
        luaVM := lua.NewState()
 
        defer luaVM.Close()
 
        err := luaVM.DoString(`print("Hello from Lua")`)
        if err != nil {
                log.Fatal(err)
        }
}

Po spuštění tohoto demonstračního příkladu se vypíše jediný řádek textu:

Hello from Lua

Další demonstrační příklady nepatrně vylepšíme tím, že se při inicializaci i deinicializaci virtuálního stroje jazyka Lua budou vypisovat zprávy do logu s využitím standardního balíčku log:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoString(`print("Hello from Lua!")`)
        if err != nil {
                log.Fatal(err)
        }
}

Výsledek může vypadat například takto:

2019/05/24 20:50:43 Lua VM has been initialized
Hello from Lua!
2019/05/24 20:50:43 Lua VM has been closed

5. Víceřádkové řetězce v jazyku Go

Již v prvních dvou demonstračních příkladech jsme se mohli setkat s tím, že skripty zapsané v jazyku Lua byly reprezentovány řetězcem, ovšem tento řetězec nebyl uzavřen do běžných uvozovek "", ale do zpětných apostrofů ``. Jedná se o takzvaný „raw string literal“, což je konstanta typu řetězec, který je při kompilaci načten přesně v takové formě, jak je zapsán, tj. bez interpretace speciálních znaků začínajících zpětným lomítkem atd. V těchto řetězcích je taktéž možné použít konce řádků, což není u běžných řetězců povoleno (minimálně nikoli v programovacím jazyku Go). Naproti tomu pokud je řetězec uzavřen do běžných uvozovek "", jedná se o takzvaný „interpreted string literal“, v němž jsou speciálně interpretována zpětná lomítka, za nimiž je možné zapsat kód speciálního znaku (\n je konec řádku, \t znak Tab, \xnn ASCII kód znaku, \unnn a \Unnnnnnnn Unicode znak atd.). V těchto řetězcích se nesmí použít konec řádku, což je ovšem dosti nevýhodné pro zápis kódu v jiném programovacím jazyce do řetězce.

Právě z tohoto důvodu jsme v demonstračních příkladech použili řetězce zapsané do zpětných apostrofů. A díky tomu, že v raw řetězcích je možné použít i konce řádků, můžeme do nich bez problémů zapsat i složitější několikařádkové skripty, například:

const Script = `
for i = 1,10 do
    print("Hello from Lua!", i)
end`
Poznámka: pochopitelně je nutné dát pozor na to, aby se ve skriptech nevyskytoval zpětný apostrof, což však není v jazyku Lua příliš často.

Úplný demonstrační příklad, v němž je použit několikařádkový Lua skript, může vypadat následovně:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const Script = `
for i = 1,10 do
    print("Hello from Lua!", i)
end`
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        log.Println("Starting the following Lua script:")
        log.Println(Script)
 
        err := luaVM.DoString(Script)
        if err != nil {
                log.Fatal(err)
        }
}

Po spuštění tohoto příkladu by se na standardním výstupu měly objevit informace o inicializaci virtuálního stroje jazyka Lua a následně i výsledek spuštěného skriptu (celkem deset řádků):

2019/05/24 20:50:54 Lua VM has been initialized
2019/05/24 20:50:54 Starting the following Lua script:
2019/05/24 20:50:54
for i = 1,10 do
    print("Hello from Lua!", i)
end
Hello from Lua! 1
Hello from Lua! 2
Hello from Lua! 3
Hello from Lua! 4
Hello from Lua! 5
Hello from Lua! 6
Hello from Lua! 7
Hello from Lua! 8
Hello from Lua! 9
Hello from Lua! 10
2019/05/24 20:50:54 Lua VM has been closed

6. Načtení a spuštění skriptů uložených v samostatných souborech

Spuštění skriptů reprezentovaných řetězci jazyka Go se v praxi pravděpodobně nebude příliš často používat. Mnohem častěji se setkáme s požadavkem, aby se spustily skripty uložené v externích souborech. I tuto možnost nám pochopitelně interpret programovacího jazyka Go nabízí, což si ukážeme na dalším demonstračním příkladu. V tomto příkladu je použita metoda pro načtení a spuštění Lua skriptu přečteného z externího souboru:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "hello.lua"
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
}

Samotný skript hello.lua je až triviálně jednoduchý:

for i = 1,10 do
    print("Hello #", i)
end

Výsledek spuštění předchozího demonstračního příkladu:

2019/05/26 20:31:58 Lua VM has been initialized
Hello # 1
Hello # 2
Hello # 3
Hello # 4
Hello # 5
Hello # 6
Hello # 7
Hello # 8
Hello # 9
Hello # 10
2019/05/26 20:31:58 Lua VM has been closed

7. Inicializace několika virtuálních strojů jazyka Lua

Každý virtuální stroj získaný zavoláním funkce lua.NewState() poskytuje skriptům vzájemnou izolaci. To například znamená, že jednotlivé skripty nebudou mít přístup ke svým globálním proměnným (a to proměnným jakéhokoli typu, tedy i k funkcím). Toto chování si pochopitelně můžeme velmi snadno otestovat, a to tak, že vytvoříme dva virtuální stroje a spustíme v nich různé skripty. Nejdříve se podívejme na úplný zdrojový kód demonstračního příkladu

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource1 = "v1.lua"
const LuaSource2 = "v2.lua"
const LuaSource3 = "v3.lua"
 
func main() {
        luaVM1 := lua.NewState()
        log.Println("Lua VM1 has been initialized")
 
        luaVM2 := lua.NewState()
        log.Println("Lua VM2 has been initialized")
 
        defer func() {
                luaVM1.Close()
                log.Println("Lua VM1 has been closed")
        }()
 
        defer func() {
                luaVM2.Close()
                log.Println("Lua VM2 has been closed")
        }()
 
        err := luaVM1.DoFile(LuaSource1)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM2.DoFile(LuaSource2)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM1.DoFile(LuaSource3)
        if err != nil {
                log.Fatal(err)
        }
}

Vidíme, že z prvního virtuálního stroje se volá skript v1.lua a v3.lua, zatímco ze druhého stroje pouze skript v2.lua. Interně jsou tyto skripty prakticky totožné:

print("v1.lua, x=", x)
x = 10
print("v1.lua, x=", x)
print("v2.lua, x=", x)
x = 10
print("v2.lua, x=", x)
print("v3.lua, x=", x)
x = 10
print("v3.lua, x=", x)

Vidíme, že všechny tři skripty se nejdříve pokusí vypsat hodnotu globální proměnné x, následně tuto hodnotu změní a znovu ji vypíšou. Pokud budou skripty zcela izolované, měla by se vždy vypsat speciální hodnota nil a teprve poté hodnota 10. Pokud jsou naopak všechny skripty sdílené v jediné VM, vypíše se hodnota nil pouze jedenkrát. A konečně v případě, že jsou skripty spouštěné v různých VM navzájem izolované, měla by se hodnota nil vypsat dvakrát. Podívejme se tedy na skutečný výsledek běhu tohoto příkladu:

2019/05/26 20:43:31 Lua VM1 has been initialized
2019/05/26 20:43:31 Lua VM2 has been initialized
v1.lua, x=      nil
v1.lua, x=      10
v2.lua, x=      nil
v2.lua, x=      10
v3.lua, x=      10
v3.lua, x=      10
2019/05/26 20:43:31 Lua VM2 has been closed
2019/05/26 20:43:31 Lua VM1 has been closed

Z výsledků je patrné, že skripty v1.lua a v3.lua sdílí prostor se svými globálními proměnnými. To je v pořádku, neboť běží v rámci jedné VM.

8. Gorutiny a virtuální stroje jazyka Lua

Interní stav VM implementovaný v projektu Gopher-Lua není zcela zabezpečen proti problémům, které vzniknou, pokud by se spustilo více skriptů v několika gorutinách v jediném VM. Nic nám však pochopitelně nebrání vytvořit si několik gorutin, v každé gorutině inicializovat vlastní VM a v něm potom spustit Lua skripty s tím, že případná komunikace bude provedena s využitím kanálů. V dalším demonstračním příkladu je ukázáno, jakým způsobem se paralelně spustí dva Lua skripty, každý ve své vlastní VM:

for i = 1,10 do
    print("Hello from VM1", i)
end
for i = 1,10 do
    print("Hello from VM2", i)
end

Samotné spuštění gorutin se nijak nevymyká informacím, které známe z předchozích částí tohoto seriálu. Jen pochopitelně nesmíme zapomenout počkat na dokončení všech vytvořených gorutin, a to s využitím kanálů – každá gorutina na konci zapíše do příslušného kanálu jednu hodnotu, na kterou čekáme v hlavní gorutině:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource1 = "l1.lua"
const LuaSource2 = "l2.lua"
 
func callLuaVM1(c chan bool) {
        defer func() { c <- true }()
 
        luaVM1 := lua.NewState()
        log.Println("Lua VM1 has been initialized")
 
        defer func() {
                luaVM1.Close()
                log.Println("Lua VM1 has been closed")
        }()
 
        err := luaVM1.DoFile(LuaSource1)
        if err != nil {
                log.Fatal(err)
        }
}
 
func callLuaVM2(c chan bool) {
        defer func() { c <- true }()
 
        luaVM2 := lua.NewState()
        log.Println("Lua VM2 has been initialized")
 
        defer func() {
                luaVM2.Close()
                log.Println("Lua VM2 has been closed")
        }()
 
        err := luaVM2.DoFile(LuaSource2)
        if err != nil {
                log.Fatal(err)
        }
}
 
func main() {
        c1 := make(chan bool)
        c2 := make(chan bool)
 
        go callLuaVM1(c1)
        go callLuaVM2(c2)
 
        <-c1
        <-c2
}

Výpis ze skriptů se kvůli nezavolání funkce flush() po každém volání print() navzájem pomíchá:

2019/05/26 20:53:14 Lua VM1 has been initialized
2019/05/26 20:53:14 Lua VM2 has been initialized
Hello from VM2  Hello from VM1  1
Hello from VM1  2
Hello from VM1  3
1
Hello from VM1  4
Hello from VM1  5
Hello from VM1  6
Hello from VM1Hello from VM2            7
Hello from VM1  8
Hello from VM1  9
Hello from VM1  10
2019/05/26 20:53:14 Lua VM1 has been closed
2
Hello from VM2  3
Hello from VM2  4
Hello from VM2  5
Hello from VM2  6
Hello from VM2  7
Hello from VM2  8
Hello from VM2  9
Hello from VM2  10
2019/05/26 20:53:14 Lua VM2 has been closed

9. Volání funkce vytvořené v jazyku Lua z Go

Tím hlavním důvodem, proč se do aplikací naprogramovaných v jazyku Go vkládá interpret skriptovacího jazyka Lua, je možnost kooperace mezi oběma částmi aplikace – tj. mezi nativní částí vytvořenou přímo v Go a skripty napsanými v jazyce Lua. Kooperace přitom může být obousměrná:

  1. Z aplikace napsané v Go je umožněno volat funkce naprogramované (naskriptované) v jazyku Lua. To se samozřejmě týká i možnosti předávání parametrů a zpracování návratové hodnoty či návratových hodnot.
  2. A naopak, ze skriptu napsaného v jazyce Lua lze volat zvolené (registrované) funkce naprogramované v Go.

Nejdříve si ukážeme tu nejjednodušší variantu, tj. zavolání funkce naskriptované v jazyce Lua. Celý Lua skript, v němž je funkce definována, vypadá takto:

function hello()
    print("Hello from Lua")
end

Samotné zavolání funkce hello je zpočátku komplikované, protože je nutné nastavit jak jméno funkce, tak i počet a typ předávaných parametrů i typ a počet návratových hodnot. V našem případě je funkce volána bez parametrů a nemá žádné návratové hodnoty, takže ji můžeme zavolat takto:

err = luaVM.CallByParam(lua.P{
        Fn:   luaVM.GetGlobal("hello"),
        NRet: 0,
})

Úplný zdrojový kód příkladu, který po svém spuštění zavolá Lua funkci, naleznete na adrese https://github.com/tisnik/go-root/blob/master/article27/06_ca­ll_lua.go:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "function.lua"
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM.CallByParam(lua.P{
                Fn:   luaVM.GetGlobal("hello"),
                NRet: 0,
        })
 
        if err != nil {
                log.Fatal(err)
        }
 
}

Spuštění příkladu:

2019/05/26 20:37:26 Lua VM has been initialized
Hello from Lua
2019/05/26 20:37:26 Lua VM has been closed

10. Předání parametrů do funkce naprogramované v jazyku Lua

Nyní se podívejme, jak se vlastně předávají parametry z aplikace psané v Go do funkce/funkcí naprogramovaných ve skriptovacím jazyku Lua. Samotný skript naprogramovaný v Lue používá dynamicky typované proměnné:

function hello(a, b)
    print("Hello from Lua")
    print("1st parameter", a)
    print("2nd parameter", b)
end

V aplikaci psané v Go je nutné zajistit převod z datového typu Go na datový typ, který je zpracovatelný virtuálním strojem jazyka Lua. Pokud se bude jednat o numerické hodnoty, použijeme toto volání:

err = luaVM.CallByParam(lua.P{
        Fn:   luaVM.GetGlobal("hello"),
        NRet: 0,
}, lua.LNumber(1), lua.LNumber(2))

Povšimněte si, že jsme dvě celočíselné hodnoty typu int museli převést na objekty typu LNumber stejně pojmenovanou funkcí.

Podívejme se nyní na úplný kód příkladu, který zavolá výše vypsanou funkci hello() a předá ji dvě numerické hodnoty 1 a 2:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "function_params.lua"
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM.CallByParam(lua.P{
                Fn:   luaVM.GetGlobal("hello"),
                NRet: 0,
        }, lua.LNumber(1), lua.LNumber(2))
 
        if err != nil {
                log.Fatal(err)
        }
 
}

Výsledek po spuštění:

2019/05/26 21:09:08 Lua VM has been initialized
Hello from Lua
1st parameter   1
2nd parameter   2
2019/05/26 21:09:08 Lua VM has been closed

Naprosto stejné funkci hello() je ovšem možné předat například i dva řetězce:

err = luaVM.CallByParam(lua.P{
        Fn:   luaVM.GetGlobal("hello"),
        NRet: 0,
}, lua.LString("foo"), lua.LString("bar"))

Příklad se jen nepatrně upraví:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "function_params.lua"
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM.CallByParam(lua.P{
                Fn:   luaVM.GetGlobal("hello"),
                NRet: 0,
        }, lua.LString("foo"), lua.LString("bar"))
 
        if err != nil {
                log.Fatal(err)
        }
 
}

Výsledek po spuštění:

2019/05/26 21:09:25 Lua VM has been initialized
Hello from Lua
1st parameter   foo
2nd parameter   bar
2019/05/26 21:09:25 Lua VM has been closed

11. Zpracování návratové hodnoty

Podívejme se nyní na další velmi jednoduchou funkci naprogramovanou ve skriptovacím jazyce Lua. Tato funkce akceptuje dva parametry, vypíše je na svůj standardní výstup a nakonec vrátí jejich součet:

function add(a, b)
    print("1st parameter", a)
    print("2nd parameter", b)
    return a+b
end

Jakým způsobem je ovšem možné přečíst návratovou hodnotu této funkce a nějak ji zpracovat? Pro předávání návratových hodnot (může jich být totiž více – v tomto ohledu není Go prvním jazykem s podobným přístupem) se používá zásobník, z něhož je možné hodnoty načítat metodou Get() a odstraňovat metodou Pop(). Výsledkem je speciální hodnota typu LValue s metodami String() a Type():

ret := luaVM.Get(-1)
luaVM.Pop(1)
println("Type", ret.Type())
println("Value", ret.String())

Při volání funkce nesmíme zapomenout zadat počet návratových hodnot:

err = luaVM.CallByParam(lua.P{
        Fn:      luaVM.GetGlobal("add"),
        NRet:    1,
        Protect: true,
}, lua.LNumber(1), lua.LNumber(2))

Podívejme se nyní na příklad, který zavolá Lua funkci určenou pro součet dvou numerických hodnot:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "add.lua"
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM.CallByParam(lua.P{
                Fn:      luaVM.GetGlobal("add"),
                NRet:    1,
                Protect: true,
        }, lua.LNumber(1), lua.LNumber(2))
 
        if err != nil {
                log.Fatal(err)
        }
 
        ret := luaVM.Get(-1)
        luaVM.Pop(1)
        println("Type", ret.Type())
        println("Value", ret.String())
}

S výsledky:

2019/05/26 21:22:25 Lua VM has been initialized
1st parameter   1
2nd parameter   2
Type 2
Value 3
2019/05/26 21:22:25 Lua VM has been closed

Podobně můžeme zavolat Lua funkci, která spojí dva řetězce:

function concatenate(a, b)
    print("1st parameter", a)
    print("2nd parameter", b)
    return a..b
end

Příklad se upraví jen nepatrně:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "concatenate.lua"
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM.CallByParam(lua.P{
                Fn:      luaVM.GetGlobal("concatenate"),
                NRet:    1,
                Protect: true,
        }, lua.LString("foo"), lua.LString("bar"))
 
        if err != nil {
                log.Fatal(err)
        }
 
        ret := luaVM.Get(-1)
        luaVM.Pop(1)
        println("Type", ret.Type())
        println("Value", ret.String())
}

A výsledky budou pochopitelně odlišné:

2019/05/26 21:23:09 Lua VM has been initialized
1st parameter   foo
2nd parameter   bar
Type 3
Value foobar
2019/05/26 21:23:09 Lua VM has been closed

12. Konverze návratových hodnot na typy kompatibilní s jazykem Go

Typový systém programovacího jazyka Go se v několika ohledech odlišuje od typového systému skriptovacího jazyka Lua. Proto asi nebude velkým překvapením, že datové typy a hodnoty se musí explicitně konvertovat. Při posílání parametrů funkcím do jazyka Lua se používají konverzní funkce zmíněné v předchozích kapitolách. Ovšem například při čtení návratové hodnoty (návratových hodnot) z Lua funkce se musí konverze provést složitějším způsobem, a to pochopitelně z toho důvodu, že Go je staticky a silně typovaný jazyk, tj. v tomto ohledu stojí na opačné straně než dynamicky typovaný jazyk Lua. Podívejme se nyní, jak je možné konverzi provést, zde konkrétně pro funkci vracející číslo (v Lua typu double, v Go pochopitelně float64):

if number, ok := ret.(lua.LNumber); ok {
        println("Value", float64(number))
}

Konverzi si vyzkoušíme na funkci, která sečte své parametry a vrátí výsledek tohoto součtu:

function add(a, b)
    print("1st parameter", a)
    print("2nd parameter", b)
    return a+b
end

Zavolání této Lua funkce se zpracováním výsledků může vypadat následovně:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "add.lua"
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM.CallByParam(lua.P{
                Fn:      luaVM.GetGlobal("add"),
                NRet:    1,
                Protect: true,
        }, lua.LNumber(1), lua.LNumber(2))
 
        if err != nil {
                log.Fatal(err)
        }
 
        ret := luaVM.Get(-1)
        luaVM.Pop(1)
        println("Type", ret.Type())
        if number, ok := ret.(lua.LNumber); ok {
                println("Value", float64(number))
        }
}

Výsledek po spuštění:

2019/05/27 19:50:30 Lua VM has been initialized
1st parameter   1
2nd parameter   2
Type 2
Value +3.000000e+000
2019/05/27 19:50:30 Lua VM has been closed

13. Datový typ boolean v programovacím jazyce Lua

Pro Lua funkce, které vrací pravdivostní hodnoty true a false se návratové hodnoty konvertují tímto postupem:

println("Result", ret == lua.LTrue)
println("Result", ret == lua.LFalse)
Poznámka: existují sice i jiné možnosti, ovšem postup ukázaný v předchozím odstavci je idiomatický a měl by se takto používat. Předností tohoto postupu je fakt, že se současně provádí konverze i test, zda je možné konverzi provést (protože obecně nemůžeme vědět, jaký typ se vlastně v dynamicky typovaném jazyce v daný okamžik vrátí).

Ukažme si výše popsaný postup na praktickém příkladu, konkrétně na následující Lua funkci sloužící pro porovnání hodnot, kterou budeme volat s různými parametry:

function compare(a, b)
    print("1st parameter", a)
    print("2nd parameter", b)
    return a==b
end

V programovacím jazyku Go by se volání této funkce a zpracování její návratové hodnoty mohlo realizovat například následujícím způsobem:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "compare.lua"
 
func compareNumbers(a int, b int) {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM.CallByParam(lua.P{
                Fn:      luaVM.GetGlobal("compare"),
                NRet:    1,
                Protect: true,
        }, lua.LNumber(a), lua.LNumber(b))
 
        if err != nil {
                log.Fatal(err)
        }
 
        ret := luaVM.Get(-1)
        luaVM.Pop(1)
        println("Type", ret.Type())
        println("Value", ret.String())
        println("Result", ret == lua.LTrue)
}
 
func main() {
        compareNumbers(1, 2)
        compareNumbers(1, 1)
}

Výsledky, které získáme po spuštění tohoto demonstračního příkladu:

2019/05/27 19:52:08 Lua VM has been initialized
1st parameter   1
2nd parameter   2
Type 1
Value false
Result false
2019/05/27 19:52:08 Lua VM has been closed
2019/05/27 19:52:08 Lua VM has been initialized
1st parameter   1
2nd parameter   1
Type 1
Value true
Result true
2019/05/27 19:52:08 Lua VM has been closed

14. Datový typ nil v programovacím jazyce Lua

Podobně je nutné správně pracovat s hodnotou a současně i datovým typem nil. Konverze a test, zda Lua funkce vrátila tuto hodnotu, může vypadat takto:

println("is nil?", ret == lua.LNil)

Opět si chování otestujme, tentokrát na velmi primitivní Lua funkci, která za všech okolností vrátí hodnotu nil:

function return_nil()
    return nil
end

Samozřejmě si opět ukážeme úplný zdrojový kód příkladu, který tuto funkci zavolá a zobrazí její návratovou hodnotu:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "return_nil.lua"
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM.CallByParam(lua.P{
                Fn:      luaVM.GetGlobal("return_nil"),
                NRet:    1,
                Protect: true,
        })
 
        if err != nil {
                log.Fatal(err)
        }
 
        ret := luaVM.Get(-1)
        luaVM.Pop(1)
        println("Type", ret.Type())
        println("Value", ret.String())
        println("is nil?", ret == lua.LNil)
}

Výsledek:

2019/05/27 19:53:50 Lua VM has been initialized
Type 0
Value nil
is nil? true
2019/05/27 19:53:50 Lua VM has been closed

15. Zpracování většího množství návratových hodnot ze skriptu naprogramovaného v jazyce Lua

V programovacích jazycích Go a Lua je možné vracet větší množství návratových hodnot. Ukažme si nyní, jak se tyto hodnoty zpracují ve chvíli, kdy budeme mít Lua funkci vracející své parametry, ovšem v opačném pořadí:

function swap(a, b)
    print("1st parameter", a)
    print("2nd parameter", b)
    return b, a
end

Návratové hodnoty můžeme získat ze zásobníku, pochopitelně v opačném pořadí (jde o zásobník). Taktéž nesmíme zapomenout na odstranění těchto hodnot ze zásobníku, aby došlo k jeho vyvážení:

ret1 := luaVM.Get(-2)
ret2 := luaVM.Get(-1)
luaVM.Pop(2)

Následuje výpis zdrojového kódu demonstračního příkladu, který výše uvedenou funkci swap() zavolá a zpracuje její výsledky:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "swap.lua"
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM.CallByParam(lua.P{
                Fn:      luaVM.GetGlobal("swap"),
                NRet:    2,
                Protect: true,
        }, lua.LNumber(1), lua.LNumber(2))
 
        if err != nil {
                log.Fatal(err)
        }
 
        ret1 := luaVM.Get(-2)
        ret2 := luaVM.Get(-1)
        luaVM.Pop(2)
        println("Type", ret1.Type())
        println("Type", ret2.Type())
        if number, ok := ret1.(lua.LNumber); ok {
                println("Value #1", float64(number))
        }
        if number, ok := ret2.(lua.LNumber); ok {
                println("Value #2", float64(number))
        }
}

16. Volání funkcí naprogramovaných v Go z Lua skriptu

V předchozím textu jsme se dozvěděli, jakým způsobem je možné z programovacího jazyka Go zavolat libovolnou funkci naprogramovanou ve skriptovacím jazyce Lua, jak se volané funkci předávají hodnoty a pochopitelně i to, jak se zpracovává případná návratová hodnota (hodnoty). Ovšem pro úplnou integraci Go+Lua je nutné zajistit i opačný způsob volání, tj. volání Go funkce z Lua skriptu. Můžeme si nejprve vyzkoušet, co se stane, pokud se pokusíme takovou funkci přímo zavolat bez toho, abychom ji jakýmkoli způsobem registrovali.

Samotný Lua skript, který zavolá (resp. pokusí se zavolat) funkci naprogramovanou v jazyce Go, vypadá takto:

function call_go()
    print("Hello from Lua!")
    hello()
end

Funkci vytvořenou v jazyku Lua se můžeme pokusit zavolat v této aplikaci:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "go_from_lua.lua"
 
func hello(L *lua.LState) int {
        log.Println("Hello from Go!")
        return 0
}
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        err = luaVM.CallByParam(lua.P{
                Fn:      luaVM.GetGlobal("call_go"),
                NRet:    0,
                Protect: true,
        })
 
        if err != nil {
                log.Fatal(err)
        }
}

Při pokusu o spuštění tohoto příkladu se zobrazí následující chybové hlášení:

2019/05/27 19:59:41 Lua VM has been initialized
Hello from Lua!
2019/05/27 19:59:41 go_from_lua.lua:3: attempt to call a non-function object
stack traceback:
        go_from_lua.lua:3: in main chunk
        [G]: ?
exit status 1

Je tomu tak z toho prostého důvodu, že virtuální stroj skriptovacího jazyka Lua nemá žádnou informaci o tom, jak funkci zavolat – vůbec ji ve svém kontextu nezná.

17. Registrace funkce, která se má z Lua skriptů zavolat

Aby bylo možné Go funkci zavolat ze skriptu vytvořeného v jazyce Lua, je nutné takovou funkci nejprve korektně napsat. Tato funkce musí mít hlavičku:

func foobar(L *lua.LState) int {
}

Dále je nutné tuto funkci zaregistrovat, a to pod jménem, jakým bude volána z Lua skriptu:

luaVM.SetGlobal("hello", luaVM.NewFunction(Hello))
Poznámka: povšimněte si, že se funkce v Go může jmenovat odlišně, než je tomu v jazyce Lua. To je oboustranně výhodné řešení, protože není nutné dělat kompromisy při pojmenovávání funkcí (v tomto ohledu je Go striktnější) a taktéž se nemusíme obávat, že dojde k přemazání nějaké jiné funkce v kontextu jazyka Lua z důvodu použití stejného jména (vše má pod kontrolou vývojář, který si může ohlídat jak zdrojové kódy Go, tak i skripty napsané v jazyku Lua).

Následně je již možné realizovat následující sekvenci volání:

  1. Funkce main naprogramovaná v Go volá funkci s Lua skriptu.
  2. Z Lua skriptu se zavolá funkce naprogramovaná v jazyku Go.

Vše je ukázáno v dalším (již předposledním) demonstračním příkladu:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "go_from_lua.lua"
 
func Hello(L *lua.LState) int {
        log.Println("Hello from Go!")
        return 0
}
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        luaVM.SetGlobal("hello", luaVM.NewFunction(Hello))
 
        err = luaVM.CallByParam(lua.P{
                Fn:      luaVM.GetGlobal("call_go"),
                NRet:    0,
                Protect: true,
        })
 
        if err != nil {
                log.Fatal(err)
        }
}

S výsledky:

2019/05/27 20:02:51 Lua VM has been initialized
Hello from Lua!
2019/05/27 20:02:51 Hello from Go!
2019/05/27 20:02:51 Lua VM has been closed

18. Zpracování parametrů a návratových hodnot

V dnešním posledním demonstračním příkladu si ukážeme, jak se ve funkci vytvořené v jazyce Go zpracují parametry předané z Lua skriptu. Připomeňme si, že funkce v Go volaná z Lua musí mít tuto hlavičku:

func foobar(L *lua.LState) int {
}

Všechny parametry předané z Lua skriptu jsou přenášeny přes zásobník, z něhož je musíme explicitně načíst, a to například následujícím způsobem:

a := L.ToInt(1)
b := L.ToInt(2)

Výsledek se ukládá zpět na zásobník metodou Push():

L.Push(lua.LNumber(a + b))

Následuje výpis úplného zdrojového kódu příkladu, který toto dvojí volání realizuje:

package main
 
import (
        "github.com/yuin/gopher-lua"
        "log"
)
 
const LuaSource = "go_from_lua_add.lua"
 
func compute(L *lua.LState) int {
        log.Println("called from Lua")
        a := L.ToInt(1)
        b := L.ToInt(2)
        log.Printf("1st parameter %d\n", a)
        log.Printf("2nd parameter %d\n", b)
        L.Push(lua.LNumber(a + b))
        return 1
}
 
func main() {
        luaVM := lua.NewState()
        log.Println("Lua VM has been initialized")
 
        defer func() {
                luaVM.Close()
                log.Println("Lua VM has been closed")
        }()
 
        err := luaVM.DoFile(LuaSource)
        if err != nil {
                log.Fatal(err)
        }
 
        luaVM.SetGlobal("compute", luaVM.NewFunction(compute))
 
        err = luaVM.CallByParam(lua.P{
                Fn:      luaVM.GetGlobal("add"),
                NRet:    1,
                Protect: true,
        }, lua.LNumber(1), lua.LNumber(2))
 
        if err != nil {
                log.Fatal(err)
        }
 
        ret := luaVM.Get(-1)
        luaVM.Pop(1)
        println("Type", ret.Type())
        println("Value", ret.String())
}

Po spuštění tohoto příkladu by se měly vypsat následující informace o zavolání Lua funkce (a o jejích parametrech) i o zavolání funkce compute naprogramované v jazyku Go:

2019/05/27 19:55:13 Lua VM has been initialized
1st parameter   1
2nd parameter   2
2019/05/27 19:55:13 called from Lua
2019/05/27 19:55:13 1st parameter 1
2019/05/27 19:55:13 2nd parameter 2
Type 2
Value 3
2019/05/27 19:55:13 Lua VM has been closed

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_do_string1.go spuštění Lua skriptu uloženého v řetězci aplikace naprogramované v Go https://github.com/tisnik/go-root/blob/master/article27/01_do_strin­g1.go
2 02_do_string2.go vylepšení předchozího příkladu https://github.com/tisnik/go-root/blob/master/article27/02_do_strin­g2.go
3 02B_do_string3.go víceřádkový skript naprogramovaný v jazyce Lua https://github.com/tisnik/go-root/blob/master/article27/02B_do_strin­g3.go
4 03_do_file.go spuštění Lua skriptu načteného z externího zdrojového souboru https://github.com/tisnik/go-root/blob/master/article27/03_do_fi­le.go
5 04_two_vms.go použití dvou na sobě nezávislých virtuálních strojů jazyka Lua https://github.com/tisnik/go-root/blob/master/article27/04_two_vms­.go
6 05_two_vms_in_goroutines.go dva virtuální stroje jazyka Lua, každý spuštěný v samostatné gorutině https://github.com/tisnik/go-root/blob/master/article27/05_two_vms_in_go­routines.go
7 06_call_lua.go zavolání funkce jazyka Lua z aplikace psané v Go https://github.com/tisnik/go-root/blob/master/article27/06_ca­ll_lua.go
8 07_call_lua_parameters.go zavolání funkce jazyka Lua z aplikace psané v Go s předáním parametrů https://github.com/tisnik/go-root/blob/master/article27/07_ca­ll_lua_parameters.go
9 08_call_lua_parameters.go zavolání funkce jazyka Lua z aplikace psané v Go s předáním parametrů https://github.com/tisnik/go-root/blob/master/article27/08_ca­ll_lua_parameters.go
10 09_call_lua_return_value.go zpracování návratové hodnoty funkce naprogramované v Lua https://github.com/tisnik/go-root/blob/master/article27/09_ca­ll_lua_return_value.go
11 10_different_data_types.go zpracování odlišných typů hodnot, zde konkrétně řetězců https://github.com/tisnik/go-root/blob/master/article27/10_dif­ferent_data_types.go
12 11_conversion.go konverze typů ve směru Lua→Go https://github.com/tisnik/go-root/blob/master/article27/11_con­version.go
13 12_boolean.go zpracování hodnot typu boolean https://github.com/tisnik/go-root/blob/master/article27/12_bo­olean.go
14 13_nil.go zpracování hodnoty nil https://github.com/tisnik/go-root/blob/master/article27/13_nil.go
15 14_two_return_values.go funkce vracející dvě hodnoty https://github.com/tisnik/go-root/blob/master/article27/14_two_re­turn_values.go
16 15_go_from_lua.go nekorektní volání Go funkce z Lua skriptu https://github.com/tisnik/go-root/blob/master/article27/15_go_from_lu­a.go
17 16_go_from_lua_correct.go korektní volání Go funkce z Lua skriptu https://github.com/tisnik/go-root/blob/master/article27/16_go_from_lu­a_correct.go
18 17_go_from_lua_calc.go volání Go funkce z Lua skriptu s předáváním parametrů a návratové hodnoty https://github.com/tisnik/go-root/blob/master/article27/17_go_from_lu­a_calc.go

Skripty naprogramované v jazyku Lua, které jsou demonstračními příklady používány:

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