O Go 1.26 reescreveu o go fix sobre o mesmo framework do go vet. O novo comando reúne todos os modernizadores que a equipa do Go e os autores do x/tools criaram. Cada um é um pequeno analisador que reconhece um padrão antigo e o reescreve em Go atual.
Um só comando executa-os todos:
example.bashbashgo fix ./...
Corre-o numa branch limpa, revê o diff, corre os testes e faz commit. Se quiseres pré-visualizar as alterações sem escrever ficheiros, usa go fix -diff ./....
Abaixo estão todos os modernizadores incluídos no 1.26, agrupados por área. Cada entrada indica a versão do Go que introduziu a API para a qual o código é reescrito, além de um par antes/depois. Foi pensado tanto para ler na diagonal ou pesquisar com ctrl-F como para ler do início ao fim.
Índice
- Linguagem e funções integradas
- Slices, maps e iteração
- Strings e formatação
- Redes, erros e reflexão
- Concorrência e testes
- Como executar o go fix em segurança
Linguagem e funções integradas
Há aqui sete modernizadores. A maioria cobre funcionalidades da linguagem (any, a nova forma range n, a alteração da variável de ciclo no 1.22, a extensão new(expr)) e ainda algumas limpezas que atravessam várias versões.
any (Go 1.18): substitui interface{} por any
any é apenas um alias, mas lê-se melhor em assinaturas e parâmetros de tipo.
Antes:
example.gogofunc log(args ...interface{}) { fmt.Println(args...) } var cache map[string]interface{}
Depois:
example.gogofunc log(args ...any) { fmt.Println(args...) } var cache map[string]any
minmax (Go 1.21): substitui os if/else que limitam valores por min e max
As funções integradas min e max são variádicas e funcionam com qualquer tipo ordenado.
Antes:
example.gogox := f() if x < 0 { x = 0 } if x > 100 { x = 100 }
Depois:
example.gogox := max(0, min(100, f()))
rangeint (Go 1.22): ciclos for com inteiros passam a for i := range n
Um ciclo de contagem simples, sem nenhum slice ou array por trás, pode fazer range diretamente sobre o inteiro.
Antes:
example.gogofor i := 0; i < n; i++ { fmt.Println(i) }
Depois:
example.gogofor i := range n { fmt.Println(i) }
forvar (Go 1.22): elimina o truque de captura x := x
Antes do 1.22, a variável do ciclo era partilhada entre iterações, por isso as closures e as goroutines que a capturavam precisavam de x := x dentro do corpo do ciclo. O 1.22 passou a dar a cada iteração a sua própria variável, o que torna essa redeclaração redundante.
Antes:
example.gogofor _, x := range items { x := x // pre-1.22 capture trick go func() { use(x) }() }
Depois:
example.gogofor _, x := range items { go func() { use(x) }() }
O analisador só atua quando o go.mod declara go 1.22 ou posterior, já que os módulos mais antigos mantêm a semântica da variável partilhada.
newexpr (Go 1.26): ponteiro para um valor com new(expr)
O 1.26 alarga a função integrada new para aceitar uma expressão, e não apenas um tipo. new(s) devolve um *string que aponta para uma cópia de s. Isto torna desnecessárias as habituais funções auxiliares strPtr/intPtr/boolPtr.
Antes:
example.gogofunc strPtr(s string) *string { p := s return &p } cfg.Name = strPtr("LevelUpGo")
Depois:
example.gogocfg.Name = new("LevelUpGo")
Se tens funções auxiliares que ainda não podes apagar (outros chamadores, API exportada), vê o inline mais abaixo para conheceres a diretiva que te permite migrar de forma gradual.
inline (diretiva): expande funções e constantes marcadas com //go:fix inline
Adiciona a diretiva a um wrapper e o go fix reescreve todas as chamadas para usarem a expressão interna. Acho-a útil para renomeações e para retirar wrappers finos. Também a podes usar para aplicar os teus próprios modernizadores numa base de código privada.
Antes:
example.gogo//go:fix inline func Square(x int) int { return Pow(x, 2) } a := Square(5) b := Square(10)
Depois:
example.gogo//go:fix inline func Square(x int) int { return Pow(x, 2) } a := Pow(5, 2) b := Pow(10, 2)
A mesma diretiva funciona com constantes, o que a torna útil para migrar entre valores de enum renomeados.
plusbuild (Go 1.17): substitui // +build por //go:build
A sintaxe antiga das build tags está obsoleta desde o 1.17. A nova diretiva usa expressões booleanas normais de Go.
Antes:
example.gogo// +build linux,amd64 package sys
Depois:
example.gogo//go:build linux && amd64 package sys
Slices, maps e iteração
Cinco dos sete modernizadores deste grupo passam ciclos escritos à mão para os pacotes slices e maps. Os outros dois tratam de reflect.TypeFor e das tags JSON omitzero.
slicessort (Go 1.21): sort.Slice passa a slices.Sort
No caso comum de ordenar por ordem crescente um slice de um tipo ordenado, slices.Sort é genérico e dispensa por completo a closure de comparação.
Antes:
example.gogosort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
Depois:
example.gogoslices.Sort(s)
slicescontains (Go 1.21): ciclos de pesquisa manuais passam a slices.Contains
Antes:
example.gogofunc has(s []int, x int) bool { for _, v := range s { if v == x { return true } } return false }
Depois:
example.gogofunc has(s []int, x int) bool { return slices.Contains(s, x) }
slicesbackward (Go 1.23): iteração inversa com slices.Backward
slices.Backward devolve um iterador que produz pares índice/valor do fim do slice até ao início.
Antes:
example.gogofor i := len(s) - 1; i >= 0; i-- { use(i, s[i]) }
Depois:
example.gogofor i, v := range slices.Backward(s) { use(i, v) }
mapsloop (Go 1.21): ciclos de cópia, clonagem e inserção passam a chamadas a maps
O pacote maps tem uma função para cada um dos ciclos sobre maps mais comuns, por isso o nome da chamada diz a quem lê o que o ciclo fazia.
Antes:
example.gogofunc copyMap(src map[string]int) map[string]int { dst := make(map[string]int, len(src)) for k, v := range src { dst[k] = v } return dst }
Depois:
example.gogofunc copyMap(src map[string]int) map[string]int { dst := make(map[string]int, len(src)) maps.Copy(dst, src) return dst }
O mesmo modernizador cobre maps.Clone, maps.Equal e o padrão de inserção a partir de pares.
stditerators (Go 1.23): usa as funções iteradoras da biblioteca padrão em ciclos range
maps.Keys, maps.Values, slices.All, slices.Values e companhia devolvem iteradores que podes percorrer diretamente com range, por isso não precisas de construir primeiro um slice intermédio.
Antes:
example.gogokeys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } for _, k := range keys { use(k) }
Depois:
example.gogofor k := range maps.Keys(m) { use(k) }
reflecttypefor (Go 1.22): elimina o truque de reflect com um ponteiro nil
reflect.TypeFor[T]() é a forma tipada e genérica de obter um reflect.Type para um tipo estático. O velho contorno TypeOf((*T)(nil)).Elem() é anterior aos genéricos.
Antes:
example.gogot := reflect.TypeOf((*MyType)(nil)).Elem()
Depois:
example.gogot := reflect.TypeFor[MyType]()
omitzero (Go 1.24): troca o frágil omitempty por omitzero
omitempty avalia se o valor é zero segundo as regras do Go, o que significa que um time.Time{} não está «vazio» (é um struct com campos) e, por isso, nunca é omitido. omitzero chama o método IsZero do valor quando este existe, pelo que os valores zero de time.Time desaparecem da saída, como seria de esperar.
O analisador só altera os campos em que omitzero é seguro. Strings, slices e ponteiros ficam com omitempty porque o comportamento deles mudaria.
Antes:
example.gogotype User struct { Name string `json:"name,omitempty"` Created time.Time `json:"created,omitempty"` }
Depois:
example.gogotype User struct { Name string `json:"name,omitempty"` Created time.Time `json:"created,omitzero"` }
Strings e formatação
Estes cinco substituem padrões antigos de strings e fmt por APIs mais recentes. Alguns poupam uma alocação e outros são simplesmente mais fáceis de ler.
fmtappendf (Go 1.19): []byte(fmt.Sprintf(...)) passa a fmt.Appendf
fmt.Appendf escreve a saída formatada diretamente num []byte que tu forneces (ou nil para uma nova alocação), sem passar pela string intermédia.
Antes:
example.gogob := []byte(fmt.Sprintf("%s=%d", key, val))
Depois:
example.gogob := fmt.Appendf(nil, "%s=%d", key, val)
stringscut (Go 1.18): strings.Index com slicing manual passa a strings.Cut
strings.Cut devolve o prefixo, o sufixo e um bool que indica se encontrou o separador, tudo numa só chamada. Também trata de forma explícita o caso em que não o encontra.
Antes:
example.gogoi := strings.Index(s, "=") if i >= 0 { key, val := s[:i], s[i+1:] use(key, val) }
Depois:
example.gogoif key, val, ok := strings.Cut(s, "="); ok { use(key, val) }
stringscutprefix (Go 1.20): HasPrefix mais TrimPrefix passa a CutPrefix
CutPrefix devolve a string já sem o prefixo e um bool que indica se o encontrou, por isso só verificas o prefixo uma vez.
Antes:
example.gogoif strings.HasPrefix(s, "go-") { rest := strings.TrimPrefix(s, "go-") handle(rest) }
Depois:
example.gogoif rest, ok := strings.CutPrefix(s, "go-"); ok { handle(rest) }
O mesmo modernizador trata também de CutSuffix.
stringsseq (Go 1.24): Split e Fields em ciclos range passam a SplitSeq e FieldsSeq
SplitSeq e FieldsSeq devolvem iteradores em vez de alocarem um slice. Se só precisas de percorrer as partes uma vez, a forma com iterador evita por completo essa alocação.
Antes:
example.gogofor _, line := range strings.Split(s, "\n") { handle(line) }
Depois:
example.gogofor line := range strings.SplitSeq(s, "\n") { handle(line) }
O analisador só reescreve os casos em que o slice é consumido uma vez e descartado. Se guardas o slice ou acedes a ele por índice, a forma original com Split mantém-se.
stringsbuilder (Go 1.10): concatenação quadrática em ciclos passa a strings.Builder
s += p dentro de um ciclo realoca e copia em cada iteração, o que resulta em trabalho O(n²). strings.Builder cresce como um slice.
Antes:
example.gogos := "" for _, p := range parts { s += p } return s
Depois:
example.gogovar sb strings.Builder for _, p := range parts { sb.WriteString(p) } return sb.String()
Aqui o analisador é conservador. Só atua perante o padrão acumulador óbvio, e não em todos os += dentro de um ciclo. Se o teu ciclo concatena duas ou três constantes pequenas, a forma simples serve perfeitamente.
Redes, erros e reflexão
Este grupo tem três: uma armadilha antiga do net, o novo errors.AsType genérico e as funções auxiliares mais seguras do unsafe.
hostport (Go 1.0): Sprintf para endereços passa a net.JoinHostPort
fmt.Sprintf("%s:%d", host, port) constrói um endereço inválido quando host é um literal IPv6, já que os endereços IPv6 têm os seus próprios dois pontos. net.JoinHostPort coloca os hosts IPv6 entre parênteses retos e é a forma canónica de construir uma string host:port.
Antes:
example.gogoaddr := fmt.Sprintf("%s:%d", host, port) net.Dial("tcp", addr)
Depois:
example.gogoaddr := net.JoinHostPort(host, strconv.Itoa(port)) net.Dial("tcp", addr)
errorsastype (Go 1.26): errors.As passa ao errors.AsType genérico
O 1.26 acrescenta errors.AsType[T], que devolve o erro tipado e um bool, em vez de te obrigar a declarar uma variável de destino e a passar o seu endereço.
Antes:
example.gogovar myerr *MyErr if errors.As(err, &myerr) { handle(myerr) }
Depois:
example.gogoif myerr, ok := errors.AsType[*MyErr](err); ok { handle(myerr) }
unsafefuncs (Go 1.17): aritmética com unsafe passa a unsafe.Add e unsafe.Slice
O 1.17 acrescentou unsafe.Add e unsafe.Slice para poderes fazer aritmética de ponteiros e construir slices sobre memória arbitrária sem teres de escrever à mão toda a dança com uintptr.
Antes (base é um unsafe.Pointer):
example.gogop := unsafe.Pointer(uintptr(base) + uintptr(off))
Depois:
example.gogop := unsafe.Add(base, off)
O mesmo modernizador reescreve os padrões manuais com unsafe.Pointer e reflect.SliceHeader para usarem unsafe.Slice.
Concorrência e testes
Os últimos quatro arrumam código que usa sync, atomic e o pacote testing.
waitgroupgo (Go 1.25): Add(1) mais defer Done() passa a wg.Go
O 1.25 acrescentou WaitGroup.Go, que junta o Add, o lançamento da goroutine e o defer Done numa só chamada.
Antes:
example.gogovar wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() fmt.Println("go!") }() wg.Wait()
Depois:
example.gogovar wg sync.WaitGroup wg.Go(func() { fmt.Println("go!") }) wg.Wait()
atomictypes (Go 1.19): atómicos em estilo de função passam a estilo de método
atomic.Int64, atomic.Uint64, atomic.Pointer[T] e companhia guardam o seu próprio valor, por isso deixas de andar a passar ponteiros de um lado para o outro e de errar ao fazer corresponder o tamanho da operação ao tipo da variável.
Antes:
example.gogovar count int64 atomic.AddInt64(&count, 1) n := atomic.LoadInt64(&count)
Depois:
example.gogovar count atomic.Int64 count.Add(1) n := count.Load()
testingcontext (Go 1.24): WithCancel manual em testes passa a t.Context()
t.Context() devolve um context que é cancelado automaticamente quando o teste termina. Deixas de ter de te lembrar do defer cancel(), e o context propaga-se aos subtestes.
Antes:
example.gogofunc TestThing(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() work(ctx) }
Depois:
example.gogofunc TestThing(t *testing.T) { ctx := t.Context() work(ctx) }
bloop (Go 1.24): for i := 0; i < b.N; i++ passa a for b.Loop()
b.Loop() é o novo ciclo de benchmark. Também trata automaticamente de reiniciar e parar o temporizador, o que significa que as chamadas manuais a b.ResetTimer() e b.StopTimer() à volta da preparação desaparecem na maioria dos benchmarks.
Antes:
example.gogofunc BenchmarkThing(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { work() } }
Depois:
example.gogofunc BenchmarkThing(b *testing.B) { for b.Loop() { work() } }
Como executar o go fix em segurança
O go fix reescreve ficheiros de código-fonte reais. Trata-o como qualquer outra refatoração automática.
Uma ordem de operações segura:
- Começa numa branch limpa, sem outras alterações em curso.
- Corre primeiro
go fix -diff ./.... A flag imprime um diff unificado para o stdout em vez de escrever ficheiros. Dá-lhe uma vista de olhos. - Corre
go fix ./...para aplicar as alterações. - Corre
git diffsobre as alterações reais. Procura qualquer reescrita que te surpreenda, sobretudo em código que foi escrito de propósito num estilo mais antigo. - Corre os teus testes, o teu linter e
go build ./.... - Faz commit.
Uma só execução muitas vezes não chega
Aplicar uma correção pode abrir caminho a outra, e os analisadores não perseguem essa cascata dentro de uma só passagem.
O limite da secção do minmax mostra isto. Começa com:
example.gogox := f() if x < 0 { x = 0 } if x > 100 { x = 100 }
A primeira execução reescreve o limite inferior com max:
example.gogox := max(0, f()) if x > 100 { x = 100 }
A segunda execução vê o padrão if/atribuição que resta e termina o trabalho:
example.gogox := min(100, max(0, f()))
São precisas duas passagens porque cada modernizador é um analisador independente que olha para o teu código uma vez por cada execução do go fix. Percorre a árvore sintática, encontra os padrões que conhece e emite correções para eles.
No exemplo do limite, a primeira passagem reescreve o bloco < 0. Depois dessa alteração, o bloco > 100 vem a seguir a uma chamada a max(...) em vez de um simples x := f(), o que é uma nova oportunidade para o minmax. Mas o minmax já fez a sua única passagem, por isso só vai ver a nova forma quando voltares a correr o go fix.
O mesmo acontece entre analisadores. Uma reescrita do mapsloop pode abrir caminho a uma do stditerators, porque o analisador de iteradores procurava uma forma range maps.Keys(m) que não existia antes de o ciclo ser reescrito.
A solução é voltar a correr go fix ./... até o diff vir vazio. Duas passagens chegam para a maioria das bases de código, e uma terceira é rara.
Outras notas
- Os modernizadores dependentes da versão (
forvar,rangeint,newexpr,errorsastype) só atuam se ogo.moddeclarar a versão mínima certa. Se não vês a reescrita que esperavas, verifica a tua diretivago. - Para migrações específicas da tua base de código, usa a diretiva
//go:fix inline. Marca um wrapper ou uma constante, corrego fixe todas as chamadas são atualizadas no próprio sítio. - Se uma reescrita altera o comportamento de uma forma que o analisador não detetou, isso é um bug do analisador e vale a pena reportá-lo. A equipa do Go faz a triagem desses casos rapidamente.
Onde aprender mais
Para a cobertura completa da versão Go 1.26 (novas APIs de criptografia, Green Tea GC, deteção de fugas de goroutines e o resto), consulta o guia de novidades do Go 1.26.
Nas fontes originais, a documentação de x/tools/go/analysis/passes/modernize cobre cada analisador em detalhe, e o artigo do blog do Go sobre o go fix explica o novo framework.
