Hlavní navigace

Go: minimalistický a překvapivě výkonný programovací jazyk

20. 11. 2018
Doba čtení: 30 minut

Sdílet

 Autor: Go Lang
V novém seriálu se seznámíme s jazykem Go. Umožňuje překlad do nativního kódu, takže výsledkem by měly být rychlé aplikace. Současně se ovšem Go v některých ohledech od nízkoúrovňových jazyků liší.

Obsah

1. Go – minimalistický a překvapivě výkonný programovací jazyk

2. Go není „pouze“ vylepšený programovací jazyk C

3. Popularita programovacího jazyka Go mezi vývojáři

4. Instalace základních nástrojů pro jazyk Go do Fedory

5. Nastavení proměnné prostředí GOPATH

6. Základní kontrola instalace

7. První demonstrační příklad – klasický „Hello world!“ ve dvou variantách

8. Jak pracovat s dokumentací

9. Doporučovaný styl zápisu programů

10. Nástroj gofmt určený pro přeformátování zdrojových kódů

11. Kontrola použití importovaných modulů

12. Plná podpora Unicode v programech

13. Základy typového systému – odvození typu proměnné z hodnoty

14. Kontrola typu hodnoty přiřazované do proměnné

15. Deklarace funkcí

16. Specifikace typu argumentů funkcí

17. Příkaz return

18. Vrácení většího množství hodnot z funkce

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

20. Odkazy na Internetu

1. Go – minimalistický a překvapivě výkonný programovací jazyk

Jak jsme se již zmínili v perexu, začíná dnes na Rootu vycházet nový seriál, v němž se postupně seznámíme se zajímavým a relativně novým programovacím jazykem nazvaným Go. Jedná se o programovací jazyk umožňující překlad zdrojových kódů do nativního (strojového) kódu, takže výsledkem by měly být rychlé a paměťově efektivní aplikace (alespoň teoreticky) srovnatelné s výsledky, které jsou produkované překladači jazyků C, C++, D či Rust (popř. Fortran, FreePascal apod.). Současně ovšem Go používá automatickou správu paměti (GC – garbage collector), takzvané gorutiny a kanály a současně i poměrně minimalistickou syntaxi, čímž se od dvojice C a C++ dosti podstatným způsobem odlišuje (Rust, s nímž jsme se na Rootu již seznámili, je ovšem koncipován odlišným způsobem, i když je s jazykem Go poměrně často srovnáván, například v tomto pěkném článečku).

Tento programovací jazyk krátce po svém oficiálním uvedení v roce 2009 vzbudil poměrně velký ohlas, neboť se jedná o staticky typovaný jazyk kompilovaný do nativního kódu – mohlo by se tedy zdát, že je určen pro oblast doposud ovládanou programovacími jazyky C a C++. Jak si však řekneme v dalším textu, byla praxe poněkud složitější a Go začal být – možná poněkud překvapivě i pro jeho samotné tvůrce – používán těmi vývojáři, kteří tvořili aplikace založené na technologiích typu Node.js, Pythonu či Ruby (viz též třetí kapitolu). Využíván je též pro psaní síťových utilit atd. Mezi větší a známější aplikace naprogramované v Go patří především OpenShift, Kubernetes a zapomenout nesmíme ani na Docker.

Poznámka: na tomto místě je nutné upozornit na to, že Go vzbudil ohlas nejenom kvůli svým technickým vlastnostem, ale i díky tomu, že pochází z „dílny“ Google a taktéž proto, že za jeho designem stojí mj. i dvě slavné osobnosti z oblasti IT: Rob „Commander“ Pike a Ken Thompson. Poněkud zvláštní je fakt, že jazyk vytvořený v Googlu má jméno, které není unikátní a tak se špatně hledá – většinou je nutné kombinovat „go“ a „golang“.

2. Go není „pouze“ vylepšený programovací jazyk C

Víme již, že mezi autory návrhu i první implementace programovacího jazyka Go patří mj. i Ken Thompson. Když si uvědomíme, že Ken vytvořil i (dnes prehistorický) programovací jazyk B, který je předchůdcem slavného jazyka C, mohlo by se zdát, že Go bude pouhým vylepšením programovacího jazyka C. Je sice pravda, že Go alespoň částečně z jazyka C vychází, ovšem z hlediska syntaxe jsou si mnohem bližší například jazyky C a Java než C a Go. Tvůrci programovacího jazyka Go se navíc snažili poučit se z některých problematických rysů jazyků C a taktéž C++ a navrhli Go takovým způsobem, aby byla jeho syntaxe jednodušší a snáze pochopitelná (sami se po prohlédnutí zdrojových kódů můžete rozhodnout, do jaké míry se to splnilo). Samotný jazyk je velmi konzervativní a někteří kritici dokonce (s nadsázkou) tvrdí, že zcela ignoruje všechny novinky, které se na poli programovacích jazyků za posledních dvacet let objevily.

