1Vysvětlete, jak fungují nulové hodnoty (zero values) v jazyce Go pro vestavěné a referenční typy a proč jsou důležité při deklaraci proměnných bez explicitní inicializace.
V jazyce Go je proměnná deklarovaná bez explicitního inicializátoru automaticky inicializována na nulovou hodnotu (zero value) svého typu. Číselné typy se nastaví na 0, bool na false, string na "" a pole nebo struktury (structs) jsou vynulovány prvek po prvku nebo položka po položce. Typy fungující jako ukazatele nebo reference, jako jsou ukazatele, řezy (slices), mapy, kanály (channels), funkce a rozhraní (interfaces), mají jako svou nulovou hodnotu nil. Je to důležité, protože proměnné v Go a vynechané položky struktur začínají v deterministickém stavu, místo aby obsahovaly nesmyslná data v paměti (garbage). Mnoho API (Application Programming Interfaces) je navíc navrženo tak, že nulová hodnota představuje užitečnou výchozí hodnotu, ačkoli některé hodnoty nil stále vyžadují před určitými operacemi dodatečnou inicializaci.
2Jak Go řeší rovnost u struktur a co se stane, když struktura obsahuje neporovnatelné položky?
Hodnoty struktur v Go lze porovnávat operátory `==` a `!=` pouze tehdy, když je každá položka struktury porovnatelná. Rovnost porovnává odpovídající položky pomocí jejich vlastních pravidel pro rovnost. Pokud struktura obsahuje neporovnatelnou položku, jako je řez (slice), mapa nebo funkce, typ struktury není porovnatelný a porovnání dvou hodnot tohoto typu operátorem `==` vede k chybě při překladu (compile-time error). Pro takové struktury použijte vlastní logiku porovnávání nebo vhodnou pomocnou funkci pro hluboké porovnání (deep-equality), zejména v testech.
3Vysvětlete neměnnost (immutability) řetězců v Go a vztah mezi `string`, `[]byte`, `bytes.Buffer` a `strings.Builder`.
Řetězec (`string`) v Go je neměnná sekvence bajtů, často text v kódování UTF-8, i když to není podmínkou. Řetězec nelze měnit na místě (in-place); pro změnu obsahu jej obvykle převedete na `[]byte` pro úpravy na úrovni bajtů nebo na `[]rune` pro úpravy kódových bodů (code points) a následně jej převedete zpět. Běžné převody mezi `string` a `[]byte` kopírují data a mohou alokovat paměť, takže opakované převody nebo zřetězování ve smyčkách mohou být výkonnostně náročné. Typ `strings.Builder` je optimalizován pro efektivní sestavování řetězců, zatímco `bytes.Buffer` je měnitelný bajtový buffer vhodný pro bajtově orientovaná data a I/O operace, který také dokáže vygenerovat řetězec.
4Jak fungují ukazatele v Go a jaké operace jsou ve srovnání s jazykem C záměrně zakázány?
Ukazatel v Go je typovaná hodnota, která uchovává adresu jiné hodnoty, například `*int` ukazující na `int`. Použijte `&x` pro získání adresy hodnoty a `*p` k dereferencování ukazatele pro čtení nebo zápis odkazované hodnoty. Předání nebo uložení ukazatele umožňuje více místům sledovat nebo měnit stejnou podkladovou hodnotu, přičemž ukazatel může mít hodnotu `nil`. Na rozdíl od C bezpečné Go záměrně neumožňuje aritmetiku ukazatelů a libovolnou manipulaci se surovými adresami; nízkoúrovňové výjimky vyžadují balíček `unsafe`.
5Vysvětlete, jak fungují konstanty a `iota` v Go, včetně rozdílu mezi typovanými a netypovanými (untyped) konstantami.
Konstanty v Go jsou hodnoty vyhodnocované v době překladu (compile-time): logické (boolean), řetězce nebo číselné. Typovaná konstanta má specifický typ. Netypovaná konstanta nemá pevný konkrétní typ, dokud není použita v typovaném kontextu. Číselné netypované konstanty jsou do té doby reprezentovány přesně s vysokou přesností; hodnota však musí být reprezentovatelná ve zvoleném typu. `iota` je předdeklarovaný identifikátor používaný v deklaracích `const`: začíná na 0 v každém bloku `const` a inkrementuje se pro každou specifikaci konstanty, což jej činí užitečným pro konstanty podobné výčtovým typům (enum) a pro bitové příznaky.
6Jak fungují explicitní konverze v Go a proč se tento jazyk vyhýbá mnoha implicitním konverzím?
Go obecně vyžaduje při změně typu hodnoty explicitní konverze pomocí `T(x)`, jako například `int64(i)` nebo `MyID(n)`. Díky tomu jsou změny typů viditelné a předchází se tím překvapivým implicitním konverzím čísel, booleovských hodnot nebo řetězců. Konverze jsou povoleny pouze podle pravidel pro konverze v Go a mohou změnit hodnotu, například při přetečení či oříznutí celého čísla, oříznutí desetinného čísla na celé číslo směrem k nule nebo při ztrátě přesnosti. Netypované konstanty jsou flexibilnější: lze je použít v typovaném kontextu, pokud je hodnota konstanty v daném typu reprezentovatelná.
7Popište rozdíl mezi poli (arrays) a řezy (slices) v Go, včetně toho, jak se chová jejich délka, kapacita a podkladové úložiště.
Pole (array) má v Go pevnou délku, která je součástí jeho typu, například `[3]int`; ukládá své prvky přímo a přiřazení nebo předání pole zkopíruje celou jeho hodnotu. Řez (slice), například `[]int`, je malý deskriptor nad podkladovým polem: koncepčně obsahuje ukazatel na prvky, délku a kapacitu. Délka řezu je počet viditelných prvků; jeho kapacita určuje, kolik prvků lze použít od začátku řezu, než se dosáhne konce podkladového pole. Řezy jsou flexibilní: opětovné vytvoření řezu (reslicing) změní deskriptor a funkce `append` může znovu použít stejné podkladové pole, pokud to kapacita dovoluje, nebo alokovat nové, pokud ne.
8Jak se chová typ map v jazyce Go, pokud jde o typy klíčů, chybějící klíče, mapy s hodnotou nil a pořadí iterace?
Typy klíčů mapy v jazyce Go musí být porovnatelné (comparable); řezy (slices), mapy a funkce nelze použít přímo jako klíče. Vyhledání klíče, který není přítomen, vrací nulovou hodnotu daného typu prvku (zero value), proto se používá forma comma-ok (`v, ok := m[k]`) k rozlišení nepřítomnosti od přítomné nulové hodnoty. Z mapy s hodnotou `nil` lze číst a lze přes ni iterovat pomocí `range`, ale přiřazení do ní vyvolá chybu `panic`; před zápisem ji proto inicializujte. Pořadí iterace mapy není specifikováno a kód na něm nesmí záviset.
9Kdy byste měli v Go předávat strukturu (`struct`) nebo jinou hodnotu hodnotou a kdy pomocí ukazatele?
Předávejte hodnotou, když je hodnota malá, pro volání pouze pro čtení, nebo když chcete sémantiku nezávislé kopie. Předávejte pomocí ukazatele, když funkce musí modifikovat hodnotu volajícího, kopírování by bylo drahé, typ by se neměl kopírovat, nebo když je hodnota `nil` či sdílená identita součástí API (Application Programming Interface). Nepředpokládejte, že ukazatele jsou vždy rychlejší: mohou přidat aliasing (vícenásobné odkazování), úniky na haldu (heap escapes), více práce pro GC (Garbage Collector) a přístup méně optimální pro mezipaměť (cache). Upřednostňujte nejprve jasnou sémantiku a pro volby kritické z hlediska výkonu proveďte výkonnostní testy (benchmarks).
10Jak se v jazyce Go chová přiřazení a kopírování pro struktury (structs), pole (arrays), mapy a řezy (slices)?
Přiřazení v jazyce Go kopíruje přiřazovanou hodnotu, ale výsledný efekt závisí na daném typu. Přiřazení struktury zkopíruje její pole a přiřazení pole zkopíruje každý jeho prvek. Přiřazení řezu zkopíruje hlavičku řezu – ukazatel, délku a kapacitu – takže řezy obvykle sdílejí stejné podkladové pole (backing array). Přiřazení mapy zkopíruje deskriptor mapy, který se chová jako reference, takže obě proměnné odkazují na stejná data mapy. Pro nezávislé vlastnictví vytvořte defenzivní kopii, například pomocí funkce copy nebo slices.Clone pro řezy, a vytvořením nové mapy a zkopírováním jednotlivých záznamů pro mapy.
11Jak v Go uzávěry (closures) zachytávají proměnné a jaké chyby mohou vzniknout v souvislosti s cykly a goroutines?
Uzávěr (closure) v Go je hodnota funkce, která odkazuje na proměnné ze svého obklopujícího lexikálního rozsahu. Zachytává samotné proměnné, takže případné změny (mutations) jsou v uzávěru viditelné, a pokud uzávěr unikne (escapes) mimo svůj původní kontext, mohou zachycené proměnné přežít samotnou funkci, která je vytvořila. Chyby u cyklů a goroutines nastávají, když uzávěry sdílejí nebo sledují proměnnou poté, co se její hodnota změnila. To často vede k tomu, že goroutines nebo zpožděná zpětná volání (callbacks) vidí nechtěnou hodnotu, případně to způsobuje souběhy dat (data races) nad sdíleným stavem. Obvyklým řešením je předat zamýšlenou hodnotu uzávěru jako argument, nebo vytvořit novou lokální kopii pro každou iteraci a v případě potřeby synchronizovat souběžný přístup. Od verze Go 1.22 jsou proměnné cyklu deklarované pomocí `for`/`range` platné pouze pro danou iteraci, což odstraňuje mnoho klasických chyb spojených se zachytáváním proměnných v cyklu. Znovu použité proměnné mimo cyklus a další sdílený měnitelný stav (shared mutable state) však stále mohou způsobovat problémy.
12Popište sémantiku konstrukce range při iteraci přes pole, řezy (slices), mapy, řetězce a kanály (channels).
Konstrukce `range` iteruje v závislosti na typu operandu. U polí a řezů (slices) vrací index a hodnotu prvku; hodnota prvku je kopie, takže přiřazení do ní neupraví samotnou kolekci. Iterace přes hodnotu pole zkopíruje toto pole pro účely iterace, zatímco iterace přes řez využívá hlavičku řezu a indexuje podkladové pole. U mapy vrací klíč a hodnotu v neurčeném pořadí, přičemž hodnota je opět kopie. U řetězce vrací bajtový index a dekódovaný znak Unicode (typ `rune`), nikoliv index pozice samotného znaku. U kanálu (channel) přijímá hodnoty, dokud není kanál uzavřen a vyprázdněn; volání range na nil kanálu zablokuje vlákno natrvalo.
13Jaký je rozdíl mezi příjemci předávanými hodnotou (value receivers) a příjemci předávanými ukazatelem (pointer receivers), a jak volba příjemce ovlivňuje mutabilitu, kopírování a naplnění rozhraní (interface satisfaction)?
Metoda s příjemcem předávaným hodnotou obdrží kopii příjemce, takže je vhodná pro operace pouze pro čtení a malé typy s chováním nezměnitelného stavu (immutable-style); změny v kopii příjemce nejsou pro volajícího viditelné. Metoda s příjemcem předávaným ukazatelem obdrží kopii ukazatele na příjemce, takže může upravovat originál a vyhýbá se kopírování velkých hodnot nebo hodnot, které by se kopírovat neměly. Volba příjemce ovlivňuje sady metod (`method set`): metody s příjemcem hodnoty jsou v `method set` jak typu `T`, tak i `*T`, zatímco metody s příjemcem ukazatele jsou pouze v `method set` typu `*T`. Z tohoto důvodu je rozhraní, které vyžaduje metodu s příjemcem ukazatele, naplněno (satisfied) pouze typem `*T`, nikoli `T`, ačkoli adresovatelná hodnota `T` může tuto metodu zavolat pomocí běžné syntaxe pro volání metod.
14Popište, jak fungují rozhraní v jazyce Go a co to znamená, když typ splňuje rozhraní implicitně.
Rozhraní v jazyce Go definuje sadu požadovaných metod. Konkrétní typ splňuje rozhraní implicitně, pokud jeho množina metod (method set) obsahuje tyto požadované metody; neexistuje zde žádná explicitní deklarace implementace. Proměnná typu rozhraní může obsahovat konkrétní dynamickou hodnotu, jejíž typ toto rozhraní splňuje. Volání metod na rozhraní se pak přesměruje (dispatch) na implementaci odpovídající této dynamické hodnotě. Prázdné rozhraní, zapisované jako `interface{}` nebo častěji jako `any`, nemá žádné požadované metody, takže ho splňuje každý typ. Zda rozhraní splňuje typ `T`, ukazatel `*T` nebo oba, závisí na jejich množinách metod a na definici zvolených přijímačů (receivers).
15Jak fungují typové aserce (type assertions) a přepínače typů (type switches) s hodnotami rozhraní?
Typová aserce (type assertion) kontroluje dynamickou hodnotu uloženou v rozhraní: výraz `v := x.(T)` uspěje, pokud je dynamický typ hodnoty rozhraní `T`, nebo pokud je `T` rozhraní implementované touto dynamickou hodnotou. Forma s jedním výsledkem vyvolá paniku (panic), pokud aserce selže. Forma s čárkou a návratovou hodnotou, `v, ok := x.(T)`, oznamuje úspěch bez vyvolání paniky. Přepínač typů (type switch), zapsaný jako `switch v := x.(type)`, větví tok programu na základě dynamického typu hodnoty rozhraní, přičemž proměnná `v` získává typ odpovídající větvi `case`, která se shoduje.