Aktualizacja do Go 1.27: Co się psuje i co wdrożyć w pierwszej kolejności

„json/v2 pod spodem, uuid ze stdlib oraz pułapki GODEBUG.”

Page content

Go 1.27 ukazało się w sierpniu 2026 roku. Dla większości modułów aktualizacja sprowadza się do zmiany jednej linii w pliku go.mod oraz przebiegu polecenia go fix — ale kilka zmian może działać w sposób niewidoczny i problematyczny. Poniżej przedstawiam, co faktycznie może się zepsuć oraz od czego warto zacząć wdrożenie.

Główne zmiany mają charakter strukturalny: pakiet encoding/json opiera się teraz na nowej implementacji json/v2, pakiet uuid dołączył do biblioteki standardowej, a metody wreszcie mogą deklaraować własne parametry typów. Żadna z tych zmian nie wymusza przepisania kodu, a zespół Go zachował zwyczajową obietnicę kompatybilności — prawie wszystko kompiluje się i działa bez zmian.

Go 1.27 upgrade — what breaks and what to adopt

Mimo to nie wszystko dzieje się cicho. Kilka ustawień GODEBUG powoduje teraz niepowodzenie budowania przy starych wartościach, compress/flate generuje inne bajty, go test wymusza nowy sprawdzian stdversion, a nazwy symboli generowane przez kompilator uległy zmianie. Jeśli potrzebujesz przypomnienia składni w częściach, które się nie zmieniły, ściągawka do Go pozostaje aktualna; ten przewodnik obejmuje resztę, z poleceniami i poprawkami dla każdego elementu.

Ścieżka aktualizacji na jednym schemacie

Zespół Go zaleca kolejność: najpierw budowanie, potem naprawianie. Polecenie go fix przepisuje kod na nowe interfejsy API i idiomaty, ale dopiero czyste zbudowanie pozwala stwierdzić, czy któreś ze zmian na poziomie czasu wykonania dotknęło Twojego projektu.

flowchart TD A[Instalacja pakietu narzędziowego Go 1.27] --> B["go build ./..."] B --> C{Czy budowanie zakończyło się sukcesem?} C -- "Nie: usunięta wartość GODEBUG" --> D[Ustaw końcową wartość domyślną lub usuń linię] D --> B C -- Tak --> E["go test ./..."] E --> F{"Czy stdversion i vet są czyste?"} F -- "Nie: symbol zbyt nowy" --> G[Zwiększ linię go lub wymień symbol] G --> E F -- Tak --> H["go mod tidy"] H --> I["go fix ./..."] I --> J[Przegląd testów wzorcowych JSON i różnic]

Zacznij od samego pakietu narzędziowego. Jeśli zarządzasz pakietami narzędziowymi za pomocą polecenia go, możesz przypiąć wersję w go.mod lub zainstalować ją jawnie:

go install golang.org/dl/go1.27@latest
go1.27 download
go1.27 version

Alternatywnie, ustaw zmienną środowiskową GOTOOLCHAIN=go1.27.0 lub dodaj linię toolchain go1.27.0 do go.mod i pozwól poleceniu go pobrać ją automatycznie. Towarzyszący artykuł o strukturze przestrzeni roboczej Go wyjaśnia, jak linie go i toolchain współpracują między wieloma modułami; krótkie podsumowanie dla pojedynczego modułu znajduje się poniżej.

Krok 1: zwiększenie linii go i czyszczenie usuniętych ustawień GODEBUG

go mod edit -go=1.27
go mod tidy
go build ./...

Zwiększenie dyrektywy go włącza nowe funkcje języka (metody generyczne, klucze promowanych pól w dosłownych strukturach, szersza inferencja typów funkcji) i sprawia, że polecenie go staje się surowe wobec ustawień GODEBUG, których wsparcie zostało usunięte. W Go 1.27 następujące ustawienia zostały trwale usunięte:

  • asynctimerchan — kanały timera i tikera są teraz zawsze bez bufora
  • gotypesalias
  • tlsunsafeekm, tlsrsakex, tls3des, tls10server
  • x509keypairleaf