Navíc se tvůrci jazyka Go zaměřili i na další typické problémy, s nimiž se musí vypořádat prakticky všichni programátoři používající jazyky C, C++ a vlastně i uživatelé Rustu (ti nepřímo): jedná se o problematiku správy paměti a uvolňování zdrojů (resources) a v neposlední řadě i o podporu pro paralelní běh částí programů, například částí komunikujících přes síť, pracujících se souborovým systémem atd. Z tohoto důvodu byly přímo do jazyka Go přidány dvě technologie: automatický správce paměti a takzvané gorutiny (goroutines). S oběma těmito technologiemi se seznámíme v navazujících částech tohoto seriálu. Některé technologie naopak do Go přidány nebyly (slova autorů o filozofii jazyka), a to z toho důvodu, aby byl jazyk snadno použitelný a snadno implementovatelný. Právě fakt, že se v Go zpočátku neobjevily dnes již široce akceptované technologie, jako je obsluha výjimek či práce s generickými datovými typy, byl kritizován; ovšem Go se postupně mění a přidávají se do něj další vlastnosti (ovšem poměrně konzervativním způsobem, což je podle mého názoru v této oblasti jen dobře). Viz též https://golang.org/doc/faq#Why_do­esnt_Go_have_feature_X.

Poznámka: na tomto místě je vhodné si uvědomit, že použití automatického správce paměti přenáší část problémů (které by jinak musel řešit programátor) do runtime, což se může negativně projevit na výkonu aplikace (zpomalení aplikace, větší nároky na kapacitu haldy, musí se počítat i s okamžiky, kdy je aplikace pozastavena apod.). Vlivem správce paměti na výkon se budeme zabývat v samostatném článku, takže zde jen bez dalších důkazů uveďme, že automatický správce paměti implementovaný v jazyku Go nemá větší negativní vliv na dobu startu aplikací (ostatně, nemusí se například spouštět ani virtuální stroj ani inicializovat interpret), spotřeba paměti je však průměrně větší, než v případě C/C++, ale na druhou stranu menší, než je tomu u Javy [1] [2].

3. Popularita programovacího jazyka Go mezi vývojáři

Programovací jazyk Go a jeho základní knihovny byly původně navrženy takovým způsobem, aby se v tomto jazyku daly efektivně psát různé síťové aplikace, které by navíc netrpěly na potenciální problémy typu „buffer overflow“ atd. Právě z toho důvodu, že síťové a serverové aplikace musí v typické konfiguraci obsluhovat velké množství paralelně běžících požadavků, byly do jazyka Go přidány již výše zmíněné gorutiny. A nutno říci, že Go se v této oblasti skutečně používá. Taktéž se předpokládalo, že se Go bude používat pro vývoj těch aplikací, které jsou nyní psány v C či C++. Tento přechod sice skutečně nastal, ovšem prozatím ne v příliš velké míře (a to se bavíme o použití v serverových a desktopových aplikacích, protože v oblasti mikrořadičů se s Go sice laboruje, ale především u relativně výkonných MCU typicky založených na čipech Cortex-M, nikoli na malých osmibitových a šestnáctibitových mikrořadičích).

Nastala ovšem zajímavější situace, o níž jsme se krátce zmínili v úvodu – programovací jazyk Go a jeho možnosti objevili programátoři, kteří psali své síťové a speciálně pak webové aplikace v Pythonu, Ruby či Java/TypeScriptu. Přepis do jazyka Go mnohdy znamenal řádový a někdy i dvouřádový (100×) nárůst výkonu těchto aplikací. Do jisté míry je nárůst výkonu způsobem překladem do nativního kódu, ovšem nesmíme zapomenout na gorutiny, které nejsou (na rozdíl od klasických vláken) příliš náročné na paměť, takže se můžeme setkat s aplikacemi, v nichž bez větších problémů běží stovky či dokonce tisíce gorutin.

Poznámka: pokud si chcete vyzkoušet provozovat Go na mikrořadičích Cortex-M, můžete využít projekt Emgo
Další MCU ovšem nejsou prozatím podporovány. Dalším zajímavým projektem je Go on Mobile.

4. Instalace základních nástrojů pro jazyk Go do Fedory

Popišme si nyní instalaci základních nástrojů programovacího jazyka Go do distribuce Fedora. Instalace bude na všech třech posledních vydáních Fedory prakticky totožná, takže se zaměřme na Fedoru 29, pro niž byl vytvořen balíček s Go verze 1.11 (což je v současnosti nejnovější stabilní verze tohoto jazyka). Instalaci provedeme buď přímo z terminálu s přihlášeným superuživatelem (rootem):

# dnf install golang

Alternativně samozřejmě můžeme použít instalaci s využitím nástroje sudo, pokud má ovšem přihlášený uživatel příslušná práva:

$ sudo dnf install golang

V obou případech by měla instalace proběhnout prakticky totožným způsobem:

Last metadata expiration check: 3:00:43 ago on Thu Nov 15 01:24:13 2018.
Dependencies resolved.
================================================================================
 Package                 Arch   Version          Repository                Size
================================================================================
Installing:
 golang                  x86_64 1.11.2-1.fc29    updates                  625 k
Installing dependencies:
 golang-bin              x86_64 1.11.2-1.fc29    updates                   90 M
 golang-src              noarch 1.11.2-1.fc29    updates                  6.4 M
...
...
...
Transaction Summary
================================================================================
Install  35 Packages
 
