Golang – pohled na jazyk od admina

V minulém roce jsem měl, kvůli pandemii, více volného času, tak jsem se pustil do studia nového jazyka. Tím je Golang. V tomto článku bych se na něj chtěl podívat pohledem dlouhodobého administrátora serverů, který si občas potřebuje napsat nějaký pomocný prográmek. V tuto chvíli golang používám cca rok a půl.

Pro netrpělivé

Pokud chcete jen začít a nechcete číst celé tohle povídání, tady je rychlý úvod, jak na to.

  • Projděte si gotour. Doporučuju to projít celé, bez ohledu na to, jak jste zdatní v jiných jazycích. Prostředí gotur si můžete nainstalovat k sobě.
  • Budete potřebovat editor, jakýkoliv stačí, já používám VS Code. Na linuxu i na widlích.
    • Určitě chcete plugin Go – code nabízí vhodné pluginy postupně, netřeba si nic chystat dopředu. Takže v okamžiku, kdy začnete psát svůj první program, code vám nabídne podporu pro daný jazyk automatiky. Není žádný problém mít v jednom editoru (workspace) otevřených více projektů napsaným ve více jazycích.
    • Používám dark theme od Chromodynamics, vyberte si vzhled editoru podle svého.
    • Pro poznámky se hodí jazyk markdown.
  • Poučte se od zkušenějších.
    • Buď přímo tutoriály, hodí se pro úplné začátečníky.
    • Nebo nějaký ucelený seriál o hotovém projektu. Najděte si, co vás zajímá.

Proč právě Golang

Jsem správce serverů, nejsem programátor. To jen pro ujasněnou na počátek. Co to vlastně znamená? Programování beru nikoliv jako střed světa, ale jako pomocný nástroj k dosažení nějakého cíle. Nástroje od admina pro admina musejí dobře padnout do ruky stejně jako klíč v opravárenské dílně. Nejsou nutně vyrobeny z nejlepších materiálů, ani nemusí být nejoptimálnější. Musí prostě fungovat a dobře se používat. (Tohle neznamená, že mohou být napsané špatně, být chybné apod. To ne, musejí fungovat, musejí se vyrovnat s různými vstupy, ale nemusí být nutně nejrychlejší a ten kód nemusí obsahovat poslední výstřelky z teoretické informatiky.)

Pro svou práci i soukromě potřebuju občas nějaké to udělátko, kterým si zautomatizuji nějakou činnost. Původně (historicky) měl k tomuto účelu sloužit shell, konkrétně bash, ale k tomu jsem nikdy příliš nepřirostl. Shell používám jen pro spouštění programů a nějaké ty jednoduché onelinery, typicky spojení programů do kolony rourami nebo find, xargs, parallel apod.

Pokud shell přestane stačit, nebo přeroste můj práh tolerance a trpělivosti (tj. skript už má více než deset řádků), tak sáhnu po nějakém jazyku. Posledních 10 let to byl Python. Je to velmi snadno použitelný skriptovací jazyk se spoustou balíčků. Lze si tedy velmi rychle napsat cli program pro automatizace nějaké činnosti. Pro cli se hodí balíček Click, pro stahování z webu Requests, pro připojení do db Psycopg2, pro malý webserver Flask.

Zásadní výhodou jazyka Python je jeho přítomnost ve všech systémech. Jak na Linuxu, tak na FreeBSD je už v základu. Jsou v něm napsané nějaké systémové skripty. I na Windows jej lze snadno doinstalovat.

Zdálo by se tedy, že není žádný důvod měnit. Ale Python má některé nepříjemné vlastnosti. Je to skriptovací jazyk a je pomalý. Někdy to nevadí, někdy je to náročnější výpočty nutné použít NumPy. Potom má problém s paralelizací. Ano, má knihovnu multiprocessing, ale ta pouze obchází základní omezení Pythonu a tím je Global Interpretr Lock. Python je tedy fakticky jednovláknový. To neznamená, že nelze věci paralelizovat, má generátory a asynchronní volání kde čeho, ale jede to jen v jednom vlákně.

Další nepříjemnou vlastností Pythonu jsou externí balíčky. Ty se stahují pro každého uživatele zvlášť. Na FreeBSD se zásadně nedoporučuje instalovat pip balíčky do systému. Takže každý uživatel nějakého programu má u sebe vlastní kopii všech balíčků, který tento program potřebuje. Pokud to někomu nevadí, není to problém. Autor skriptu napíše setuptools a vše proběhne automaticky. Jenže mít na disku 30x totéž není zrovna systémové. Nehledě na update.

Takže jsem hledal nový jazyk.

