1Vysvětlete, jak v jazyce Go funguje hodnota nil odlišně pro ukazatele, řezy (slices), mapy, kanály (channels), funkce a rozhraní (interfaces).
V jazyce Go je nil nulovou hodnotou (zero value) pro ukazatele, řezy (slices), mapy, kanály (channels), funkce a rozhraní (interfaces), ale operace nad těmito hodnotami nil se liší podle typu. Ukazatel na nil lze s nil porovnávat, ale jeho dereference vyvolá paniku (panic). Řez s hodnotou nil má délku i kapacitu 0, lze přes něj iterovat a přidávat do něj prvky. Z mapy s hodnotou nil lze číst a iterovat přes ni, ale přiřazení do ní způsobí paniku. Odesílání do kanálu s hodnotou nil nebo přijímání z něj blokuje donekonečna a uzavření takového kanálu vyvolá paniku. Volání funkce s hodnotou nil rovněž způsobí paniku. Rozhraní (interface) je nil pouze tehdy, když nemá žádný dynamický typ ani dynamickou hodnotu; rozhraní obsahující typovou hodnotu nil, například nil ukazatel, samo o sobě nil není.
2Co jsou porovnatelné typy v Go a jak pravidla pro porovnatelnost ovlivňují klíče map, rovnost a omezení generických typů (generics constraints)?
Porovnatelné typy v Go jsou typy, jejichž hodnoty lze porovnávat operátory `==` a `!=`. Základní typy, ukazatele, kanály, rozhraní (interfaces) a struktury nebo pole, jejichž položky či prvky jsou porovnatelné, jsou rovněž porovnatelné; řezy (slices), mapy a funkce porovnatelné nejsou, s výjimkou porovnání s `nil`. Klíče map musí být porovnatelné. Rovnost se řídí pravidly porovnávání daného typu a porovnání rozhraní závisí na dynamických konkrétních hodnotách; pokud porovnávané rozhraní obsahuje neporovnatelnou dynamickou hodnotu, porovnání vyvolá výjimku panic. V generikách umožňuje předdefinované omezení (constraint) `comparable` porovnávat parametry typu pomocí `==`/`!=` a používat je jako klíče v mapách.
3Jak v Go reprezentujeme bajty, runy (runes) a text v kódování UTF-8, a proč se `len(s)` může lišit od počtu viditelných znaků pro uživatele?
V Go je `byte` alias pro `uint8` a představuje jeden surový bajt, zatímco `rune` je alias pro `int32` a představuje kódový bod (code point) v Unicode. Řetězec (`string`) je sekvence bajtů určená pouze pro čtení, obvykle obsahující text v kódování UTF-8, ale může obsahovat libovolné bajty. Funkce `len(s)` vrací počet bajtů, nikoliv run nebo znaků viditelných pro uživatele. Indexování řetězce vrací bajt; iterace přes řetězec dekóduje UTF-8 a vrací indexy bajtů a příslušné runy. Hodnota `len(s)` se může lišit od počtu viditelných znaků, protože UTF-8 může na jeden kódový bod použít více bajtů, a také proto, že jeden znak viditelný pro uživatele se může skládat z několika kódových bodů, jako jsou například kombinující značky (combining marks) nebo sekvence emotikonů (emoji).
4Jak se liší aliasy typů (type aliases) od definovaných typů (defined types) a kdy byste každý z nich použili?
Definovaný typ (defined type), jako například `type UserID int64`, vytváří nový, odlišný typ s `int64` jako jeho podkladovým typem. Nelze jej volně přiřadit k `int64` bez explicitní konverze a může mít své vlastní metody. Alias typu (type alias), jako například `type UserID = int64`, je pouze další název pro stejný typ, takže identita typu a jeho přiřaditelnost jsou zachovány. Definované typy se používají pro modelování domény, typovou bezpečnost a definici metod; aliasy typů používejte převážně pro refaktorování, migraci nebo zachování kompatibility bez nutnosti zavádět nový typ.
5Jak Go zpracovává speciální hodnoty čísel s plovoucí desetinnou čárkou (floating-point) a na jaké nástrahy při porovnávání rovnosti si dát pozor v backendových systémech?
V Go používají typy `float32` a `float64` chování ve stylu standardu IEEE-754, včetně speciálních hodnot, jako je kladné/záporné nekonečno a NaN (Not a Number). Pro `float64` existují pomocné funkce jako `math.Inf`, `math.IsInf`, `math.NaN` a `math.IsNaN`. Hodnota NaN se nerovná ničemu, dokonce ani sama sobě, takže `x == x` je nepravda (false), pokud je `x` rovno NaN. Přesné porovnávání vypočítaných čísel s plovoucí desetinnou čárkou je také riskantní, protože zaokrouhlování a přesnost mohou způsobit, že se matematicky stejné hodnoty budou lišit; používejte tolerance vhodné pro danou doménu nebo se vyhněte použití těchto typů pro přesné obchodní hodnoty, jako jsou peníze. Typy float jsou povoleny jako klíče v mapách (`map`), ale klíče s hodnotou NaN jsou problematické, protože vyhledávání v mapě závisí na porovnání rovnosti a NaN se nerovná ničemu, dokonce ani samo sobě.
6Popište vnořování struktur (struct embedding) v jazyce Go a jak se chovají povýšená pole a metody (promoted fields and methods).
Vnořování struktur (struct embedding) v jazyce Go znamená deklaraci pole pouze pomocí jeho typu bez explicitního názvu pole, například `type User struct { Person }`. Vnořená hodnota je stále skutečným polem, ke kterému lze přistupovat jako u.Person, ale její exportovaná nebo přístupná pole a metody mohou být povýšeny (promoted). Volající pak mohou zapisovat selektory jako u.Name nebo u.Greet() jako zkrácený zápis pro přístup skrze vnořené pole. Vnořování představuje kompozici, nikoliv klasickou dědičnost: vnější typ se nestává automaticky podtypem vnořeného typu. Pokud jsou povýšené selektory v konfliktu, jazyk Go nehádá; nejednoznačné názvy musí být plně kvalifikovány, jinak je nelze přes vnější hodnotu vybrat.
7Popište, jak může opětovné vytvoření řezu (reslicing) a přiřazení způsobit, že více řezů bude sdílet stejné podkladové pole, a jaké chyby to může způsobit.
Hodnota řezu je hlavička ukazující do podkladového pole. Přiřazení řezu nebo jeho předání funkci zkopíruje pouze tuto hlavičku, nikoli samotné prvky. Opětovné vytvoření řezu (reslicing) vytvoří další hlavičku ukazující na rozsah stejného podkladového pole. Proto může více řezů odkazovat na stejné úložiště: změna prvku prostřednictvím jednoho řezu může být viditelná v jiném a použití funkce `append` na jeden řez může přepsat data viditelná pro jiný řez, pokud má stále volnou kapacitu. Mezi chyby patří neočekávané mutace, poškozené výsledky, zadržování velkých podkladových polí v paměti kvůli malým podřízeným řezům (subslices) a souběhy dat (data races), když jsou aliasy používány souběžně. Chcete-li se vyhnout nechtěnému sdílení, vytvořte defenzivní kopii pomocí funkce `copy` nebo `append([]T(nil), s...)`, případně omezte kapacitu pomocí výrazu pro úplný řez (full-slice expression) před voláním `append`.
8Vysvětlete koncept růstu řezu (slice) během operace append a výkonnostní důsledky opakované realokace.
Když `append` přidává prvky do řezu (slice), zapisuje do existujícího podkladového pole, pokud má řez dostatečnou kapacitu. Pokud je kapacita nedostatečná, Go alokuje větší podkladové pole, zkopíruje stávající prvky, zapíše nové prvky a vrátí hlavičku řezu (slice header) ukazující na nové úložiště. Přesná strategie růstu závisí na implementaci, ale koncepčně roste kapacita natolik, aby byly opakované operace `append` z hlediska amortizované složitosti efektivní. Opakované realokace stále spotřebovávají čas procesoru na kopírování, vytvářejí alokace, zvyšují zátěž GC (Garbage Collector) a mohou rozbít sdílení se starými aliasy řezu. Pokud znáte očekávanou velikost, předem proveďte alokaci pomocí `make([]T, 0, n)` při budování pomocí `append` nebo `make([]T, n)` při plnění pomocí indexu, abyste omezili realokace.
9Co jsou vzory registrace v době inicializace (init-time registration) v Go a jaká rizika plynou z vedlejších účinků prázdných importů (blank imports) a globálních registrů?
Vzor registrace v době inicializace (init-time registration) je situace, kdy balíček zaregistruje implementaci do sdíleného registru pomocí funkce `init`. Prázdný import (blank import), jako například `_ "example.com/driver"`, se často používá k importu balíčku pouze kvůli jeho vedlejším účinkům, což způsobí spuštění jeho funkce `init`, i když z něj nejsou volány žádné exportované názvy. Tento postup je běžný pro body rozšíření, jako jsou ovladače, kodeky, doplňky (plugins), metriky nebo serializátory. Riziky jsou skryté závislosti a vedlejší účinky při startu aplikace, globální měnitelný stav, duplicitní nebo na pořadí závislá registrace, obtížnější izolace testů a méně explicitní propojení závislostí. Tento přístup by se měl používat uvážlivě, měl by být jasně dokumentován a rizika se často zmírňují pomocí explicitní registrace, idempotentních registrů bezpečných pro souběžnost (concurrency-safe) nebo registrů, které lze pro testy snadno resetovat a vkládat jako závislosti.
10Jak na sebe vzájemně působí příkazy `defer`, `panic` a pojmenované návratové hodnoty při implementaci úklidových operací, které mohou měnit vracené chyby?
Odložené funkce v bloku `defer` se spouštějí poté, co byly přiřazeny návratové hodnoty, ale ještě předtím, než se funkce vrátí svému volajícímu. Díky tomu může odložený uzávěr (deferred closure) číst nebo upravovat pojmenované návratové hodnoty, jako je například pojmenovaná proměnná `err`. To se běžně používá k přidání chyb vzniklých při úklidu u operací jako `Close`, `Commit` a podobně do vracené chyby, přičemž v ideálním případě by měla být primární chyba zachována a ne jen jednoduše přepsána. Během rozvíjení zásobníku z důvodu kritické chyby (panic unwinding) se odložené funkce stále spouštějí; odložená funkce se může z této chyby zotavit pomocí `recover` a nastavit pojmenovanou návratovou hodnotu, to by však mělo být vyhrazeno pouze pro hranice s úmyslným použitím mechanismu `panic`. Dejte si pozor, abyste nezastínili (shadow) pojmenovanou návratovou proměnnou, jako je `err`, protože by pak funkce v bloku `defer` mohla vidět a upravovat jinou proměnnou, než bylo zamýšleno.
11Jak fungují `errors.Is`, `errors.As` a `%w` v řetězcích zabalených chyb (error wrapping chains) v Go?
Funkce `fmt.Errorf` s formátovacím znakem `%w` vytvoří novou chybu, která zabalí (wraps) původní chybu a zároveň přidá kontext. Tyto obálky (wrappers) zpřístupňují původní chyby pomocí metody `Unwrap`, čímž vytvářejí řetězec nebo strom, který může standardní knihovna prozkoumat. Funkce `errors.Is(err, target)` kontroluje, zda se `err` nebo jakákoli chyba, kterou zabaluje, shoduje s cílovou chybou (`target`). Funkce `errors.As(err, &target)` kontroluje, zda `err` nebo jakákoli chyba v řetězci je přiřaditelná k cílovému typu, a pokud ano, uloží odpovídající hodnotu do poskytnutého ukazatele.
12Co jsou to signální chyby (sentinel errors) a jaké jsou jejich kompromisy ve srovnání s vlastními typovanými chybami nebo bohatšími doménovými chybovými modely?
Signální chyba (sentinel error) je pojmenovaná chybová hodnota, často proměnná na úrovni balíčku, jako je `var ErrNotFound = errors.New("not found")`. Používá se k reprezentaci specifické podmínky, kterou mohou volající testovat, obvykle pomocí `errors.Is`, pokud je povoleno obalování (wrapping) chyb. Signální chyby jsou jednoduché a užitečné pro široké stabilní kategorie, ale exportované signální chyby se stávají součástí API (Application Programming Interface) a mohou volající pevně svázat s konkrétními hodnotami. Vlastní typované chyby mohou nést strukturovaná pole a lze je nalézt pomocí `errors.As`. Bohatší doménové modely chyb klasifikují selhání podle druhu/kódu a mohou zahrnovat bezpečné zprávy nebo metadata, což je užitečné, když volající potřebují stabilní chování přesahující jednu pevnou chybovou hodnotu.
13Jak by měl backend v Go mapovat interní chyby na užitečné odpovědi pro klienta a zároveň poskytovat operátorům diagnostikovatelné signály?
Backend v Go by měl překládat interní chyby na hranici aplikace nebo transportní vrstvy do stabilních kategorií a odpovědí bezpečných pro klienta. Tyto kategorie by se měly mapovat na odpovídající stavové kódy HTTP nebo ekvivalentní stavy transportní vrstvy, a to s bezpečnými zprávami a strojově čitelnými kódy namísto nezpracovaných interních chyb. Operátoři by měli i nadále dostávat diagnostické informace prostřednictvím strukturovaných logů, trasování, metrik, korelačních ID nebo ID požadavků a zachovaných původních příčin. Logování je obvykle nejlepší provádět pouze jednou na hranici, která má kontext požadavku, aby se předešlo opomenutí chyb a zároveň zahlcení duplicitními logy.
14Jak správně implementovat vlastní typy chyb (custom error types) v Go a jak ovlivňuje `errors.Join` kontrolu chyb a jejich zpracování při úklidu zdrojů (cleanup-path)?
Vlastní typ chyby v Go splňuje rozhraní `error` tím, že implementuje metodu `Error() string`. Může také obsahovat strukturovaná pole a implementovat metodu `Unwrap() error`, která odhalí původní příčinu. Důležitý je rozdíl mezi pointer a value receivery: pointer receiver znamená, že rozhraní `error` splňuje pouze `*T`, zatímco value receiver obvykle znamená, že ho splňuje `T` i `*T`. To ovlivňuje kopírování a typ, který by měli volající použít u `errors.As`. Funkce `errors.Join` spojuje více chyb do jedné; `errors.Is` a `errors.As` pak kontrolují tyto spojené vnořené chyby. To je užitečné, když je potřeba vrátit chybu z primární operace i chybu z úklidu/odloženého spuštění (deferred), aniž by se jedno ze selhání ztratilo.
15Jak funguje konstrukce select s kanály (channels), včetně výběru připravené větve (ready-case choice), výchozí větve (default case) a zrušitelných operací?
Konstrukce select čeká na více operací s kanály a provede tu větev (case), u které může proběhnout odeslání nebo přijetí. Pokud není připraven žádný kanál, zablokuje vlákno, dokud se nenabídne výchozí větev (default case); výchozí větev se spustí okamžitě, ale pouze v případě, kdy nelze provést žádnou jinou operaci na kanálu. To je užitečné pro neblokující pokusy o odeslání nebo přijetí. Pokud je připraveno více větví, Go vybere jednu pseudonáhodně, nikoliv podle pořadí ve zdrojovém kódu. Zrušitelné operace na kanálech běžně přidávají větev, která přijímá data z ctx.Done(), takže goroutine může přestat čekat, když je kontext zrušen nebo vyprší jeho časový limit (timeout).