Total download size: 163 M
Installed size: 541 M
Is this ok [y/N]:

Povšimněte si, že se po nainstalování rozbalí přibližně 514 MB dat, což by pro základní sadu nástrojů byla poměrně vysoká hodnota, ovšem Fedora 29, na níž si práci s jazykem Go ukazujeme, byla nainstalována s minimální sadou balíčků a tudíž neobsahovala ani základní vývojové nástroje (Go například vyžaduje Git atd.). Samotné Go se všemi svými nástroji vyžaduje necelých 200 MB diskového prostoru, přičemž tarball má zhruba poloviční velikost.

Po odpovědi „Y“ se instalace rozběhne a pochopitelně se nijak zásadně neliší od instalace jakéhokoli jiného balíčku:

Downloading Packages:
(1/35): apr-util-bdb-1.6.1-8.fc29.x86_64.rpm                        75 kB/s |  12 kB     00:00
(2/35): apr-util-1.6.1-8.fc29.x86_64.rpm                           543 kB/s |  90 kB     00:00
(3/35): apr-util-openssl-1.6.1-8.fc29.x86_64.rpm                   3.9 MB/s |  14 kB     00:00
...
(10/35): go-srpm-macros-2-18.fc29.noarch.rpm                       442 kB/s |  11 kB     00:00
...
(24/35): golang-1.11.2-1.fc29.x86_64.rpm                           401 kB/s | 625 kB     00:01
(35/35): golang-bin-1.11.2-1.fc29.x86_64.rpm                       653 kB/s |  90 MB     02:21
---------------------------------------------------------------------------------------------------
Total                                                              1.1 MB/s | 163 MB     02:29
...
...
...
Complete!

Pro jistotu se ještě přesvědčíme, že jsou k dispozici příkazy go a gofmt:

$ whereis -b go
go: /usr/bin/go
 
$ whereis -b gofmt
go: /usr/bin/gofmt
Poznámka1: v této kapitole jsme si ukázali instalaci jazyka Go ve verzi 1.11. Ve skutečnosti budou všechny demonstrační příklady pracovat i ve starší verzi 1.9 a 1.10, protože například mezi verzí 1.10 a 1.11 nebyly do samotného jazyka přidány žádné nové vlastnosti a neproběhly ani změny stávajících vlastností.
Poznámka2: alternativně si můžeme vyzkoušet instalaci Go přímo ze zdrojových kódů. Pro bootstraping však budete stejně potřebovat funkční překladač Go nebo gccgo (GCC s frontendem pro Go). Viz podrobnější informace, které naleznete na stránce https://golang.org/doc/in­stall/source.

5. Nastavení proměnné prostředí GOPATH

Po instalaci je vhodné nastavit proměnnou prostředí (environment variable) nazvanou GOPATH. Tato proměnná bude použita pro určení cíle překladu, pro hledání zdrojových kódů modulů atd. Dnes ji sice ještě nebudeme nutně potřebovat, ale je vhodné si tuto proměnnou připravit pro další části tohoto seriálu, kde již budeme pracovat s větším množstvím projektů a knihoven. Proměnná bude nastavována při startu BASHe, takže pokud používáte jiný shell, budete si muset upravit následující příkazy:

$ mkdir -p $HOME/go
$ echo 'export GOPATH=$HOME/go' >> $HOME/.bashrc
$ source $HOME/.bashrc

Nyní by již měla být proměnná nastavena, o čemž se můžeme snadno přesvědčit (shell není zapotřebí spouštět znovu, protože jsme použili příkaz source):

$ echo $GOPATH
 
/home/tester/go

6. Základní kontrola instalace

Po instalaci základních nástrojů programovacího jazyka Go můžeme odzkoušet, zda jsou tyto nástroje skutečně použitelné. Většina funkcí je dostupná přes „univerzální“ příkaz go, takže si pro začátek vypíšeme, která verze jazyka je vlastně aktuálně dostupná. K tomu slouží tato varianta příkazu:

$ go version

V našem případě by se měla vypsat verze 1.11.2:

go version go1.11.2 linux/amd64

Při instalaci starší verze (například na Fedoru 27) se samozřejmě vypíše odlišná verze jazyka:

go version go1.9.7 linux/amd64
Poznámka: v současnosti jsou v praxi používány verze 1.8 (skutečně!), 1.9, 1.10 a 1.11. Novinky, opravy a případné známé chyby jsou shrnuty v dokumentech Release Notes, které naleznete na adresách https://golang.org/doc/go1.8, https://golang.org/doc/go1.9, https://golang.org/doc/go1.10 a samozřejmě též https://golang.org/doc/go1.11.

Vraťme se však k popisu základních nástrojů, které mají vývojáři k dispozici. Po spuštění samotného nástroje go bez dalších parametrů by se měly vypsat všechny dostupné příkazy:

$ go

Výstup zobrazený na terminálu/konzoli by měl u verze 1.11 vypadat přibližně následovně:

Go is a tool for managing Go source code.
 
Usage:
 
        go  [arguments]
 
