Torna al blog

Go 1.25: Novità e Miglioramenti

Scopri Go 1.25: JSON v2, Green Tea GC, Container-aware GOMAXPROCS, testing/synctest, Core Types Removal e tutte le novità di agosto 2025.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

10 min di lettura
Go 1.25: Novità e Miglioramenti

Go 1.25 è stato rilasciato il 12 agosto 2025. Questa release introduce l'encoding/json/v2 sperimentale con performance 10x superiori, il nuovo Green Tea GC che riduce overhead del 10-40%, Container-aware GOMAXPROCS, testing/synctest stabile e rimuove il concetto di Core Types dalla spec.

🎯 Novità Principali

JSON v2 Package (Experimental) 🔥

La feature più attesa: nuova implementazione JSON con performance 10x superiori!

Abilita JSON v2:

# Build con JSON v2
GOEXPERIMENT=jsonv2 go build

# O set environment
export GOEXPERIMENT=jsonv2
go run main.go

✅ API retrocompatibile con v1:

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    user := User{ID: 1, Name: "Alice", Email: "alice@example.com"}

    // ✅ Stesso API, performance 10x migliori!
    data, err := json.Marshal(user)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(data))
}

Nuove feature JSON v2:

1. Streaming diretto:

import "encoding/json/v2"

// ✅ MarshalWrite - scrivi direttamente senza buffer
var buf bytes.Buffer
err := json.MarshalWrite(&buf, user)

// ✅ UnmarshalRead - leggi direttamente da reader
var decoded User
err := json.UnmarshalRead(resp.Body, &decoded)

2. jsontext package per streaming avanzato:

import "encoding/json/v2/jsontext"

// Encoder avanzato
enc := jsontext.NewEncoder(&buf)
enc.WriteToken(jsontext.ObjectStart)
enc.WriteToken(jsontext.String("name"))
enc.WriteToken(jsontext.String("Alice"))
enc.WriteToken(jsontext.ObjectEnd)

3. Opzioni di marshaling personalizzate:

import "encoding/json/v2"

// Configura comportamento nil slices
opts := json.MarshalOptions{
    FormatNilSliceAsNull: false, // [] invece di null
}

data, err := json.MarshalWithOptions(users, opts)

4. Struct tag potenti:

type Product struct {
    ID    int    `json:"id"`
    Name  string `json:"name,omitzero"`  // Ometti se zero value
    Price float64 `json:"price,string"`   // Marshal come string

    // ✨ Inline nested struct
    Details Details `json:",inline"`

    // ✨ Case-insensitive unmarshal
    Category string `json:"category,nocase"`
}

5. Custom marshal/unmarshal più flessibile:

type Timestamp time.Time

func (t Timestamp) MarshalJSON() ([]byte, error) {
    return []byte(`"` + time.Time(t).Format(time.RFC3339) + `"`), nil
}

func (t *Timestamp) UnmarshalJSON(data []byte) error {
    s := string(data[1 : len(data)-1]) // Remove quotes
    parsed, err := time.Parse(time.RFC3339, s)
    if err != nil {
        return err
    }
    *t = Timestamp(parsed)
    return nil
}

Performance benchmarks JSON v2:

// Benchmark risultati
BenchmarkMarshal_v1      1000000    1200 ns/op   500 B/op
BenchmarkMarshal_v2     10000000     120 ns/op    48 B/op  // 10x faster!

BenchmarkUnmarshal_v1    500000    2400 ns/op   800 B/op
BenchmarkUnmarshal_v2   5000000     280 ns/op    80 B/op  // 8.5x faster!

Vantaggi JSON v2:

  • 10x faster: Unmarshal drammaticamente più veloce
  • Zero-alloc: Molti struct senza heap allocations
  • Streaming: API diretta senza buffer intermedi
  • Configurabile: Opzioni ricche per customize behavior
  • Backward compatible: Drop-in replacement per v1