Požadavky:

  • Pokud možno kompilovaný, jako doplněk ke stávajícímu skriptovacímu jazyku. Je vhodné mít ve výbavě jak kompilovaný, tak skriptovací jazyk.
  • Staticky a silně typovaný. Mám rád typy, mám rád kontroly. Mám rád, když mě kompilátor upozorní na spoustu věcí a nemusím je testovat až při běhu (jako v Pythonu).
  • Bez běhového prostředí. Uvažoval jsem i o Erlangu, ale odradilo mě mít jako závislost další běhové prostředí. Všechno v javě potřebuje jvm, všechno v pythonu, ruby, php potřebuje mít interpretr. Opět je potřeba mít tohle všechno v systému. (Staticky) kompilovaný program nic dalšího, kromě kernelu, nepotřebuje.
  • S podporou paralelizace. V před dvěma lety jsem se tady zasnil nad tím, proč neexistují jazyky s podporou paralelizace přímo v základu. Nechtěl jsem prozrazovat, ale zaujalo mě, že nikdo nezmínil jazyky typu Erlang (což je asi ten největší extrém), Rust nebo Golang. Nevím, jestli je to dáno typem lidí, kteří chodí na ABCLinuxu, nebo jestli je můj požadavek příliš. Podle mě je snadná paralelizace nezbytnost.
  • Minimum závislostí. Toto je ve skutečnosti velmi sporné a má to své pro a proti, ale k tomu se dostaneme.

Takže jsem měl v hledáčku Golang. Věděl jsem, že to je kompilovaný jazyk od autorů Unixu, že má za cíl nahradit C jako systémový jazyk, že má paralelizaci a je silně a staticky typovaný. (Jestli se mu toto povede necháme na jinou diskusi. Zatím se zdá, že systémovou náhradou za C++ bude spíše Rust. Ale to vůbec nevadí.)

První seznámení s go byl šok. V tom negativním slova smyslu. Tohle má být jazyk pro 21. století? Vypadalo to, jako neúspěšná verze jazyka z roku 1975. Vždyť to neumí ani vizuálně syntakticky přiřadit metodu ke struktuře. (Vážně, ona se někomu syntaxe func receiver líbí? – na můj vkus je na jednom řádku příliš závorkových bublin – pro datový typ, pro argumenty fce a pro návratové hodnoty, pokud je jich víc. Tři bubliny na řádku a každá z nich má jiný syntaktický význam.) Poznámka po roce: ano, i na toto se dá zvyknout. Oddělení implementace metod od definice struct má tu možnost, že si func receiver napíšete kdykoliv a pro jakýkoliv datový typ.

Co je Golang

Staticky typovaný jazyk,

kde o typy prakticky nezavadíte. Tohle mě velmi překvapilo. Čekal jsem, že na každém řádku a při každé deklaraci proměnné bude nutné zadávat typ. Ne, není to nutné. Právě naopak. Typy proměnných není nutné zadávat skoro nikdy. Vždy se odvodí od typu návratové hodnoty z nějaké func. Díky statickému typování kompilátor ví, jaký typ má vrácená hodnota a už nepožaduje tuto informaci uvádět ještě u proměnné.

Minimalistický jazyk

Nevím, kolik má vyhrazených slov a zda je nejmenší (určitě není), ale Golang je syntakticky velmi jednoduchý jazyk. Už po pár dnech se naučíte číst cizí kusy kódu nebo číst standardní knihovnu (doporučuju), protože těch jazykových konstrukcí není moc. Ano, k některým se budete muset vracet (pro mě překvapivě for a switch, protože for umí smyčku asi pěti různými způsoby) do dokumentace, ale velmi rychle to přejde do krve.

Golang má first class (high order) funkce. Upřímně nevím, proč se tohle všude uvádí, protože pointer na funkci umělo i C a Pascal (1970), ale ano, i v Go můžeme funkci předat jako parametr nebo ji ukládat do proměnné, mít pole funkcí, mapu funkcí apod. Prostě anonymní funkce. Klidně z toho udělejte lambdu nebo uzávěr. (Copak existuje moderní jazyk, který to nemá? Sice se tomu říká pokaždé jinak, už v roce 1997 v Pascalu jsme to používali pro callback, ale je to jedno.)

Golang má goto. Ano, použil jsem goto. Při implementaci jednoduchého konečného automatu je to lepší, než les ifů a elsů. Pochopitelně to nepřehánět. Vždy se tomu dá vyhnout. I ten DFA lze napsat normálně pomocí matice transformačních funkcí.

Paralelizace v základu

Golang obsahuje gorutiny a kanály. Gorutina je funkce běžící samostatně ve vlastním vlákně. Golang funguje na principu Work Stealing Scheduler a má několiv front podle počtu procesorů. Do jednotlivých front jsou přiřazovány gorutiny. Tj. program s 1000 gorutinami nemá 1000 vláken operačního systému, ale třeba 16 podle CPU. Každému CPU je přirazena jedna fronta.