The commands are:
 
        bug         start a bug report
        build       compile packages and dependencies
        clean       remove object files and cached files
        doc         show documentation for package or symbol
        env         print Go environment information
        fix         update packages to use new APIs
        fmt         gofmt (reformat) package sources
        generate    generate Go files by processing source
        get         download and install packages and dependencies
        install     compile and install packages and dependencies
        list        list packages or modules
        mod         module maintenance
        run         compile and run Go program
        test        test packages
        tool        run specified go tool
        version     print Go version
        vet         report likely mistakes in packages
 
Use "go help <command>" for more information about a command.
 
Additional help topics:
 
        buildmode   build modes
        c           calling between Go and C
        cache       build and test caching
        environment environment variables
        filetype    file types
        go.mod      the go.mod file
        gopath      GOPATH environment variable
        gopath-get  legacy GOPATH go get
        goproxy     module proxy protocol
        importpath  import path syntax
        modules     modules, module versions, and more
        module-get  module-aware go get
        packages    package lists and patterns
        testflag    testing flags
        testfunc    testing functions
 
Use "go help <topic>" for more information about that topic.

Poslední základní kontrola, kterou dnes provedeme, se týká nastavení proměnné prostředí GOPATH, o níž jsme se zmínili výše. Zobrazení aktuálně nastavené hodnoty této proměnné prostředí tak, jak ji vidí nástroje Go, zajistí příkaz:

$ go env GOPATH

Pro uživatele „tester“ dostaneme tento výstup:

/home/tester/go

7. První demonstrační příklad – klasický „Hello world!“ ve dvou variantách

Důležitá poznámka: ve zdrojových kódech pro Go je zvykem pro odsazení používat znaky Tab (tabulační zarážky). Ovšem vzhledem k omezením redakčního systému serveru Root (resp. celého Internet Infa) nemusí být tyto znaky ve výpisech zdrojových kódů zachovány. Z tohoto důvodu je vhodné buď zdrojové kódy po jejich zkopírování z dnešního článku „prohnat“ nástrojem gofmt popsaným v desáté kapitole nebo – což je vhodnější – si naklonovat Git repositář se všemi demonstračními příklady a jejich případnými opravami. Více informací o tomto repositáři zjistíte v devatenácté kaptiole.

