Dnes si povíme něco o tom, jak funguje kryptografie v Go. Go jako (relativně) nový programovací je v této oblasti daleko a podporuje prakticky všechny možné šifry a řešení. Co je ale na osvěžující je fakt, že Go se stále snaží dělat věci jednoduše, takže na rozdíl od jiných nejmenovaných jazyků se zde nesetkáme s milionem interfaců a object factory.
Kryptografie je opravdu obsáhlý prostor, takže my si ukážeme použitelné a praktické implementace kryptografie.
Poslední dobou je možné sledovat na poli jazyka Go zajímavou bitvu. Jak se blíží Go verze 2.0
(a pozor, po 1.9
může klidně přijít 1.10
), tak se začínají objevovat nápady typu “přidejme do jazyka generické typy”. Nejdřív jsem i já stál na generické straně barikády, ale musím uznat, že postupem času se spíš kloním ke skupině genericsů méně, nebo třeba vůbec ne. Možná bych chtěl na toto téma rozvířit debatu, takže zde je můj pohled na věc.
Určitě to víte - Go nepodporuje výčtové typy - tedy enum
. Go umí numerické sekvence pomocí iota
, ale to se opravdu nerovná enumu. Když jsem se nedávno na projektu vrátil ke staršímu kódu (který jsem napsal), tak jsem si uvědomil, že v jedné chvíli jsem vlastně použil něco, co může fungovat jako náhrada za výčty. No posuďte sami.
package main
import (
"fmt"
)
type AnswerEnum struct {
string
}
var (
Yes AnswerEnum = AnswerEnum{"Yes"}
No AnswerEnum = AnswerEnum{"No"}
)
func main() {
cond := []AnswerEnum{AnswerEnum{"Yes"}, AnswerEnum{"No"}}
for i, answer := range cond {
switch answer {
case Yes:
fmt.Println("na indexu", i, "je hodnota", Yes.string)
case No:
fmt.Println("na indexu", i, "je hodnota", No.string)
}
}
}
To co se zde děje je zjevné. Nejdříve si vytvoříme nový typ typu AnswerEnum
, který je kompozicí typu string
. Jelikož lze stringy mezi sebou porovnávat, tak je pak možné použít tyto hodnoty v bloku switch
.
Určitě jste si toho všimli - Go má (podle mě) velmi dobrou podporu relačních databází. A to včetně věcí typu transakce. Pokud chcete ovšem transparentně přistupovat k databázi tak, aby vám bylo jedno, jestli dotaz provádíte v rámci transakce, nebo mimo ni, tak máte problém. Standardní API totiž rozlišuje mezi přímým dotazem do databáze a mezi dotazem v rámci transakce.
Abychom tuto “podružnost” eliminovali, a také si ukázali jak si udělat abstrakční vrstvu nad konkrétní implementací, tak si napíšeme zaobalení (wrapper) databáze. Bude mít stejné funkce na provádění základních dotazů jako má standardní Go API.
Mnohdy si člověk myslí, že něco je naprosto jasné. Tedy až do okamžiku, kdy dostanete dotaz, který vám ukáže, že nejasnosti jsou na každém rohu.
Zrovna teď se mně kolega ptal, jaký je rozdíl mezi kanálem prázdné struktury a kanálem prázdného interfacu.
Odpověď je, že zásadní.
Prázdná struktura jako typ má omezené, ale legální použití. Sama o sobě nemůže nést data (nemá žádné pole, kam se dalo cokoliv uložit), ale už její přítomnost nám umožňuje ji použít na místech, kde prostě potřebujeme “nějakou” proměnnou. Jako další místo použití vidím jako možnost zavěsit na tuto strukturu receivera (příjemce) a použít takto vytvořenou proměnnou jako parametr typu interface.
Když jsme se bavili o slicech tak nezazněla jedna podstatná informace. Jak vlastně vypadá taková struktura slicu.
Vytvořme si tedy slice.
package main
import "fmt"
func main() {
slice := []int{}
fmt.Println("délka:", len(slice))
fmt.Println("maximální délka:", cap(slice))
}
Délka i maximální délka jsou shodné, tedy 0.
Teď si k danému slicu přidejme jedno položku a zkusme to znovu.
package main
import "fmt"
func main() {
slice := []int{}
slice = append(slice, 0)
fmt.Println("délka:", len(slice))
fmt.Println("maximální délka:", cap(slice))
}
Tentokrát je délka 1 a maximální délka je 2. Co to znamená? append
nám nejen přidal do pole jednu položku, ale také nám nechal ve slicu “fuku” pro jedno další číslo. Tzn. pokud přidáme další položku, tak nám append
nemusí zvětšovat pole, prostě přidá položku na konec již alokovaného pole.
Možná už jste někdy slyšeli termín “duck typing
”. Pro ty co náhodou ne, tak jen krátce uvedu, že se jedná o aplikaci známého pořekadla “kváká to jako kachna, vypadá to jako kachna, tak je to kachna”.
Představte si tyto dva typy.
type Člověk struct {
Jméno string
}
type Mazlíček struct {
Jméno string
}
Je jasné, že se jedné o dva typy. Člověk
a Mazlíček
. Oba jsou ovšem stejné - mají (sice jen jedno) pole - Jméno
.
Pokud jako já používáte Visual Studio Code pro práci s projekty v Go tak jsme možná narazili na problém při formátování zdrojových souborů (standardně cmd + alt + L
na MacOs). Místo vytouženého zformátování se zobrazí
Cannot read property 'substring' of undefined
Nuže problém je v chybějící prázdné řádce na konci souboru. Stačí tedy jen přidat na konec souboru jednu prázdnou řádku a problém je vyřešen.
Např.
package main
func main () {
....
} // následuje prázdná řádka
Více na Githubu.
Dnes opět jen telegraficky. Go nepodporuje sady (množiny) a tak tu máme drobnou obezličku, jak si takovou sadu vytvořit. Využijeme toho, že mapy mají unikátní klíče a jsou netříděné. Že to zní povědomě? Ano! Vždyť to je přece množina.
Takže než abychom si mazali ruce nějakým bastlením kódu si uděláme např. toto:
package main
import (
"fmt"
)
func main() {
množinaIntů := map[int]struct{}{}
množinaIntů[2] = struct{}{}
množinaIntů[1] = struct{}{}
množinaIntů[3] = struct{}{}
množinaIntů[4] = struct{}{}
množinaIntů[1] = struct{}{}
// jen si mapu prosvištíme, ať víme co v ní je
for key := range množinaIntů {
fmt.Println("Položka množiny:", key)
}
// zjištění jestli množina obsahuje položku
if _, ok := množinaIntů[1]; ok {
fmt.Println("Množina obsahuje položku 1.")
}
}
Padl tu dotaz, proč je použit typ map[int]struct{}
. Tedy mapu s klíčem int
a položkami typu prázdná struktura. Důvod je prostý, prázdná struktura zabírá (logicky) v paměti 0 bajtů.
Dnes si povíme něco o Go rutinách. Všichni víme, že se používají, pokud chceme pracovat paralelně. Všichni určitě i víme, že Go rutiny nejsou thready (vlákna). Co tedy jsou Go rutiny a proč se používají?
Prvně, proč Go nepoužívá vlákna? Go vlákna používá, ale nepoužívá je 1:1 ke Go rutinám. Vlákno, jakkoliv se používají často, je má svoji režii. Režii na úrovni operačního systému (je to OS, kdo vlákna vytváří), režii na úrovni správy (je potřeba vlákna časovat a přepínat) a režii na úrovni paměti a registrů procesoru (je potřeba při přepínání vlákna ukládat prakticky všechny registry procesoru). Jak je zřejmé, pokud bychom chtěli mít tisíce Go rutin, tak by režie OS byla natolik veliká, že by se systém zhroutil.