V perexu dnešního článku je zmíněno dilema resp. problém, který se dotýká prakticky všech existujících programovacích jazyků. Lidé, kteří tyto jazyky vyvíjí nebo navrhují jejich vývoj (což mohou být jednotlivci, komunita, nebo i celé více či méně oficiální komise), se musí prakticky s každou navrhovanou změnou rozhodovat, jestli má dojít k rozšíření programovacího jazyka takovým způsobem, jenž způsobí nekompatibilitu se staršími verzemi, nebo se naopak všechny změny budou navrhovat (nebo spíše zavrhovat) takovým způsobem, že dotčený programovací jazyk bude „zakonzervován“ a vlastně se nebude dále vyvíjet.
Co se dozvíte v článku
- Kompatibilita zdrojových kódů v různých programovacích jazycích
- Jak je tomu v jazyce Go?
- Kompatibilita z pohledu sémantiky
- Kontroly prováděné překladačem jazyka Go
- Chování při spuštění příkazem go run
- Změna sémantiky programové smyčky for
- Při návrhu jazyka jsme udělali chybu – a co teď?
- Otestování chování v režimu kompatibility s Go 1.20 a Go 1.22
- Podobný příklad, ovšem v upravené podobě
- Otestování chování v režimu kompatibility s Go 1.20 a Go 1.22
- Lze sestavit aplikaci z modulů určených pro odlišné verze jazyka Go?
- Obsah modulu module1
- Obsah modulu module2
- Obsah hlavního modulu main
- Chování aplikace po překladu a spuštění
- Závěr
- Repositář s demonstračními příklady
- Odkazy na Internetu
V určité míře se tento problém týká i knihoven, ovšem palčivější je právě u syntaxe a sémantiky popisující a mnohdy i definující programovací jazyk. Pouze malé množství v praxi používaných jazyků je totiž rigorózně definováno, takže mnohdy se za „definici“ jazyka považuje jeho referenční implementace, která je typicky (v těch lepších případech) doplněna seznamem nedefinovaných chování.
Kompatibilita zdrojových kódů v různých programovacích jazycích
V praxi můžeme vidět různé přístupy k řešení tohoto problému. Do některých programovacích jazyků se přidávají nové sémantické konstrukce bez toho, aby bylo nutné přidávat nová klíčová slova. Výsledek sice nemusí být ideální, ale na druhou stranu umožňuje další vývoj programovacího jazyka (a lze tvrdit, i když poněkud s nadsázkou, že jazyk, který se nevyvíjí, je mrtvým jazykem).
U některých dalších jazyků se postupně přidávají nová klíčová slova, což může způsobit problémy v případě, že tato slova byla použita ve starších zdrojových kódech jako běžné identifikátory. Příkladem může být Java, do které byla postupně přidávána další klíčová slova:
| Slovo | Přidáno ve verzi |
|---|---|
| strictfp | 1.2 |
| assert | 1.4 |
| enum | 1.5 |
Příkladem dalšího programovacího jazyka, do kterého byly zařazeny změny, které způsobí problémy se staršími zdrojovými kódy, je slavné céčko. Následující zdrojový kód je bez problémů přeložitelný překladači ANSI C, ovšem překladače rozeznávající C99 (a vyšší verze) ohlásí chybu, protože v C99 je inline definováno jako (nové) klíčové slovo:
int main(void) {
int inline = 0;
return inline;
}
Mimochodem, v rámci normy C11 bylo přidáno sedm podobných slov (i když s podtržítkem na začátku), v C23 přibylo dokonce patnáct nových klíčových slov. Výsledkem je situace, ve které následující zdrojový kód bude přeložen jen překladačem pracujícím v režimu ANSI C (a teoreticky v žádném jiném překladači). Novější překladače (resp. ten samý překladač po přepnutí do vyšší verze jazyka) budou hlásit buď jednu nebo větší množství chyb (v závislosti na použité verzi jazyka!), a K&R překladače budou mít naopak problém jak se syntaxí, tak i s rezervovaným slovem entry (které už však v novějších verzích céčka rezervovaným slovem pro změnu není; ostatně v praxi snad ani nikdy nebylo implementováno):
int main(void) {
int inline = 0;
int alignof = 1;
int entry = 2;
return inline * alignof + entry;
}
Obrázek 1: Výňatek z původního manuálu k jazyku C, v němž je zmíněno i slovo "entry".
Jen pro zajímavost si zkusme tento zdrojový kód skutečně přeložit pro různé verze céčka:
$ gcc -std=c89 riddle.c
(výborně, přeloženo bez problémů)
$ gcc -std=c99 riddle.c
riddle.c: In function ‘main’:
riddle.c:2:16: error: expected identifier or ‘(’ before ‘=’ token
2 | int inline = 0;
| ^
riddle.c:5:12: error: expected expression before ‘inline’
5 | return inline * alignof + entry;
| ^~~~~~
(dvě chyby)
$ gcc -std=c23 riddle.c
riddle.c: In function ‘main’:
riddle.c:2:16: error: expected identifier or ‘(’ before ‘=’ token
2 | int inline = 0;
| ^
riddle.c:3:9: error: expected identifier or ‘(’ before ‘alignof’
3 | int alignof = 1;
| ^~~~~~~
riddle.c:5:12: error: expected expression before ‘inline’
5 | return inline * alignof + entry;
| ^~~~~~
(pro změnu tři chyby)
To ovšem není jen specifická vlastnost GCC, protože podobně se chová Clang:
$ clang -ansi riddle.c
$ clang -std=c99 riddle.c
riddle.c:2:16: error: expected identifier or '('
2 | int inline = 0;
| ^
riddle.c:5:12: error: expected expression
5 | return inline * alignof + entry;
| ^
2 errors generated.
$ clang -std=c23 riddle.c
riddle.c:2:16: error: expected identifier or '('
2 | int inline = 0;
| ^
riddle.c:3:9: error: expected identifier or '('
3 | int alignof = 1;
| ^
riddle.c:5:12: error: expected expression
5 | return inline * alignof + entry;
| ^
3 errors generated.
Zajímavý je přístup použitý v jazyce Python. Do tohoto programovacího jazyka totiž byla přidána podpora pro pattern matching, která vyžadovala přidání nových klíčových slov, konkrétně slov match a case. A kromě toho byla do Pythonu přidána i podpora pro definici nových datových typů, která si vyžádala přidání klíčového slova type. Ovšem označení těchto slov za rezervovaná klíčová slova je problematické, protože by to znamenalo, že starší zdrojové kódy nebudou kompatibilní s novou verzí interpretrů Pythonu (v praxi se to asi nejvíce týká slova type, protože si dovedu představit, že by bylo použito pro pojmenování parametru funkce či dokonce jako jméno metody).
Z tohoto důvodu autoři Pythonu zvolili (zlatou?) střední cestu – slova match, case a type jsou označena termínem soft keywords. Starší zdrojové kódy mohou tato slova používat jako identifikátory, ovšem v novém kódu již budou použity jako (rezervovaná) klíčová slova. Rozlišení je dosti komplikované a je proto prováděno na úrovni parseru, tedy nikoli (jak by bylo obvyklé) už při tokenizaci zdrojového kódu – to je sice z pohledu návrhu interpretru nepěkné řešení, ovšem z pohledu programátora je asi nejpraktičtější.
To tedy znamená, že následující skripty jsou oba korektní, i když jeden z nich používá type jako jméno proměnné a druhý jako klíčové slovo:
type = "foo" print(type)
a:
type text = str x: text = "foo" print(x)
A aby toho nebylo málo:
type text = str x: text = "foo" print(x) print(type) type = "bar" print(type)
Problematika Python 2 vs Python 3 je dostatečně známá a snad se již podobná situace nebude opakovat…
Jak je tomu v jazyce Go?
Tvůrci programovacího jazyka Go jsou (nebo vlastně ještě relativně nedávno byli) poměrně konzervativní v otázce přidávání nových vlastností do tohoto jazyka. Konkrétně se například dlouho (několik let) řešilo, jakým způsobem se do Go přidá podpora pro generické datové typy, přidání podpory pro práci s výjimkami je téměř nekonečné téma (neustále odsouvané z dobrých i méně dobrých důvodů) apod. Podobně konzervativní je vývoj a změny prováděné ve standardní knihovně jazyka Go, která je poměrně rozsáhlá a nutno říci, že i kvalitní (což je v poměrně ostrém kontrastu s mnoha balíčky třetích stran). Nicméně i tento programovací jazyk se postupně vyvíjí a jsou do něj přidávány nové vlastnosti – například již zmíněná podpora pro generické datové typy (verze 1.18), možnost zápisu smyčky for range integer (verze 1.22), podpora pro iterátory (verze 1.23), nová sémantika standardní funkce new (verze 1.26) apod. Na tomto místě je ovšem dobré poznamenat, že mnohé změny jsou spíše kosmetické.
Mimochodem: následujících 25 klíčových slov bylo rezervováno již v Go 1.0 před šestnácti lety. Od té doby nedošlo k přidání žádného nového slova, i když se mezitím jazyk rozšířil (a byl přidán například znak ~ související s generickými typy):
break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var
Nové změny přidávané do Go jsou navrženy takovým způsobem, aby syntaxi jazyka rozšiřovaly, ale neměnily, takže i starší zdrojové kódy by měly být bez problémů přeložitelné i nejnovější verzí překladače Go. To si ostatně můžete relativně snadno otestovat, protože jsou k dispozici (například na GitHubu) archivy starých zdrojových kódů určených například pro Go 1.8 atd. Pokud už muselo dojít k takové změně ve specifikaci jazyka, která (obecně) není kompatibilní, jsou nové překladače Go vybaveny logikou, která zdrojové kódy přeloží kompatibilním způsobem. Tedy například zdrojové kódy balíčku určeného pro verzi 1.19 mohou být přeloženy jinak, než ty samé zdrojové kódy, ovšem určené pro verzi 1.26.
Kompatibilita z pohledu sémantiky
V případě, že se do programovacího jazyka přidává nějaká nová vlastnost, většinou to znamená změnu či spíše rozšíření syntaxe a taktéž sémantiky. Taková změna je většinou pro vývojáře snadno „uchopitelná“, protože každý chápe, že nová jazyková konstrukce má i svou (novou) sémantiku. Ovšem některé změny nemusí být na první pohled viditelné. Příkladem může být přesnější specifikace chování bez toho, aby došlo k modifikaci syntaxe. V Go 1.21 se například změnil způsob pořadí inicializace balíčků, což mj. ovlivňuje chování aplikací v případě, že balíčky mají definovanou funkci init. Dalším příkladem může být změna chování nějaké jazykové konstrukce – tj. i když se bude používat stejný způsob zápisu (tedy stejná syntaxe), bude chování v novější verzi jazyka jiné (odlišná sémantika). To je ovšem potenciálně velmi problematické, protože to znamená, že chování nějaké aplikace závisí na tom, jaká verze překladače bude použita.
Taková změna v jazyce Go skutečně (nutno dodat, že bohužel) nastala, a to konkrétně ve verzi 1.22. Jedná se o odlišné chování proměnných definovaných v programové smyčce for. A právě existenci této nekompatibilní změny využijeme v dnešním článku, abychom si ukázali, jakým způsobem jsou nekompatibility tohoto typu v jazyce Go řešeny. Můžeme dopředu prozradit, že je tento problém uchopen poměrně dobrým způsobem navrženým tak, aby co nejméně zatěžovat vývojáře a aby navíc umožnil kombinaci balíčků a knihoven vytvořených pro různé verze jazyka Go.
Kontroly prováděné překladačem jazyka Go
Překladač programovacího jazyka Go se při překladu zdrojových kódů ohlíží i na verzi jazyka specifikovanou vývojářem v souboru go.mod. Ukažme si to na prozatím velmi jednoduchém demonstračním příkladu, ve kterém jsou definovány dvě proměnné, a to konkrétně řez (slice) s a pole (array) a. Při inicializaci pole a se používá obsah řezu s, díky čemuž je možné pole inicializovat bez použití programové smyčky:
package main
import "fmt"
func main() {
s := []int{1, 2, 3}
a := [3]int(s)
a[0] = 9
fmt.Println("slice:", s)
fmt.Println("array: ", a)
}
Tento zdrojový kód je velmi dobře pochopitelný, ovšem ve skutečnosti je platný (resp. bychom mohli říci korektní) až od verze Go 1.20. Právě v této verzi jazyka Go totiž byla rozšířena sémantika konverzních operací. Zde konkrétně se jedná o novou konverzi z řezu na pole (viz též https://go.dev/doc/go1.20#language s podrobnostmi o vydání). Ověřme si tedy, jakým způsobem bude tento program přeložen v případě, že výše uvedený zdrojový kód je součástí projektu určeného pro verzi 1.19 a verzi 1.22 (libovolná verze vyšší nebo rovna 1.20).
Začneme projektovým souborem go.mod, který má definováno, že má být přeložen Go 1.19 (viz třetí řádek):
module example1 go 1.19
V případě, že se pokusíme o překlad takto nakonfigurovaného modulu, nahlásí překladač jazyka Go chybu, protože se snaží chovat tak, jakoby se jednalo o překladač verze 1.19, i když ve skutečnosti používám Go 1.24. Překladač dokonce přesně napíše, v čem spočívá problém:
$ go build # example2 ./example2.go:7:14: cannot convert s (variable of type []int) to type [3]int: conversion of slice to array requires go1.20 or later (-lang was set to go1.19; check go.mod)
Ve druhém kroku přeložíme naprosto stejný zdrojový kód, který ovšem nyní bude součástí projektu připraveného pro verzi 1.22. Projektový soubor go.mod bude tedy vypadat následovně (opět viz třetí řádek):
module example1 go 1.22
V tomto kontextu bude překlad zdrojového kódu bez problémů dokončen:
$ go build
Samozřejmě si ještě ověříme chování přeloženého programu, abychom si ověřili sémantiku – pole je sice inicializováno obsahem řezu, ale jedná se o odlišné bloky operační paměti. Tudíž se změna obsahu pole nijak „nepropíše“ do obsahu původního řezu:
$ ./example1 slice: [1 2 3] array: [9 2 3]
Chování při spuštění příkazem go run
Chování otestované v předchozí kapitole, tedy kontrola, pro jakou verzi jazyka Go je modul určen, se projeví v případě, že modul překládáme příkazem go build (výsledkem bude staticky slinkovaný spustitelný soubor). Vyzkoušejme si ovšem chování v případě, že použijeme příkaz go run example1.go nebo go run example2.go:
$ pushd example1 ~/src/go-root/compatibility/example1 ~/src/go-root/compatibility $ go run example1.go slice: [1 2 3] array: [9 2 3] $ popd ~/src/go-root/compatibility $ pushd example2 ~/src/go-root/compatibility/example2 ~/src/go-root/compatibility $ go run example2.go slice: [1 2 3] array: [9 2 3]
I když se na pozadí stále provádí překlad (typicky do adresáře /tmp pokud nenastavíte jinak), bude se překládat a spouštět pouze jeden zdrojový soubor a konfigurace modulu zapsaná do souboru go.mod bude ignorována. I toto chování může vývojářům přinést různá (nepěkná) překvapení.
Změna sémantiky programové smyčky for
Jak jsme si již naznačili v úvodních kapitolách, došlo – a to přes snahu autorů Go o velkou stabilitu – v Go 1.21 (oficiálně pak v Go 1.22) ke změně chování, tedy ke změně sémantiky jazyka. Tato změna chování se týká vlastnosti proměnných definovaných a naplňovaných při opakování programové smyčky for – tyto proměnné se někdy nazývají čítače smyčky, i když ve skutečnosti mohou být naplňovány obsahem řezu či pole. V případě, že je smyčka prováděna sekvenčně (v jediné gorutině) a navíc nebude obsah takové proměnné použit v uzávěru, bude se programová smyčka chovat přesně podle očekávání.
Ukažme si program, na němž se rozdílná změna chování ukáže. V programu postupně do řezu prints vkládáme uzávěry, tj. (anonymní) funkce navázané na proměnnou i, která je použita jako počitadlo smyčky. A ve druhé programové smyčce tyto uzávěry postupně zavoláme, tj. zavolá se fmt.Println(něco):
package main
import "fmt"
func main() {
var prints []func()
for i := 1; i <= 3; i++ {
prints = append(prints, func() { fmt.Println(i) })
}
for _, print := range prints {
print()
}
}
Otázka bude znít – jaké hodnoty se vypíšou po spuštění tohoto programu. Při pohledu na zdrojový kód by se mohlo zdát, že se vždy vypíše:
1 2 3
Ovšem zde je popsáno, že kvůli špatně navržené sémantice starších verzí Go se ve skutečnosti vypíše (dokument se věnuje gorutinám, ovšem platí i pro uzávěry):
4 4 4
Je tomu tak z toho důvodu, že se program přeloží (resp. překládal) takovým způsobem, že sdílí jedinou instanci proměnné i.
Při návrhu jazyka jsme udělali chybu – a co teď?
Z výše uvedeného příkladu je patrné, že si tvůrci Go uvědomili, že při návrhu jazyka zvolili špatnou (či ne zcela dokonalou) sémantiku chování proměnných v programových smyčkách. A stáli před rozhodnutím, které (aspoň jsem si jistý) museli udělat i návrháři dalších programovacích jazyků. Chování, které není ideální, je totiž možné „zakonzervovat“ a tvrdit přitom, že primárním důvodem je snaha o zpětnou kompatibilitu. Nebo se naopak může chování jazyka změnit, což ovšem ovlivní starší zdrojové kódy, které (celkem logicky) očekávaly původní chování. Jak byl tento konkrétní problém vyřešen, už pravděpodobně tušíte – nové verze překladačů obsahují rozhodovací konstrukci, která na základě verze zapsané v souboru go.mod zvolí jednu z variant překladu zdrojových kódů. To tedy znamená, že zdrojové kódy, které počítají s původním chováním, skutečně budou funkční. A nové zdrojové kódy již mohou využívat vylepšenou (či možná přesněji řečeno opravenou) sémantiku.
Otestování chování v režimu kompatibility s Go 1.20 a Go 1.22
Otestujme si nyní, zda a jak se bude lišit výše uvedený demonstrační příklad, pokud bude jeho zdrojový kód uložen v modulu pro verzi Go 1.20 popř. naopak v modulu pro Go 1.22.
Začneme modulem určeným pro Go 1.20. Obsah go.mod je následující:
module example4 go 1.20
Po překladu a spuštění zjistíme, že se na standardní výstup třikrát vypíše hodnota 4, tj. program se chová „postaru“:
$ go build $ ./example4 4 4 4
Druhá varianta příkladu se stejným zdrojovým kódem je určena pro Go 1.22, což je patrné z obsahu souboru go.mod:
module example3 go 1.22
Chování po překladu a spuštění je nyní zcela odlišné:
$ go build $ ./example3 1 2 3
Podobný příklad, ovšem v upravené podobě
Ukažme si ještě jeden (možná poněkud komplikovanější) příklad, jehož chování při překladu a především po spuštění závisí na verzi Go. V tomto příkladu opět vytváříme uzávěr (closure) v programové smyčce, přičemž v prostředí (environment) uzávěru je i proměnná i použitá jako počitadlo smyčky. Uzávěr se vytváří (a ukládá do proměnné f) pouze v první iteraci a poté se ihned zavolá:
package main
import "fmt"
func main() {
var f func()
for i := 0; i < 10; i++ {
if i == 0 {
f = func() { fmt.Print(i) }
}
f()
}
}
Otázkou opět zůstává, jaké hodnoty se vytisknou při spuštění tohoto příkladu. Bude se desetkrát tisknout hodnota i odpovídající podmínce i == 0 nebo se vytiskne vždy aktuální hodnota proměnné i? (tisk „nečeho“ se samozřejmě provede vždy, ovšem samotné volání f může být odlišné).
Otestování chování v režimu kompatibility s Go 1.20 a Go 1.22
Opět si ověříme, jak se bude tento zdrojový kód chovat v případě, že jeho modul je určený pro různé verze jazyka Go.
Modul nazvaný example5 má následující obsah souboru go.mod:
module example5 go 1.22
Naproti tomu modul example6 je určen pro starší verzi Go:
module example6 go 1.20
Chování první varianty ukazuje, že se do f provede přiřazení jen jedenkrát a uzávěr f si zapamatuje aktuální hodnotu i:
$ pushd example5 ~/src/go-root/compatibility/example5 ~/src/go-root/compatibility $ go build $ ./example5 0000000000
Varianta určená pro starší verze jazyka Go se chová odlišně, protože se uzávěru f stále mění jeho prostředí:
$ popd ~/src/go-root/compatibility $ pushd example6 ~/src/go-root/compatibility/example6 ~/src/go-root/compatibility $ go build $ ./example6 0123456789
Lze sestavit aplikaci z modulů určených pro odlišné verze jazyka Go?
Prozatím jsme měli k dispozici dvojici modulů pro různé verze Go, které se překládaly a spouštěly samostatně. Ověřili jsme si, že překladač Go v takovém případě bere v úvahu verzi Go zapsanou v souboru go.mod. Ovšem v praxi je situace mnohem složitější, neboť se aplikace typicky skládá z mnoha modulů (i třetích stran), z nichž každý může být určen pro jinou verzi Go. Lze tedy očekávat situaci, kdy se jeden a ten samý zdrojový kód má v runtime chovat odlišně. Překladač Go resp. celý komplex standardních nástrojů Go je navržen takovým způsobem, aby tento problém řešil: každý modul je překládán ve svém „režimu kompatibility“.
Abychom si chování překladače jazyka Go ověřili, vytvoříme následující souborovou strukturu, do které jsou umístěny tři moduly nazvané main, module1 a module2:
.
├── main
│ ├── go.mod
│ └── main.go
├── module1
│ ├── go.mod
│ └── module1.go
└── module2
├── go.mod
└── module2.go
Obsah modulu module1
Modul, který jsem pro jednoduchost nazval module1, obsahuje pouze dva soubory go.mod a module1.go. V go.mod je (v kontextu dnešního článku) nejdůležitější specifikace verze jazyka Go, konkrétně Go 1.22:
module module1 go 1.22
Zdrojový kód module1.go obsahuje funkci Foo volatelnou (viditelnou) i z jiných modulů, protože začíná velkým písmenem. Tato funkce obsahuje problematické využití řídicí proměnné smyčky:
package module1
import "fmt"
func Foo() {
var f func()
for i := 0; i < 10; i++ {
if i == 0 {
f = func() { fmt.Print(i) }
}
f()
}
}
Obsah modulu module2
Druhý z modulů, který se jmenuje module2, má v souboru go.mod předepsáno, že je určen pro Go 1.20:
module module2 go 1.20
Vlastní zdrojový kód tohoto modulu obsahuje naprosto stejnou funkci Foo, jako modul první, takže jen pro úplnost:
package module2
import "fmt"
func Foo() {
var f func()
for i := 0; i < 10; i++ {
if i == 0 {
f = func() { fmt.Print(i) }
}
f()
}
}
Obsah hlavního modulu main
Nejzajímavější je obsah hlavního modulu nazvaného (nudně) main. Tento modul je určen pro překlad s využitím Go 1.24, ovšem důležitější je definice závislostí na obou výše popsaných modulech module1 a module2. Abych zbytečně nevytvářel umělé repositáře na GitHubu či GitLabu, obsahuje soubor go.mod i řádky s klauzulemi replace, které zajistí „přejmenování“ jmen modulů example.com/module1 a example.com/module2 na lokální moduly uložené v podadresářích module1 a module2 (tento trik se ovšem hodí i v dalších případech, například pro testování, rychlé úpravy modulů bez nutnosti vytvářet oficiální pull requesty atd.):
module main
go 1.24.10
require (
example.com/module1 v1.2.3
example.com/module2 v1.2.3
)
replace example.com/module1 => ../module1
replace example.com/module2 => ../module2
A konečně se podívejme na zdrojový kód uložený v souboru se jménem v main/main.go. V tomto kódu voláme funkci Foo definovanou v modulu module1 a taktéž stejně pojmenovanou funkci, ovšem z modulu module2:
package main
import (
"fmt"
"example.com/module1"
"example.com/module2"
)
func main() {
module1.Foo()
fmt.Println()
module2.Foo()
fmt.Println()
}
Chování aplikace po překladu a spuštění
V dalším kroku provedeme překlad hlavního modulu main. Překladač jazyka Go zajistí, že se přeloží (a slinkují) i oba moduly module1 a module2:
$ cd main/ $ go build $ ls go.mod main main.go
Přesvědčíme se, že jsou ve výsledném binárním souboru main obsaženy obě varianty funkce Foo:
$ go tool nm main | grep Foo 491dc0 T example.com/module1.Foo 491e60 T example.com/module1.Foo.func1 491ec0 T example.com/module2.Foo 491f60 T example.com/module2.Foo.func1
Nyní nastává čas, kdy bude odhalena pravda, tj. jak se budou chovat obě varianty funkce Foo:
$ ./main 0000000000 0123456789
Závěr
Programovací jazyk Go se vyvíjí již druhé desetiletí, což je v IT velmi dlouhá doba. Stále máme k dispozici Go verze 1, ovšem jazyk se postupně rozšiřuje a došlo i k opravě sémantiky, která znamenala porušení zpětné kompatibility. Řešení je vlastně pro ekosystém jazyka Go typické: překladač byl bez větších fanfár a oslavných videí upraven takovým způsobem, aby překlad prováděl podle přání autorů jednotlivých modulů (na pozadí a přitom korektně a dohledatelným způsobem). To je stabilita, kterou mnohé jiné ekosystémy postrádají.
Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root. V případě, že nebudete chtít klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
funkce se smyčkou s proměnnou využitou v uzávěrufunkce se smyčkou s proměnnou využitou v uzávěru
Odkazy na Internetu
- Go Wiki: LoopvarExperiment
https://go.dev/wiki/LoopvarExperiment - Go, Backwards Compatibility, and GODEBUG
https://tip.golang.org/doc/godebug - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - All releases
https://go.dev/dl/ - Release History
https://go.dev/doc/devel/release - go1.21.0 (released 2023–08–08)
https://go.dev/doc/devel/release#go1.21.0 - go1.22.0 (released 2024–02–06)
https://go.dev/doc/devel/release#go1.22.0 - go1.23.0 (released 2024–08–13)
https://go.dev/doc/devel/release#go1.23.0 - go1.24.0 (released 2025–02–11)
https://go.dev/doc/devel/release#go1.24.0 - go1.25.0 (released 2025–08–12)
https://go.dev/doc/devel/release#go1.25.0 - go1.26.0 (released 2026–02–10)
https://go.dev/doc/devel/release#go1.26.0 - Go 1.19 Release Notes
https://go.dev/doc/go1.19 - Go 1.20 Release Notes
https://go.dev/doc/go1.20 - Go 1.21 Release Notes
https://go.dev/doc/go1.21 - Go 1.22 Release Notes
https://go.dev/doc/go1.22 - Go 1.23 Release Notes
https://go.dev/doc/go1.23 - Go 1.24 Release Notes
https://go.dev/doc/go1.24 - Go 1.25 Release Notes
https://go.dev/doc/go1.25 - Go 1.26 Release Notes
https://go.dev/doc/go1.26 - Go 1.27 Release Notes
https://go.dev/doc/go1.27 - Language interoperability
https://en.wikipedia.org/wiki/Language_interoperability - Python: keywords
https://docs.python.org/3/reference/lexical_analysis.html#keywords - Python: soft keywords
https://docs.python.org/3/reference/lexical_analysis.html#soft-keywords - gcc 3.4: Options Controlling C Dialect
https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html - Has entry keyword ever been implemented in C?
https://stackoverflow.com/questions/77904431/has-entry-keyword-ever-been-implemented-in-c - Has the “entry” keyword ever been implemented in C?
https://retrocomputing.stackexchange.com/questions/28357/has-the-entry-keyword-ever-been-implemented-in-c - Are C versions backwards compatible?
https://stackoverflow.com/questions/67319687/are-c-versions-backwards-compatible - Go evolves in the wrong direction
https://itnext.io/go-evolves-in-the-wrong-direction-7dfda8a1a620 - Java programming language
https://en.wikipedia.org/wiki/Java_(programming_language) - Java Language Keywords
https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html - What happens with closures running as goroutines?
https://go.dev/doc/faq#closures_and_goroutines