Konečně se dostáváme k popisu samotné syntaxe a sémantiky programovacího jazyka Go. Již tradičně začínají podobně koncipované články a knihy programem, který po svém spuštění vypíše na terminál řetězec „Hello world!“ a následně se ukončí. V Go je možné tento příklad napsat následujícím způsobem (úplný a naformátovaný zdrojový kód získáte z adresy https://github.com/tisnik/go-root/blob/master/article01/01_he­llo_world.go):

package main
 
func main() {
        println("Hello world!")
}

Povšimněte si, že v programu explicitně specifikujeme jméno balíčku (zde „main“). Dále se ve zdrojovém kódu programu s využitím klíčového slova func deklaruje funkce nazvaná taktéž „main“. Tato funkce, která je současně vstupním bodem do aplikace, nemá žádné parametry ani návratovou hodnotu (více se o návratových hodnotách dozvíme v navazujících kapitolách) a voláme z ní funkci nazvanou println(). Jedná se o funkci dostupnou ze základní knihovny, jejíž možnosti jsou sice dosti zásadním způsobem omezeny, ovšem pro programy typu „Hello world!“ mohou být dostačující. A na závěr – za příkazem volání funkce není nutné psát středník (ve skutečnosti bude středník při přeformátování zdrojového kódu odstraněn).

První příklad si můžeme spustit a to konkrétně příkazem go run. Před samotným spuštěním se sice (potichu) provede překlad, přičemž spustitelná verze programu je uložena do dočasného souboru:

$ go run 01_hello_world.go
Hello world!

Pokud si budeme přát program explicitně přeložit a zachovat výsledný binární soubor, použijeme namísto go run příkaz go build:

$ go build 01_hello_world.go

Výsledkem bude soubor pojmenovaný stejně jako soubor zdrojový, ovšem bez koncovky „.go“ (na Unixových systémech):

$ ls -l

total 1072
-rwxrwxr-x. 1 tester tester 1067905 Nov  8 12:45 01_hello_world
-rw-rw-r--. 1 tester tester      55 Nov  7 09:02 01_hello_world.go

Nově vytvořený soubor nazvaný „01_hello_world“ obsahuje jak přeložený nativní kód naší aplikace, tak i všechny potřebné funkce nutné pro spuštění aplikace (včetně automatického správce paměti). Právě kvůli tomu, že se v tomto binárním souboru nachází i všechny potřebné runtime funkce, je soubor tak velký (cca 1,1MB, oproti tomu aplikace se stejnou funkcí naprogramovaná v C bude mít po překladu velikost 6 až 14 kB v závislosti na architektuře, v assembleru se na platformě x86–64 dostaneme na 504 bajtů).

Právě přeloženou aplikaci si můžeme velmi snadno spustit následujícím příkazem:

$ ./01_hello_world
Hello world!

Pokud budete chtít prozkoumat, jaké symboly se vlastně v souboru „01_hello_world“ nachází (je jich zhruba 1300!, protože je zde skutečně celý runtime), můžeme použít příkaz objdump:

$ objdump -t 01_hello_world

Ve druhém příkladu, který naleznete na adrese https://github.com/tisnik/go-root/blob/master/article01/02_bet­ter_hello_world.go, používáme namísto dosti primitivní funkce println univerzálnější funkci Println. Tato funkce se nachází v balíčku fmt, který je nutné explicitně naimportovat a funkci plně kvalifikovat:

package main
 
import "fmt"
 
func main() {
        fmt.Println("Hello world!")
}

Opět si ukažme způsob tichého překladu a spuštění tohoto příkladu:

$ go run 02_better_hello_world.go 
Hello world!

8. Jak pracovat s dokumentací

Dokumentace k jazyku Go i ke standardním knihovnám je umístěna na adrese https://godoc.org/. V předchozích příkladech jsme používali balíček „fmt“, který je součástí standardní knihovny jazyka Go a je samozřejmě taktéž na výše zmíněných stránkách popsán: https://godoc.org/fmt.

Kromě toho je však možné si nechat dokumentaci zobrazit přímo v terminálu při vývoji. K tomu slouží příkaz go doc, kterému se jako další parametr předá jméno balíčku popř. přímo jméno funkce, jejíž popis potřebujeme získat:

$ go doc builtin

Výsledek bude na terminálu zobrazen následujícím způsobem:

package builtin // import "builtin"
 
Package builtin provides documentation for Go's predeclared identifiers. The
items documented here are not actually in package builtin but their
descriptions here allow godoc to present documentation for the language's
special identifiers.
...
...
...
func print(args ...Type)
func println(args ...Type)

Samozřejmě je možné použít utility pro stránkování – less, more atd.:

$ go doc fmt | less

S výstupem:

package fmt // import "fmt"
 
Package fmt implements formatted I/O with functions analogous to C's printf
and scanf. The format 'verbs' are derived from C's but are simpler.
 
 
Printing
 
The verbs:
 
General:
 
    %v  the value in a default format
        when printing structs, the plus flag (%+v) adds field names
    %#v a Go-syntax representation of the value
    %T  a Go-syntax representation of the type of the value
    %%  a literal percent sign; consumes no value

Pokud je zapotřebí zobrazit dokumentaci k funkci z nějakého balíčku, použije se následující styl zápisu:

$ go doc fmt.Println

S výstupem:

func Println(a ...interface{}) (n int, err error)
    Println formats using the default formats for its operands and writes to
    standard output. Spaces are always added between operands and a newline is
    appended. It returns the number of bytes written and any write error
    encountered.

9. Doporučovaný styl zápisu programů

Tvůrci programovacího jazyka Go navrhli, jakým způsobem se mají formátovat zdrojové kódy. Nezůstalo ovšem pouze u doporučení, protože přímo v základní sadě nástrojů jazyka Go nalezneme i utilitku nazvanou příznačně gofmt. Tento nástroj slouží k přeformátování zdrojových kódů takovým způsobem, aby jejich výsledný tvar odpovídal specifikaci. Díky tomu mají (nebo by alespoň měly mít) všechny zdrojové kódy napsané v Go jednotný standardní formát a například diskuze o použití tabů nebo mezer, popř. o kolik znaků se má provést odsazení bloků, přestávají mít smysl (o to jsou ale zábavnější).

Ve zkratce autoři Go vlastně říkají: namísto dlouhých popisů standardů formátování zdrojových kódů, které známe například z PEP-8, GNU Coding Standards či Linux kernel coding style se v Go formátováním programátoři příliš zabývat nemusí, protože za ně většinu práce odvede nástroj gofmt, který má navíc (z definice) vždycky pravdu :-)

10. Nástroj gofmt určený pro přeformátování zdrojových kódů

Pokud jste provedli instalaci Go a jeho nástrojů podle pokynů ze čtvrté kapitoly, bude nástroj gofmt jednoduše použitelný:

$ gofmt --help

Tento nástroj podporuje různé volby, ovšem v praxi využijete pravděpodobně především volbu -w pro zápis přeformátovaného zdrojového kódu do jiného souboru:

usage: gofmt [flags] [path ...]
  -cpuprofile string
        write cpu profile to this file
  -d    display diffs instead of rewriting files
  -e    report all errors (not just the first 10 on different lines)
  -l    list files whose formatting differs from gofmt's
  -r string
        rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')
  -s    simplify code
  -w    write result to (source) file instead of stdout

Zkusme například formátovači předat následující zdrojový kód se středníky, špatným odsazením, prázdnými řádky atd.:

package main
 
 
 
 
import "fmt";
func main() {
  fmt.Println("Hello, playground");
}

Po naformátování získáme tento obsah, který plně odpovídá specifikaci:

package main
 
import "fmt"
 
func main() {
    fmt.Println("Hello, playground")
}

11. Kontrola použití importovaných modulů

Ve třetím příkladu, jehož zdrojový kód naleznete na adrese https://github.com/tisnik/go-root/blob/master/article01/03_u­nused_imports.go, je předchozí zdrojový kód upraven takovým způsobem, že namísto funkce fmt.Println opět použijeme standardní funkci println, ovšem ponecháme deklaraci importu „fmt“:

package main
 
import "fmt"
 
func main() {
        println("Hello world!")
}