Green Tea GC (Experimental) 🍵

Nuovo garbage collector con 10-40% meno overhead!

Abilita Green Tea GC:

# Build con nuovo GC
GOEXPERIMENT=greenteagc go build

# O runtime flag
export GOEXPERIMENT=greenteagc
go run main.go

Come funziona:

Il Green Tea GC migliora:

  • Locality: Oggetti piccoli scansionati più efficientemente
  • Parallelism: Migliore utilizzo CPU durante mark phase
  • Scanning: Algoritmo ottimizzato per small objects

Performance impact:

// Workload GC-intensive
type Node struct {
    Value int
    Next  *Node
}

// Green Tea GC benefici:
// - Applicazioni con molti small objects: -20-40% GC overhead
// - Microservices con alta allocazione rate: -15-25% latency
// - Data processing pipelines: +10-20% throughput

Monitor GC performance:

import "runtime"

var stats runtime.MemStats
runtime.ReadMemStats(&stats)

fmt.Printf("GC Pauses: %v\n", stats.PauseNs)
fmt.Printf("GC CPU: %.2f%%\n", stats.GCCPUFraction*100)

Quando usare Green Tea GC:

  • ✅ Apps con molti small objects (<1KB)
  • ✅ High allocation rate (>1GB/sec)
  • ✅ GC-sensitive workloads (sub-millisecond latency)
  • ⚠️ Experimental - test before production!

Container-Aware GOMAXPROCS

GOMAXPROCS ora dinamico e container-aware su Linux!

❌ Prima - GOMAXPROCS statico:

// Container con limit 2 CPU
// GOMAXPROCS = 8 (num CPU host)
// ❌ Oversubscription! Performance poor

runtime.GOMAXPROCS(0) // Returns 8 (wrong!)

✅ Go 1.25 - Container-aware:

// Container con CPU limit 2.5
// GOMAXPROCS = 2 (floor of 2.5)
// ✅ Ottimale!

runtime.GOMAXPROCS(0) // Returns 2 (correct!)

Come funziona:

# Kubernetes CPU limit
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: myapp
    image: myapp:latest
    resources:
      limits:
        cpu: "2"      # Go 1.25 set GOMAXPROCS=2
      requests:
        cpu: "1"      # Ignores requests (solo limits)

Aggiornamento dinamico:

// GOMAXPROCS si aggiorna automaticamente!

// Container scale da 2 CPU → 4 CPU
// GOMAXPROCS: 2 → 4 (automatico!)