Nowa reguła: polecenie go akceptuje nadal te ustawienia w go.mod (wpisy godebug) lub w kodzie źródłowym (komentarze //go:debug), pod warunkiem że są ustawione na końcową wartość domyślną, która istniała przed usunięciem. Stara wartość — ta, która wyłączała zmianę zachowania — powoduje błąd budowania:

//go:debug asynctimerchan=0  // błąd budowania: stara wartość usuniętego ustawienia
//go:debug asynctimerchan=1  // zaakceptowane, ale lepiej usunąć linię

Jeśli Twoje budowanie zawiedzie w tym miejscu, oznacza to, że kod zależał od buforowanych kanałów timera z czasów przed Go 1.23. Poprawka dotyczy kodu, nie konfiguracji: sprawdź użycie Timer.Stop i Timer.Reset w kontekście wzorcow kontekstu, anulowania i timeoutów, które polegają na kanałach synchronicznych. Ta sama zasada dotyczy ustawień związanych z TLS — wszystko, co przypinało tls3des=1 lub tls10server=1, utrzymywało przy życiu stare kity szyfrowania, a Go 1.27 odmawia kompilacji takiego kodu.

Krok 2: uruchomienie go test — pułapka stdversion

Polecenia go build i go test nie są już spójne. W Go 1.27 polecenie go test domyślnie uruchamia sprawdzian vet o nazwie stdversion, który zgłasza symbole biblioteki standardowej, które są zbyt nowe w stosunku do wersji go ważnej w pliku powołującym.

// go.mod nadal mówi "go 1.26"
func Base(p string) string {
    _, file, ok := strings.CutLast(p, "/") // dodane w Go 1.27
    if !ok {
        return p
    }
    return file
}

Z pakietem narzędziowym 1.27 polecenie go build powiedzie się — kompilator ma dostęp do strings.CutLast — podczas gdy go test nie przejdzie etapu vet, ponieważ go.mod nadal deklaruje wersję 1.26. Z rzeczywistym pakietem narzędziowym 1.26 plik w ogóle się nie skompiluje. Lekcja jest proceduralna: nigdy nie waliduj aktualizacji tylko poleceniem go build; uruchom pełny zestaw testów, a dopiero potem zwiększ linię go. Zestaw vet w go test wykrył też nowe ostrzeżenie printf: fmt.Errorf("...: %w", p), gdzie p jest typem *E, a E implementuje error, teraz wydaje ostrzeżenie, ponieważ zawijanie wskaźnika prowadzi do asercji typów i sprawdzeń errors.Is, które mogą zawieść. Ten wzorzec awarii pojawia się w kodzie obsługi błędów dokładnie tak, jak opisano we wzorcach architektury obsługi błędów w Go — sprawdź typ operanda, nie tylko wyraz formatujący.

encoding/json: silnik v2 pod spodem

Największa zmiana pod maską: encoding/json/v2 (eksperymentalne od Go 1.25 jako GOEXPERIMENT=jsonv2) zostało wypromowane, a klasyczny pakiet encoding/json opiera się teraz na implementacji v2. Dla użytkowników v1 zachowanie jest zachowane — puste tablice (nil slices) kodują nadal jako null, klucze map są nadal sortowane — z jedną widoczną różnicą: dokładny tekst niektórych komunikatów o błędach uległ zmianie. Jeśli testy lub monitoring pasują do komunikatów o błędach, spodziewaj się różnic.

Może dojść do trzech problemów:

  1. Tekst błędów. Każde asercje strings.Contains(err.Error(), ...) oparte na starym słownictwie v1 wymagają aktualizacji.
  2. Ścieżki wrażliwe na wydajność mogą się zmienić. Rozpakowywanie jest szybsze, ale przepisana implementacja zmienia, które operacje przydzielają pamięć; benchmarki zaznaczone w CI to pokażą.
  3. Rozwiązanie awaryjne dla kompatybilności. GOEXPERIMENT=nojsonv2 przywraca oryginalną implementację v1 w czasie budowania. Jest to rozwiązanie tymczasowe — planuj jego usunięcie, a nie wdrażanie z nim.

Efekt drugorzędnego rzędu dla testów wzorcowych (golden tests): kodownik compress/flate zmienił się w tym samym wydaniu, więc wyjścia archive/zip, compress/gzip, compress/zlib i image/png mogą się różnić bajt po bajcie od Go 1.26, nawet jeśli dekodują się identycznie. Odtwórz skompresowane pliki wzorcowe, zamiast przypinać stare bajty.

Celowe migracje do interfejsu v2

Interfejs API v2 to osobny import (encoding/json/v2) z funkcjami Marshal, Unmarshal i systemem opcji. Wsparcie ścieżka migracji polega najpierw na tym, aby wywołania v2 zachowywały się tak jak v1, a następnie na selektywnym zacieśnieniu ustawień:

import (
    "encoding/json/v2"
    "encoding/json/jsontext"
)
// Krok 1: identyczne zachowanie jak w encoding/json — bezpieczna, mechaniczna zmiana
out, err := jsonv2.Marshal(v, jsonv2.DefaultOptionsV1())

// Krok 2: włączanie jednej domyślnej opcji v2 naraz
out, err := jsonv2.Marshal(v,
    jsonv2.DefaultOptionsV1(),
    jsonv2.FormatNilSliceAsNull(false)) // puste tablice (nil slices) kodują się teraz jako []

Gdy usuniesz DefaultOptionsV1, zastosują się domyślne ustawienia v2, które są surowsze i bardziej interoperacyjne niż w v1:

Zachowanie v1 (encoding/json) Domyślnie w v2
Nieprawidłowy UTF-8 w łańcuchach Zamieniany na U+FFFD Błąd
Duplikowane nazwy w obiekcie Dozwolone Błąd
Pusta tablica (nil slice) / pusta mapa (nil map) null [] / {}
Dopasowanie pól struktury Fallback na ignorowanie wielkości liter Wrażliwe na wielkość liter
Nieznane człony obiektu Ignorowane Ignorowane (błąd przy RejectUnknownMembers)
Kolejność kluczy map Zawsze sortowane Domyślnie niesortowane
time.Duration Wbudowane kodowanie łańcuchowe Brak domyślnego kodowania

Niesortowane klucze map to ten element, który zaskakuje testy z plikami wzorcowymi (golden-file tests): v2 pomija sortowanie, ponieważ zajmuje to czas. Gdy potrzebujesz stabilnego wyjścia — pliki wzorcowe, testy migawkowe (snapshot tests), reprezentacje hashowane — przekaż json.Deterministic(true), co przywraca sortowane wyjście dla map i struktur. Modele z artykułu porównanie ORM mają wszystkie znaczniki json; te znaczniki działają nadal w v2, ale opcja znacznika format została usunięta podczas eksperymentu, a inline zmieniono nazwę na embed, więc przeszukaj definicje modeli w poszukiwaniu obu tych słów kluczowych przed migracją. Dla potrzeb niskiego poziomu — strumieniowanie, przetwarzanie token po tokenu, obsługa niestandardowej składni — towarzyszący pakiet encoding/json/jsontext udostępnia maszynę stanową Encoder/Decoder dla tekstu JSON.

stdlib uuid: rezygnacja z github.com/google/uuid

Go 1.27 dodaje uuid do biblioteki standardowej, implementując RFC 9562. Zakres interfejsu API obejmuje powszechne przypadki:

id := uuid.New()    // obecnie równoważne NewV4
v7 := uuid.NewV7()  // sortowalne wg czasu, 48-bitowy znacznik czasu + losowe
u, err := uuid.Parse("550e8400-e29b-41d4-a716-446655440000")

github.com/google/uuid to drugi co do częstości używania moduł Go, więc ta migracja ma znaczenie przy dużej skali. Dwa interfejsy API są podobne — typy to wartości [16]byte porównywalne za pomocą == i używane jako klucze map, String(), MarshalText/UnmarshalText działają w obie strony — i projekt źródłowy przygotowuje symulacje go:fix inline, dzięki czemu go fix może przepisać wywołania uuid.NewV4() na pakiet standardowy i usunąć zależność.

Ciekawą decyzją wdrożeniową jest NewV7. UUID wersji 7 zawierają znacznik czasu w milisekundach w najbardziej znaczących bitach, więc wartości sortują się w kolejności utworzenia (chyba że zegar systemowy ruszy wstecz). Dla kluczów głównych baz danych oznacza to lepszą lokalność w drzewach B niż w przypadku losowych UUID v4, oraz darmową kolejność typu created_at samą w sobie w identyfikatorze — ta sama cecha, która napędza dyskusję o kluczach sortowalnych czasem w modelach danych wielonajemczych.

Zmiany w języku: metody generyczne i inne

Metody generyczne to najbardziej oczekiwana funkcja językowa w tym wydaniu: deklaracja metody może teraz deklaraować własne parametry typów.

// Go 1.27: math/rand/v2 zyskała metodę generyczną obok funkcji
func (r *Rand) N[Int intType](n Int) Int

Dwa ograniczenia utrzymują system typów w postaci rozstrzygalnej: metody interfejsów nie mogą deklaraować parametrów typów, a metody interfejsów nie mogą być implementowane przez metody generyczne. Wraz z nimi, klucze dosłownych struktur mogą teraz być dowolnym poprawnym selektorem pola (nie tylko nazwami pól najwyższego poziomu), a inferencja typów funkcji uogólnia się do wszystkich kontekstów, w których funkcja generyczna jest przypisywana do odpowiadającej zmiennej typu funkcji. Żadna z tych zmian nie psuje istniejącego kodu; usuwają one obejścia — funkcje pomocnicze generyczne na poziomie pakietu i jawne instancjacje.

Polecenie go fix zyskało nowoczesniaki (modernizery) dla sąsiednich porządkowań: atomictypes, embedlit, slicesbackward i unsafefuncs. Jedna uwaga z praktycznych laboratoriów aktualizacyjnych: uruchamiaj go fix -fix=atomictypes świadomie, a nie masowo na bibliotekach, ponieważ może zmienić typy zmiennych na poziomie pakietu, do których odnoszą się inne pakiety. Nowoczesniak waitgroup został przemianowany na waitgroupgo; zauważ, że sync.WaitGroup.Go (od 1.25) obejmuje już większość tego, co wykrywa — zobacz wzorce ograniczonej współbieżności, aby zobaczyć, jak wpasowuje się to w zamykanie puli pracowników.

Rzeczy, które cicho się psują

To zmiany, które kompilują się bez problemu, ale kosztują Cię dzień wydania:

  • Nazwy symboli literałów funkcji. Kompilator generuje teraz prostsze, niezależne od inline nazwy dla domyknięć i może łączyć identyczne litery funkcji. Testy asercjonujące nazwy symboli w śladach (tracebacks) lub profilach zawiodą; kod porównujący wskaźniki kodu funkcji pod kątem równości jest narażony na więcej przypadków.
  • Wyrównanie gofmt. Poprawka logiki wyrównania kolumn (wcześniej różniła się między architekturami CPU) przepływa małą liczbę już sformatowanych plików. Spodziewaj się różnic zawierających tylko spacje w commitcie gofmt ./... po aktualizacji.
  • go mod tidy przepisuje go.mod. Dla modułów na go 1.27, tidy łączy teraz zduplikowane bloki require w co najwyżej dwa bloki (bezpośrednie, pośrednie), zachowując komentarze. Porównaj swoje go.mod i rozwiąż pozostałości po konfliktach scalania przed committem wydania, a nie po nim.
  • Etykiety śladów (Traceback labels). Ślady (tracebacks) dla modułów na go 1.27 zawierają etykiety gorut runtime/pprof w linii nagłówkowej; wyłącz to GODEBUG=tracebacklabels=0, jeśli etykiety niosą wrażliwe dane.
  • go tool trace wiąże się z localhostem. -http=:6060 nasłuchuje teraz tylko na localhost; przekaż jawnie -http=0.0.0.0:6060, gdy potrzebujesz dostępu zdalnego (i pamiętaj, że port profilowania na współdzielonym hoście jest ryzykiem — zobacz workflow profilowania produkcyjnego dla utwardzonego ustawienia).

Eksperymenty warte obserwacji

Dwa eksperymentalne pakiety ukazały się w 1.27, oba włączane w czasie budowania GOEXPERIMENT=simd:

  • simd — przenośne, niezależne od rozmiaru wektora API (Int8s, Float32s, …) mapujące na instrukcje sprzętowe tam, gdzie są dostępne. Wspiera AVX/AVX2/AVX-512 na amd64, NEON na arm64 oraz WebAssembly SIMD. Biblioteka standardowa już używa tej mechaniki wewnętrznie: implementacja map Swiss Table w Go korzysta z tego.
  • simd/archsimd — warstwa specyficzna dla architektury dla kodu, który musi znać szerokość wektora.

Po stronie kryptografii, crypto/mldsa implementuje podpisowy schemat ML-DSA po-quantum z FIPS 204 w trzech zestawach parametrów (MLDSA44, MLDSA65, MLDSA87), zintegrowany z crypto/x509 i oferowany jako schematy sygnaturowe TLS 1.3. Hybrydowa wymiana kluczy po-quantum może teraz być wymuszona przez Config.CurvePreferences, nawet gdy ustawienia GODEBUG tlsmlkem/tlssecpmlkem są ustawione jako fallbacki. Żadna z tych rzeczy nie jest przedmiotem migracji dzisiaj, ale obie kształtują powierzchnię konfiguracji TLS, w którą Twoje usługi będą rosły.

Nowe API warte natychmiastowego wdrożenia

Kilka dodatków rozwiązuje codzienne tarć i nie kosztuje niczego wdrożenia:

// strings.CutLast / bytes.CutLast — zastępują taniec z LastIndex
before, after, found := strings.CutLast("a/b/c.tar.gz", ".")

// httptest.NewTestServer — fałszywa sieć w pamięci, automatyczne czyszczenie
srv := httptest.NewTestServer(t, mux) // brak prawdziwego portu, brak defer srv.Close()

httptest.NewTestServer tworzy httptest.Server oparty o sieć w pamięci, a nie nasłuchiwanie TCP i rejestruje własne t.Cleanup. Jego prawdziwa zapłata to parowanie z testing/synctest: rundy HTTP wykonują się w syntetycznym czasie, czyniąc testy timeoutów i ponowień szybkimi i deterministycznymi — połączenie to jest zademonstrowane w artykule testowanie współbieżne z synctest.

Kontrolka aktualizacji

Kolejność jest ważniejsza niż elementy; każdy krok zakłada, że poprzedni zakończył się sukcesem.

Krok Mały moduł Serwis / monorepo
1. Pakiet narzędzi go install golang.org/dl/go1.27@latest Przymus toolchain go1.27.0; wyrównaj obrazy CI
2. go.mod go mod edit -go=1.27 && go mod tidy Tak samo, plus rozwiązanie różnic scalania bloków require
3. Usunięte GODEBUG Szukaj //go:debug i godebug: Audytuj również wdrożone konfiguracje i flagi
4. go build ./... Popraw błędy kompilacji Poprawiaj per-moduł; bisekcja awarii
5. go test ./... Popraw znajdowania stdversion/printf Uruchom przed zwiększeniem go, aby wychwycić dryf symboli
6. go fix ./... Przeglądaj różnicę Po jednym nowoczesniaku na raz
7. Testy JSON/wzorcowe Odtwórz pliki wzorcowe Sprawdź też pliki wzorcowe oparte na flate (zip/gzip/png)
8. GOEXPERIMENT=nojsonv2 Pomiń Tylko tymczasowo; zgłoś problemy dla tego, co to wymusiło

Notatki końcowe

Złamania w Go 1.27 skupiają się wokół krawędzi, których większość programów nie dotyka — usunięte opt-outy GODEBUG, asercje nazw symboli, pliki wzorcowe z bajtami skompresowanymi — podczas gdy zwycięstwa trafiają tam, gdzie żyje każdy program: szybszy JSON, standardowe UUID, metody generyczne. Czysta aktualizacja to głównie dyscyplina: go build, potem go test, potem go fix, w tej kolejności, z linią go zwiększoną przed uruchomieniem testów, a nie po nim. Rozwiązanie awaryjne nojsonv2 i reguła akceptacji asynctimerchan przy końcowej wartości domyślnej kupują czas, ale oba są zaplanowane do zniknięcia, więc traktuj tę aktualizację jako termin na odłożone poprawki, które dziedziczysz z wersji 1.23–1.26.

Subskrybuj

Otrzymuj nowe wpisy o systemach, infrastrukturze i inżynierii AI.