Z pohledu překladače programovacího jazyka Go se jedná o potenciální chybu, na kterou taktéž standardním způsobem upozorní:

$ go run 03_unused_imports.go
 
# command-line-arguments
./03_unused_imports.go:3:8: imported and not used: "fmt"
Poznámka: navíc ještě překladač hlídá, zda nedochází k cyklickým importům. Podrobnosti si uvedeme příště.

12. Plná podpora Unicode v programech

Další vlastnost programovacího jazyka Go by nás neměla překvapit. Jedná se o plnohodnotnou podporu Unicode a taktéž kódování UTF-8. Proč není tato vlastnost překvapivá? Za vznikem UTF-8 totiž stojí právě Rob Pike a Ken Thompson, shodou okolností dva ze tří návrhářů programovacího jazyka Go. Ukažme si, že se UTF-8 může používat přímo ve zdrojovém kódu čtvrtého demonstračního příkladu:

package main
 
import "fmt"
 
func main() {
        fmt.Println("╭─────────────────────╮")
        fmt.Println("│ příλiš žλuťΩučký kůň│")
        fmt.Println("╰─────────────────────╯")
}

Tento program půjde bez problémů přeložit i spustit (za předpokladu, že je správně nastavený terminál, což by snad v 21.století nemusel být problém):

$ go run 04_hello_unicode.go 
 
╭─────────────────────╮
│ příλiš žλuťΩučký kůň│
╰─────────────────────╯
Poznámka: ve skutečnosti je přímo definováno, že zdrojové kódy Go jsou reprezentovány v UTF-8 (ASCII je podmnožinou UTF-8). Nevznikají zde tedy tak problematické situace, jako například v Javě, kdy je možné zdrojový kód reprezentovat v různých kódováních a formátech. Můžeme zde opět vidět stejný pragmatismus, jaký jsme již zažili u formátování zdrojových kódů.

13. Základy typového systému – odvození typu proměnné z hodnoty

Podobně jako v mnoha dalších kompilovaných programovacích jazycích nalezneme i v jazyce Go velké množství základních datových typů, které do určité míry odráží vlastnosti mikroprocesorů, na kterých budou aplikace provozovány (předpokládají se dnes převažující architektury s osmibitovými, 16bitovými, 32bitovými a 64bitovými slovy, ne některé speciální DSP atd.). Kromě numerických typů patří mezi základní datové typy i řetězce (ty jsou neměnitelné – immutable), pole (array), takzvané řezy (slice) a mapy (map). V Go nalezneme i ukazatele, ovšem jejich význam je oproti C či C++ do značné míry omezen. Nové datové typy jsou samozřejmě uživatelsky definovatelné, což je téma, kterému se budeme podrobněji věnovat příště.

Zajímavé a velmi užitečné je, že Go dokáže odvodit typ proměnné z hodnoty, která je proměnné přiřazena při její deklaraci. Pro přiřazení se v takovém případě používá operátor := (short variable declaration) a u proměnné se nemusí deklarovat její typ (úplný zdrojový kód):

package main
 
import "fmt"
 
func main() {
        a := 10
        fmt.Println(a)
        b := "hello"
        fmt.Println(b)
        c := true
        fmt.Println(c)
}

Výsledek po spuštění programu:

$ go run 05_basic_type_inference.go
 
10
hello
true
Poznámka: Go používá silný a především statický typový systém, v němž odvození typu proměnné na základě přiřazované hodnoty je jen syntaktický cukr, nikoli snaha o vytvoření dynamického typování.

Jakmile je proměnná deklarována, není ji možné v daném bloku deklarovat znovu, tudíž ani není možné použít znovu přiřazení zapisované operátorem :=. Můžeme si to snadno otestovat na dalším příkladu:

package main
 
import "fmt"
 
func main() {
        a := 10
        fmt.Println(a)
        b := "hello"
        fmt.Println(b)
        c := true
        fmt.Println(c)
 
        a := "world"
        fmt.Println(a)
        b := 0
        fmt.Println(b)
        c := nil
        fmt.Println(c)
}

V tomto případě dojde k chybě při překladu:

$ go run 06_variable_redeclaration.go
 
# command-line-arguments
./06_variable_redeclaration.go:13:4: no new variables on left side of :=
./06_variable_redeclaration.go:15:4: no new variables on left side of :=
./06_variable_redeclaration.go:17:4: no new variables on left side of :=

Pokud ovšem namísto operátoru := použijeme běžné přiřazení, bude zdrojový kód bez problému přeložitelný (a bude samozřejmě korektní i po stránce sémantiky):

package main
 
import "fmt"
 
func main() {
        a := 10
        fmt.Println(a)
        b := "hello"
        fmt.Println(b)
        c := true
        fmt.Println(c)
 
        a = 20
        fmt.Println(a)
        b = "world"
        fmt.Println(b)
        c = false
        fmt.Println(c)
}

Výsledek po překladu a spuštění:

$ go run 07_variable_reassign.go 
 
10
hello
true
20
world
false

14. Kontrola typu hodnoty přiřazované do proměnné

Díky silnému typovému systému je pochopitelně odhalena snaha o přiřazení hodnoty jiného typu do již deklarované proměnné, což si ověříme na dalším příkladu:

package main
 
import "fmt"
 
func main() {
        a := 10
        fmt.Println(a)
        b := "hello"
        fmt.Println(b)
        c := true
        fmt.Println(c)
 
        a = "world"
        fmt.Println(a)
        b = 0
        fmt.Println(b)
        c = nil
        fmt.Println(c)
}

Výsledek snahy o překlad:

$ go run 08_no_true_dynamic_type.go 
 
# command-line-arguments
./08_no_true_dynamic_type.go:13:6: cannot use "world" (type string) as type int in assignment
./08_no_true_dynamic_type.go:15:4: cannot use 0 (type int) as type string in assignment
./08_no_true_dynamic_type.go:17:4: cannot use nil as type bool in assignment

Tento příklad byl sice velmi jednoduchý, ale podobně bude typová kontrola fungovat i při použití uživatelských typů, polí, řezů atd.

Poznámka: možná jste si všimli přiřazení řetězce (řetězcového literálu) do proměnné. Z pohledu programátora se s řetězci pracuje stejně, jako s dalšími hodnotami nějakého primitivního typu (celé číslo, znak, číslo s plovoucí řádovou čárkou). Interně je samozřejmě nutné provést alokaci na haldě, to je pro nás ovšem většinou zcela skryto.

15. Deklarace funkcí

S deklarací funkce jsme se již setkali (jednalo se o funkci main), takže si jen krátce ukážeme, jak je možné nadeklarovat další funkci bez parametrů a bez návratové hodnoty. Testovaná funkce se bude jmenovat printHello. Vzhledem k tomu, že funkce neakceptuje žádné parametry, uvedou se za jejím názvem pouze prázdné kulaté závorky a za nimi již začíná tělo funkce uvozené levou složenou závorkou. Při volání funkce bez parametrů se opět použijí prázdné kulaté závorky:

package main
 
import "fmt"
 
func printHello() {
        fmt.Println("Hello world!")
}
 
func main() {
        printHello()
}

Tato funkce je volána z funkce main, pochopitelně bez parametrů:

$ go run 09_simple_function.go 
 
Hello world!

16. Specifikace typu argumentů funkcí

Ukažme si nyní, jak se specifikuje typ parametru či parametrů funkce. Na rozdíl od jazyků C, C++ či Java se pro specifikaci parametrů používá zápis „jméno parametru [mezera] datový typ“. Volání funkce s parametrem/parametry se již nijak výrazně neodlišuje od jiných mainstreamových programovacích jazyků:

package main
 
import "fmt"
 
func printMessage(message string) {
        fmt.Println(message)
}
 
func main() {
        printMessage("Hello world!")
}

Překlad a spuštění tohoto demonstračního příkladu:

$ go run 10_function_with_params.go 
 
Hello world!

17. Příkaz return

Funkce, které tvoří základní stavební prvek programů psaných v Go (ostatně i metody jsou zde chápány jako funkce), samozřejmě mohou vracet nějakou návratovou hodnotu. Její typ se uvede za pravou uzavírací kulatou závorku se specifikací parametrů funkce. Přímo ve funkci je možné použít příkaz return, ovšem existují i další možnosti, s nimiž se seznámíme později. Podívejme se, jak může vypadat deklarace funkce bez parametrů, která ovšem vrací hodnotu typu řetězec (string):

func getMessage() string {
        return "Hello world!"
}

Příklad s voláním takové funkce:

package main
 
import "fmt"
 
func getMessage() string {
        return "Hello world!"
}
 
func printMessage(message string) {
        fmt.Println(message)
}
 
func main() {
        printMessage(getMessage())
}

Tento demonstrační příklad po svém spuštění opět vypíše zprávu „Hello world!“:

$ go run 11_return_statement.go
 
Hello world!

Alternativně je možné pojmenovat výstupní parametr (parametry) a vlastní určení, jaká hodnota se má vrátit, bude provedeno prostým přiřazením (což se vzdáleně podobná způsobu, jakým pracují funkce v Pascalu):

package main
 
import "fmt"
 
func getMessage() (message string) {
        message = "Hello world!"
        return
}
 
func printMessage(message string) {
        fmt.Println(message)
}
 
func main() {
        printMessage(getMessage())
}

Po příkazu return by se již neměl použít žadný další příkaz, což je kontrolováno překladačem:

package main
 
import "fmt"
 
func getMessage() (message string) {
        message = "Hello world!"
        return
        x := 42
}
 
func printMessage(message string) {
        fmt.Println(message)
}
 
func main() {
        printMessage(getMessage())
}

Otestujme si, zda překladač skutečně ohlídá, že se za return už žádný další příkaz nepoužije:

$ go run 16_return_at_the_end_of_function.go 
 
# command-line-arguments
./16_return_at_the_end_of_function.go:9:1: missing return at end of function

18. Vrácení většího množství hodnot z funkce

V programovacím jazyce Go se velmi často setkáme s tím, že funkce vrací volajícímu kódu větší množství návratových hodnot. Mnoho funkcí ze standardní knihovny například vrací vlastní požadovanou hodnotu a taktéž kód chyby; toto chování je ovšem doporučeno používat i v ostatních modulech. Podívejme se na základní příklad, v němž funkce swap akceptuje dva parametry a vrací je, ovšem v opačném pořadí. Kód pracující s oběma návratovými hodnotami je zvýrazněn:

