Go 1.24 (Февраль 2025)
Go 1.24 принёс значительные улучшения производительности (Swiss tables), новые возможности для низкоуровневого программирования (weak pointers) и долгожданные улучшения в тестировании.
Weak Pointers -- слабые ссылки
Пакет weak добавляет слабые указатели -- ссылки, которые не препятствуют сборке мусора объекта. Если объект собран GC, слабый указатель возвращает nil.
Базовое использование
package main
import (
"fmt"
"runtime"
"weak"
)
func main() {
// Create a strong pointer
val := new(string)
*val = "hello"
// Create a weak pointer from strong pointer
wp := weak.Make(val)
// Get strong pointer back (may return nil if GC collected)
if strong := wp.Value(); strong != nil {
fmt.Println(*strong) // "hello"
}
// Drop the strong reference
val = nil
runtime.GC() // Force GC
// Weak pointer now returns nil
if strong := wp.Value(); strong == nil {
fmt.Println("Value was garbage collected")
}
}
Use case: кеш с автоматическим освобождением памяти
package cache
import (
"sync"
"weak"
)
// WeakCache stores values that can be garbage collected when memory is needed
type WeakCache[K comparable, V any] struct {
mu sync.RWMutex
items map[K]weak.Pointer[V]
}
func NewWeakCache[K comparable, V any]() *WeakCache[K, V] {
return &WeakCache[K, V]{
items: make(map[K]weak.Pointer[V]),
}
}
func (c *WeakCache[K, V]) Set(key K, value *V) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = weak.Make(value)
}
func (c *WeakCache[K, V]) Get(key K) (*V, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
wp, exists := c.items[key]
if !exists {
return nil, false
}
// Value may have been garbage collected
val := wp.Value()
if val == nil {
// Clean up stale entry
c.mu.RUnlock()
c.mu.Lock()
delete(c.items, key)
c.mu.Unlock()
c.mu.RLock()
return nil, false
}
return val, true
}
// Cleanup removes stale entries whose values were garbage collected
func (c *WeakCache[K, V]) Cleanup() int {
c.mu.Lock()
defer c.mu.Unlock()
removed := 0
for key, wp := range c.items {
if wp.Value() == nil {
delete(c.items, key)
removed++
}
}
return removed
}
Use case: каноническое значение (interning с weak pointers)
package intern
import (
"sync"
"weak"
)
// Interner canonicalizes values: equal values share the same pointer
type Interner[T comparable] struct {
mu sync.Mutex
cache map[T]weak.Pointer[T]
}
func NewInterner[T comparable]() *Interner[T] {
return &Interner[T]{
cache: make(map[T]weak.Pointer[T]),
}
}
func (in *Interner[T]) Get(val T) *T {
in.mu.Lock()
defer in.mu.Unlock()
// Check if we already have this value
if wp, ok := in.cache[val]; ok {
if ptr := wp.Value(); ptr != nil {
return ptr // Return existing canonical value
}
}
// Create new canonical value
ptr := new(T)
*ptr = val
in.cache[val] = weak.Make(ptr)
return ptr
}
Взаимодействие с GC
| Тип ссылки | GC может собрать? | Когда использовать |
|---|---|---|
Strong (*T) |
Нет, пока ссылка жива | Обычные данные |
Weak (weak.Pointer[T]) |
Да, в любой момент | Кеши, канонические значения |
Важно:
weak.Pointer[T].Value()может вернуть nil в любой момент после GC-цикла. Всегда проверяйте результат на nil.
Swiss Tables -- новая реализация map
Go 1.24 полностью заменил внутреннюю реализацию map на Swiss tables (вдохновлённые Abseil от Google). Это прозрачное изменение -- API остался прежним, но производительность значительно выросла.
Что изменилось внутри
| Аспект | Старая реализация | Swiss Tables (1.24+) |
|---|---|---|
| Хеш-таблица | Bucket array с overflow | Open addressing с SIMD probing |
| Поиск (hit) | ~60 ns | ~40 ns (~30% быстрее) |
| Поиск (miss) | ~80 ns | ~35 ns (~55% быстрее) |
| Итерация | Среднее | До 2x быстрее |
| Память | Больше overhead | Меньше overhead на элемент |
| Удаление | Дорогое | Дешёвое (tombstone) |
Практические бенчмарки
func BenchmarkMapLookup(b *testing.B) {
m := make(map[string]int, 1000)
for i := range 1000 {
m[fmt.Sprintf("key_%d", i)] = i
}
key := "key_500"
for b.Loop() { // Go 1.24+ benchmark syntax
_ = m[key]
}
}
// Go 1.23: BenchmarkMapLookup-8 ~60 ns/op
// Go 1.24: BenchmarkMapLookup-8 ~40 ns/op (Swiss tables)
Влияние на ваш код
Никакого. Swiss tables полностью обратно совместимы. Все программы работают так же, но быстрее. Единственное замечание: порядок итерации по map (и так не гарантированный) может измениться, что является ожидаемым поведением.
testing/synctest (экспериментальный) -- детерминированное тестирование конкурентности
testing/synctest позволяет тестировать конкурентный код детерминированно, контролируя расписание горутин и время.
package main
import (
"testing"
"testing/synctest"
"time"
)
func TestDebounce(t *testing.T) {
synctest.Run(func() {
var calls int
debounced := Debounce(100*time.Millisecond, func() {
calls++
})
// Rapid fire
debounced()
debounced()
debounced()
// Advance virtual time
synctest.Sleep(50 * time.Millisecond)
debounced() // Reset timer
synctest.Sleep(50 * time.Millisecond)
// Still within debounce window, no call yet
synctest.Sleep(100 * time.Millisecond)
// Now the debounce fired
if calls != 1 {
t.Errorf("expected 1 call, got %d", calls)
}
})
}
func TestTimeout(t *testing.T) {
synctest.Run(func() {
ch := make(chan string, 1)
go func() {
synctest.Sleep(5 * time.Second) // Virtual time
ch <- "result"
}()
// Fast-forward virtual clock
synctest.Sleep(5 * time.Second)
select {
case result := <-ch:
if result != "result" {
t.Errorf("unexpected: %s", result)
}
default:
t.Error("expected result")
}
})
}
Преимущества synctest
| Обычные тесты | synctest |
|---|---|
time.Sleep(5*time.Second) -- ждём реально |
Виртуальное время -- мгновенно |
| Flaky из-за timing | Детерминированные |
| Трудно тестировать timeout | Простое управление временем |
| Порядок горутин недетерминирован | Контролируемое расписание |
os.Root -- ограничение файловых операций
package main
import (
"fmt"
"os"
)
func main() {
// Create a root restricted to a directory
root, err := os.OpenRoot("/var/data/uploads")
if err != nil {
panic(err)
}
defer root.Close()
// All file operations are restricted to this directory
f, err := root.Open("document.pdf")
if err != nil {
panic(err)
}
defer f.Close()
// Path traversal attacks are prevented!
_, err = root.Open("../../etc/passwd")
// Error: path escapes root directory
// Stat, Create, etc. also restricted
info, err := root.Stat("report.txt")
if err == nil {
fmt.Printf("Size: %d\n", info.Size())
}
// Create file within root
newFile, err := root.Create("output.txt")
if err != nil {
panic(err)
}
newFile.Write([]byte("Safe content"))
newFile.Close()
}
Безопасность: предотвращение path traversal
// БЫЛО: уязвимо к path traversal
func serveFile(userPath string) (*os.File, error) {
// User can pass "../../etc/passwd"!
return os.Open(filepath.Join("/uploads", userPath))
}
// СТАЛО (Go 1.24+): безопасно по дизайну
func serveFile(root *os.Root, userPath string) (*os.File, error) {
// Path traversal impossible -- restricted to root directory
return root.Open(userPath)
}
Generic Type Aliases -- полная поддержка
// Go 1.24: generic type aliases fully supported
type Set[T comparable] = map[T]struct{}
type Pair[A, B any] = struct {
First A
Second B
}
// Usage
var s Set[string]
s = make(Set[string])
s["hello"] = struct{}{}
p := Pair[string, int]{First: "age", Second: 30}
До Go 1.24 type aliases не поддерживали параметры типов. Теперь это ограничение снято.
b.Loop() -- новый паттерн для бенчмарков
// БЫЛО: ручной цикл с b.N
func BenchmarkOld(b *testing.B) {
for i := 0; i < b.N; i++ {
processItem(testData)
}
}
// СТАЛО (Go 1.24+): b.Loop() -- чище и точнее
func BenchmarkNew(b *testing.B) {
for b.Loop() {
processItem(testData)
}
}
Преимущества b.Loop()
| b.N loop | b.Loop() |
|---|---|
| Setup code внутри цикла может исказить результат | Setup вне цикла, только рабочий код в цикле |
| Компилятор может оптимизировать мёртвый код | Предотвращает dead code elimination |
Нужно помнить b.ResetTimer() |
Автоматическое управление таймером |
func BenchmarkSort(b *testing.B) {
// Setup: done once, outside the loop
data := generateTestData(10000)
for b.Loop() {
// b.Loop() creates a fresh copy for each iteration
// and manages timer correctly
s := slices.Clone(data)
slices.Sort(s)
}
}
func BenchmarkWithSubBenchmarks(b *testing.B) {
sizes := []int{10, 100, 1000, 10000}
for _, size := range sizes {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
data := generateTestData(size)
for b.Loop() {
processData(data)
}
})
}
}
json omitzero tag
type Response struct {
Name string `json:"name"`
Count int `json:"count,omitzero"` // Omit if zero value
CreatedAt time.Time `json:"created_at,omitzero"` // Omit zero time
Tags []string `json:"tags,omitzero"` // Omit nil/empty slice
}
// omitzero vs omitempty:
// omitempty: omits "", 0, false, nil, empty slice/map
// omitzero: omits only the zero value of the type (uses IsZero() if available)
type Event struct {
Time time.Time `json:"time,omitzero"` // Uses time.Time.IsZero()
// With omitempty, time.Time{} would NOT be omitted (it's a non-empty struct)
// With omitzero, time.Time{} IS omitted (IsZero() returns true)
}
| Tag | "" |
0 |
false |
nil |
time.Time{} |
[]int{} |
|---|---|---|---|---|---|---|
omitempty |
Omit | Omit | Omit | Omit | Keep | Omit |
omitzero |
Omit | Omit | Omit | Omit | Omit | Omit |
crypto/mlkem -- пост-квантовая криптография
package main
import (
"crypto/mlkem"
"fmt"
)
func main() {
// ML-KEM (previously CRYSTALS-Kyber) -- post-quantum key encapsulation
// Safe against quantum computer attacks
// Generate key pair
dk, err := mlkem.GenerateKey768()
if err != nil {
panic(err)
}
// Encapsulate: create shared secret + ciphertext
ciphertext, sharedSecret1 := dk.EncapsulationKey().Encapsulate()
// Decapsulate: recover shared secret from ciphertext
sharedSecret2, err := dk.Decapsulate(ciphertext)
if err != nil {
panic(err)
}
fmt.Println(string(sharedSecret1) == string(sharedSecret2)) // true
fmt.Printf("Shared secret: %x...\n", sharedSecret1[:16])
}
GOAUTH для приватных модулей
# Configure authentication for private module registries
GOAUTH="https://proxy.company.com:bearer:$(cat /path/to/token)" go mod download
# Multiple auth entries separated by semicolons
GOAUTH="https://github.com:bearer:$GH_TOKEN;https://gitlab.com:bearer:$GL_TOKEN"
Улучшения go tool
# go tool now supports running tools from modules
go tool golang.org/x/tools/cmd/goimports@latest .
# List available tools
go tool -n
# Run vet with specific analyzer
go tool vet -unusedresult ./...
Go 1.25 (Август 2025)
Go 1.25 сфокусирован на внутренних улучшениях runtime, продолжении оптимизации Swiss tables и стабилизации экспериментальных фич.
Удаление core types из спецификации
Core types были внутренним механизмом спецификации, усложнявшим понимание дженериков. Go 1.25 упростил спецификацию, убрав это понятие без изменения поведения кода.
// Before: the spec talked about "core type" of a constraint
// After: simpler rules about how operations work with type parameters
// Practical impact: NONE for users
// The same code compiles and works the same way
// Only the specification text changed
Продолжение оптимизации Swiss Tables
Go 1.25 продолжил оптимизацию реализации map:
- Улучшена производительность для маленьких map (< 8 элементов)
- Оптимизация итерации (
for k, v := range m) - Улучшение memory layout для кеш-дружественности
- Меньший overhead памяти для пустых map
// Benchmark comparison
// Go 1.24 Swiss tables -> Go 1.25 optimized Swiss tables:
// Small map (8 elements) lookup: ~35ns -> ~28ns
// Map iteration (1000 elements): ~2.5us -> ~1.8us
// Empty map memory: 48 bytes -> 0 bytes (nil map optimization)
Улучшения testing/synctest
Go 1.25 расширил возможности synctest:
func TestRateLimiter(t *testing.T) {
synctest.Run(func() {
limiter := NewRateLimiter(10, time.Second) // 10 requests per second
// Fire 10 requests instantly
for range 10 {
if !limiter.Allow() {
t.Error("should allow first 10 requests")
}
}
// 11th request should be denied
if limiter.Allow() {
t.Error("should deny 11th request")
}
// Advance time by 1 second
synctest.Sleep(time.Second)
// Should allow again
if !limiter.Allow() {
t.Error("should allow after rate limit window")
}
})
}
Исследование Green Threads (Preview)
Go 1.25 включил экспериментальную поддержку "green threads" -- механизма, позволяющего горутинам переключаться без участия runtime scheduler в определённых случаях:
# Enable experimental green threads
GOEXPERIMENT=greenthreads go build ./...
Что это даёт:
- Снижение latency переключения горутин
- Лучшая производительность для I/O-bound нагрузок
- Уменьшение overhead при большом количестве горутин (>100k)
Статус: Экспериментальная фича. Не для production. API и поведение могут измениться.
Улучшения crypto
- Обновлённые реализации SHA-256/SHA-512 с использованием SIMD-инструкций
- Улучшенная производительность TLS 1.3 handshake
- Расширенная поддержка ML-KEM (пост-квантовая криптография)
Производительность стандартной библиотеки
encoding/json Marshal: ~15% faster (reduced allocations)
net/http server: ~10% lower latency (connection reuse)
regexp matching: ~8% faster (optimized DFA)
fmt.Sprintf: ~12% faster (reduced allocations for common formats)
Оптимизации компилятора
- Улучшенный escape analysis: больше переменных остаётся на стеке
- Лучшее инлайнирование для generic-функций
- Оптимизация bounds check elimination для слайсов
- Уменьшение размера бинарников на ~3-5%
Сводная таблица Go 1.24 -- 1.25
| Фича | Версия | Влияние | Действие |
|---|---|---|---|
| weak.Pointer | 1.24 | Среднее | Использовать для кешей, canonical values |
| Swiss tables | 1.24 | Высокое | Автоматическое ускорение map |
| testing/synctest | 1.24 | Высокое | Детерминированные тесты конкурентности |
| os.Root | 1.24 | Среднее | Безопасная работа с файлами |
| Generic type aliases | 1.24 | Среднее | Упрощение generic-кода |
| b.Loop() | 1.24 | Среднее | Заменить b.N циклы в бенчмарках |
| json omitzero | 1.24 | Среднее | Использовать вместо omitempty для time/struct |
| crypto/mlkem | 1.24 | Низкое | Пост-квантовая безопасность |
| GOAUTH | 1.24 | Среднее | Упрощение работы с приватными модулями |
| Core types removal | 1.25 | Нет | Никаких действий, только упрощение спеки |
| Swiss table improvements | 1.25 | Среднее | Автоматическое ускорение |
| Green threads preview | 1.25 | Нет (эксперимент) | Только для тестирования |
| Runtime performance | 1.25 | Среднее | Автоматическое ускорение |