Kanály slouží ke komunikaci mezi gorutinami. Zjednodušený pohled na kanály je jako na poštovní schránky. Gorutina hodí něco do schránky (kanálu) a jde dělat další práci. Nemusí čekat, až si to někdo vyzvedne. Adresát zprávy může dělat svoji práci a jednou za čas se podívá do svého kanálu a vybere si zprávu. Tu nějak zpracuje. A takto gorutiny běží vlastním tempem a jen si posílají zprávy. (Kanály mohou být nebufferované a v tomto případě odesílatel čeká na příjemce, je to tedy blokující; ale mohou být také bufferované na určitou velikost a potom to funguje výše popsaným způsobem.)

Kompilace do jedné binárky

Výsledkem vašeho jakkoliv rozsáhlého programu je jedna binárka. Nikoliv hromada dynamických knihoven. Toto je ve skutečnosti docela sporné téma. Klasický linuxový přístup ke tvorbě balíčků a programů je používat knihovny. Výhodou je, že v případě chyby nalezené v knihovně stačí aktualizovat jen tu jednu knihovnu a všechny programy, které ji používají, zůstávají beze změny. Takže bezpečnostní problémy lze vyřešit velmi snadno.

Golang se vydal jinou cestou. Cestou velmi snadné kompilace. Nástroj go build (nebo go install) sestaví program a automaticky si stáhne (go get) veškeré jeho závislosti. V případě, že je nutné nějakou závislost opravit, je nutné tento program překompilovat.

A teď je otázkou, co je lepší. Jestli mít systém knihoven a jejich verzí (bohužel) a dynamicky k nim linkovat programy. Nebo mít programy s přesně definovanými závislostmi a možností je kdykoliv snadno zkompilovat? Já nevím. Asi na to není univerzální odpověď. Pokud jsou v systému dobře připravené balíčky a lze definovat závislosti mezi balíčky, jsou lepší dynamické knihovny. Ale v prostředí, kdy programy vytváří kde kdo (myšleno negativně) a nikdo se nechce otravovat s vytvářením balíčků pro svůj OS, tak je možná lepší statická varianta. Z mého pohledu jakožto admina je lepší statická kompilace.

Instalace jakéhokoliv golang programu vypadá následovně:

go build && sudo cp program /usr/local/bin/

Odinstalace:

sudo rm /usr/local/bin/program

V době, kdy lidi používají kontejnery pro distribuci svých výtvorů místo toho, aby si vytvořili balíček pro svůj OS, je přístup golangu přijatelný kompromis. Můj výtvor je prostě jedna binárka, tu dám do systémového /usr/local/bin a je to nainstalováno. Jednou a pro všechny uživatele. Update je triviální, stačí přepsat existující binárku.

Crosscompilace v základu

K výše uvedené snadné kompilaci jedním příkazem je nutné ještě zmínit další skvělou vlastnost golangu a tím je stejně snadná možnost zkompilovat binárku i pro zcela jiný OS (i HW).

Velmi jednoduše pomocí GOOS= specifikujete OS, pro který se bude překládat. Takto na linuxu snadno vyrobím binárku pro windows nebo freebsd:

GOOS=windows go build
GOOS=freebsd go build

A nemusím na daných prostředích vůbec instalovat golang a kompilovat program tam. Toto mi jako adminovi šetří spoustu času, protože na pracovním ntb mám Windows (neptejte se), a velmi snadno si napíšu program, který potom spustím na linux serveru.

Možnost kompletně statické binárky

Binárka ze standardní kompilace přece jen závisí ještě na C knihovně a případně na dalších externích knihovnách od modulů třetích stran. Pokud je potřeba skutečně statická kompilace, je potřeba přidat parameter CGO=0:

export GOOS=linux
export GOARCH=amd64
export CGO=0
go build

Tady velmi záleží na externích knihovnách. Někdy nelze statické binárky dosáhnout. V takovém případě je potřeba hledat pure go alternativu k danému modulu a nebo se bez něj zcela obejít.

Závěr

Netvrdím, že Golang je nejlepší jazyk na světě. To ne. Někomu vadí to, že je od Google a že raději zůstane u nezávislého C++. Někomu vadí, že obsahuje garbage collector, tak má raději Rust.

Pro mě je golang ideálním spojením jednoduchosti jazyka, všemi nástroji okolo (kéž by takový toolset měl každý jazyk), dostatečnou standardní knihovnou (která na počátku může vypadat jak chudá příbuzná), to, že výsledkem může být jedna statická binárka (z logiky věci závislá pouze na kernelu), kterou snadno „instaluju“ pouhým zkopírováním, to že standardní knihovna nabízí interní webserver a tak se velmi snadno vyrobí restful api služba běžící na nějakém portu. Zkrátka snadno a rychle si vyrobíte udělátko, které vám na cli bezvadně poslouží a ze kterého, když na to přijde, velmi snadno vyrobíte systémovou službu. Pro mě, jako admina, ideální řešení.

Příspěvek byl publikován v rubrice FreeBSD, Golang, Linux, Počítače, Programovací jazyky. Můžete si uložit jeho odkaz mezi své oblíbené záložky.