package main
 
import "fmt"
 
func swap(a int, b int) (int, int) {
        return b, a
}
 
func main() {
        x := 1
        y := 2
 
        var z int
        var w int
 
        z, w = swap(x, y)
        fmt.Println("z =", z)
        fmt.Println("w =", w)
}

Výsledek po překladu a spuštění tohoto demonstračního příkladu:

$ go run 14_swap_function.go 
 
z = 2
w = 1

Zajímavé bude propojení dvou vlastností programovacího jazyka Go: automatického určení typu proměnné při její deklaraci na základě přiřazované hodnoty a možnosti vrátit větší množství hodnot z volané funkce. Povšimněte si zejména zvýrazněného řádku, na němž jsou deklarovány dvě lokální proměnné z a w, je jim přiřazen typ a taktéž přímo hodnota získaná voláním funkce swap:

skoleni

package main
 
import "fmt"
 
func swap(a int, b int) (int, int) {
        return b, a
}
 
func main() {
        x := 1
        y := 2
        z, w := swap(x, y)
 
        fmt.Println("z =", z)
        fmt.Println("w =", w)
}

Opět si ukažme výsledek po překladu a spuštění upraveného demonstračního příkladu:

$ go run 15_swap_function_B.go 
 
z = 2
w = 1

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

Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Demonstrační příklad Popis Cesta
1 01_hello_world.go klasický „Hello world!“ https://github.com/tisnik/go-root/blob/master/article01/01_he­llo_world.go
2 02_better_hello_world.go upravená verze předchozího příkladu https://github.com/tisnik/go-root/blob/master/article01/02_bet­ter_hello_world.go
3 03_unused_imports.go chybný příklad: nepoužívaný import https://github.com/tisnik/go-root/blob/master/article01/03_u­nused_imports.go
4 04_hello_unicode.go použití Unicode ve zdrojovém kódu https://github.com/tisnik/go-root/blob/master/article01/04_he­llo_unicode.go
5 05_basic_type_inference.go deklarace proměnné s odvozením typu https://github.com/tisnik/go-root/blob/master/article01/05_ba­sic_type_inference.go
6 06_variable_redeclaration.go snaha o deklaraci existující proměnné https://github.com/tisnik/go-root/blob/master/article01/06_va­riable_redeclaration.go
7 07_variable_reassign.go přiřazení hodnoty již existující proměnné https://github.com/tisnik/go-root/blob/master/article01/07_va­riable_reassign.go
8 08_no_true_dynamic_type.go přiřazení hodnoty špatného typu již existující proměnné https://github.com/tisnik/go-root/blob/master/article01/08_no_tru­e_dynamic_type.go
9 09_simple_function.go uživatelská funkce https://github.com/tisnik/go-root/blob/master/article01/09_sim­ple_function.go
10 10_function_with_params.go funkce s parametry https://github.com/tisnik/go-root/blob/master/article01/10_fun­ction_with_params.go
11 11_return_statement.go návratová hodnota funkce https://github.com/tisnik/go-root/blob/master/article01/11_re­turn_statement.go
12 12_named_return_variable.go pojmenovaná návratová hodnota https://github.com/tisnik/go-root/blob/master/article01/12_na­med_return_variable.go
13 13_sum_function.go parametry funkce https://github.com/tisnik/go-root/blob/master/article01/13_sum_fun­ction.go
14 14_swap_function.go funkce vracející dvě hodnoty https://github.com/tisnik/go-root/blob/master/article01/14_swap_fun­ction.go
15 15_swap_function_B.go alternativa předchozího příkladu https://github.com/tisnik/go-root/blob/master/article01/15_swap_fun­ction_B.go

20. Odkazy na Internetu

  1. The Go Programming Language (home page)
    https://golang.org/
  2. GoDoc
    https://godoc.org/
  3. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  4. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  5. The Go Programming Language Specification
    https://golang.org/ref/spec
  6. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  7. Package builtin
    https://golang.org/pkg/builtin/
  8. Package fmt
    https://golang.org/pkg/fmt/
  9. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  10. 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
  11. Learning Go
    https://www.miek.nl/go/
  12. Go Bootcamp
    http://www.golangbootcamp.com/
  13. 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
  14. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  15. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  16. The Go Blog
    https://blog.golang.org/
  17. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  18. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  19. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  20. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  21. 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
  22. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  23. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  24. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  25. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  26. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  27. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  28. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  29. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  30. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  31. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  32. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  33. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  34. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  35. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  36. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  37. 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/
  38. 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
  39. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  40. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  41. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  42. Go vs. Python
    https://www.peterbe.com/plog/govspy
  43. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  44. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  45. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  46. Go by Example: Slices
    https://gobyexample.com/slices
  47. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  48. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  49. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  50. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  51. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  52. nils In Go
    https://go101.org/article/nil.html
  53. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  54. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  55. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  56. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  57. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  58. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  59. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  60. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  61. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  62. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  63. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  64. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  65. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  66. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  67. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  68. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.