// Monitoring
go func() {
    ticker := time.NewTicker(10 * time.Second)
    for range ticker.C {
        fmt.Printf("Current GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
    }
}()

Override manuale (se necessario):

// Disable auto-adjustment
runtime.GOMAXPROCS(4) // Fixed a 4

// O env variable
export GOMAXPROCS=4

Vantaggi:

  • No oversubscription in containers
  • Dynamic scaling se CPU limit cambia
  • Zero config: Funziona automaticamente
  • Better resource usage: CPU non sprecate

testing/synctest Package (Stable!)

Package per testing concorrenza ora stabile!

❌ Prima - Test concurrency difficile:

func TestConcurrent(t *testing.T) {
    done := make(chan bool)

    go func() {
        time.Sleep(100 * time.Millisecond)
        done <- true
    }()

    select {
    case <-done:
        // OK
    case <-time.After(1 * time.Second):
        t.Fatal("timeout") // ❌ Flaky test!
    }
}

✅ Go 1.25 - synctest.Test:

import "testing/synctest"

func TestConcurrent(t *testing.T) {
    synctest.Test(t, func(t *testing.T) {
        done := make(chan bool)

        go func() {
            // ✨ Time virtualizzato!
            time.Sleep(100 * time.Millisecond)
            done <- true
        }()

        <-done // ✅ No timeout! Test istantaneo
    })
}

Come funziona synctest:

// Virtual time in "bubble"
synctest.Test(t, func(t *testing.T) {
    start := time.Now()

    // Goroutine che aspetta
    go func() {
        time.Sleep(1 * time.Hour) // ✨ Istantaneo!
        fmt.Println("Done!")
    }()

    synctest.Wait() // Aspetta goroutines bloccate

    elapsed := time.Since(start)
    fmt.Printf("Elapsed: %v\n", elapsed) // ~0ms!
})

Real-world example - Rate limiter test:

type RateLimiter struct {
    rate     time.Duration
    lastTick time.Time
}

func (r *RateLimiter) Allow() bool {
    now := time.Now()
    if now.Sub(r.lastTick) < r.rate {
        return false
    }
    r.lastTick = now
    return true
}

func TestRateLimiter(t *testing.T) {
    synctest.Test(t, func(t *testing.T) {
        limiter := &RateLimiter{rate: 1 * time.Second}

        // First call
        if !limiter.Allow() {
            t.Fatal("should allow")
        }

        // Immediate second call
        if limiter.Allow() {
            t.Fatal("should block")
        }

        // ✨ Virtual time advance
        time.Sleep(1 * time.Second)

        // Now should allow
        if !limiter.Allow() {
            t.Fatal("should allow after 1s")
        }

        // Test completa in <1ms invece di 2+ secondi!
    })
}

Vantaggi:

  • Fast tests: No real sleep/timeout
  • Deterministic: No flaky tests
  • Easy: Drop-in per test concurrency
  • Virtual time: time.Sleep istantaneo

Core Types Removal

Go spec semplificata rimuovendo concetto Core Types!

Context:

Go 1.18 introdusse "core types" per gestire generics, complicando la spec.

❌ Prima - Core types ovunque:

// Spec pre-1.25
// "For a channel ch, close(ch) is valid if the core type of ch is a channel..."
// ❌ Devi capire "core types" anche per operazioni base!

✅ Go 1.25 - Spec semplice:

// Spec 1.25
// "For a channel ch, close(ch) records that no more values will be sent"
// ✅ Comprensibile senza sapere niente di generics!

// Solo se usi type parameters:
// "If ch is a type parameter, all types in its type set must be channels..."

Impact per sviluppatori:

// Il codice funziona esattamente come prima!
func Send[T any, C ~chan T](ch C, val T) {
    ch <- val
    close(ch) // ✅ Stesso behavior, spec più chiara
}

Vantaggi:

  • Spec più semplice: Meno concetti da imparare
  • Non-generic code: Indipendente da generics
  • Future flexibility: Apre porta a miglioramenti

Flight Recorder API

Nuovo trace recorder in-memory!

import (
    "os"
    "runtime/trace"
)

func main() {
    // ✅ Crea flight recorder
    rec, err := trace.NewFlightRecorder(trace.FlightRecorderConfig{
        Size: 10 * 1024 * 1024, // 10MB buffer
    })
    if err != nil {
        panic(err)
    }
    defer rec.Close()

    // Run app logic...
    processRequests()

    // Su errore/evento, snapshot trace
    if errorDetected {
        f, _ := os.Create("crash-trace.out")
        rec.WriteTo(f, "")
        f.Close()
    }
}

Vantaggi:

  • Low overhead: Solo recent events in memory
  • On-demand: Snapshot solo quando serve
  • Small traces: Non GB di trace data
  • Production-safe: Sempre attivo senza impatto

🚀 Compiler & Toolchain

DWARF 5 Debug Info

# Go 1.25 usa DWARF 5 di default
go build main.go

# Risultato:
# - Binary debug info: -30% size
# - Link time: -20% faster
# - GDB/Delve: Supporto completo

Disable se necessario:

GOEXPERIMENT=nodwarf5 go build

Stack Allocation Optimizer

Compiler alloca più slices su stack:

// Ora su stack invece che heap!
func process() []int {
    data := make([]int, 100) // ✅ Stack alloc (se escape analysis OK)
    for i := range data {
        data[i] = i * 2
    }
    return data
}

Impact:

  • -15-25% allocations in hot paths
  • Better GC pressure
  • ⚠️ Può amplificare bugs con unsafe.Pointer

Debug problemi:

# Trova allocation problematica
go build -gcflags=-d=variablemake=debug

# Disable tutte le nuove stack allocs
go build -gcflags=all=-d=variablemakehash=n

Nil Pointer Check Fix

Compiler bug FIXATO - nil checks ora corretti!

❌ Bug Go 1.21-1.24:

f, err := os.Open("nonExistent")
name := f.Name() // ❌ Non panic! (bug)
if err != nil {
    return
}
println(name)

✅ Go 1.25 - Corretto:

f, err := os.Open("nonExistent")
name := f.Name() // ✅ PANIC qui! (corretto)
if err != nil {
    return
}
println(name)

Fix per codice broken:

// ✅ Check err PRIMA di usare f
f, err := os.Open("file")
if err != nil {
    return err
}
name := f.Name() // Safe

📦 Standard Library

go vet Analyzers

Nuovi analyzers:

# waitgroup analyzer
go vet ./...
# Detect: wg.Add() after wg.Wait()

# hostport analyzer
go vet ./...
# Detect: Invalid host:port parsing

Esempio waitgroup:

// ❌ go vet detect bug
var wg sync.WaitGroup
wg.Wait()
wg.Add(1) // ERROR: Add after Wait!

Esempio hostport:

// ❌ go vet detect bug
host, port, _ := net.SplitHostPort("[::1:8080") // Malformed!

archive/tar Symlink Support

import "archive/tar"
import "io/fs"

// ✅ Writer.AddFS ora supporta symlink!
var buf bytes.Buffer
tw := tar.NewWriter(&buf)

fsys := os.DirFS(".")
tw.AddFS(fsys) // Symlink preservati!

encoding/asn1 Stricter Parsing

// Parsing T61String/BMPString più strict
// Malformed encodings → error (prima accepted)

📊 Performance Benchmarks

Metrica Go 1.24 Go 1.25 Improvement
JSON Unmarshal 2400ns 280ns 8.5x
JSON Marshal 1200ns 120ns 10x
GC Overhead (GreenTea) 40% 25% -37.5%
Stack Allocs 100% 75% -25%
Binary Debug Size 100% 70% -30%

🔄 Migration Guide

Upgrade Go

# Download Go 1.25
wget https://go.dev/dl/go1.25.linux-amd64.tar.gz

# Install
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.25.linux-amd64.tar.gz

# Verify
go version
# go version go1.25 linux/amd64

Test JSON v2

# Try JSON v2
GOEXPERIMENT=jsonv2 go test ./...

# Benchmark
GOEXPERIMENT=jsonv2 go test -bench=. ./...

Test Green Tea GC

# Try new GC
GOEXPERIMENT=greenteagc go run main.go

# Monitor impact
GODEBUG=gctrace=1 GOEXPERIMENT=greenteagc go run main.go

Fix Nil Pointer Bugs

# Audit code per bug pattern
git grep -n "err :=" | grep "\\..*() "

# Fix: check err BEFORE uso result

💡 Conclusioni

Go 1.25 è una release performance-focused:

JSON v2 - 10x faster per API/microservices ✅ Green Tea GC - -37% overhead per GC-heavy apps ✅ Container-aware GOMAXPROCS - Ottimale in K8s ✅ testing/synctest - Fast & deterministic concurrent tests ✅ Core Types removal - Spec più semplice ✅ Flight Recorder - Production-safe tracing ✅ Compiler fixes - Nil check bug finalmente fixato

Raccomandazione:

  • Production apps: Upgrade e test JSON v2
  • ⚠️ GreenTea GC: Test in staging prima
  • K8s/containers: Immediate benefits da GOMAXPROCS
  • Concurrent code: Usa synctest per test