Go 1.27 añade cinco paquetes nuevos a la biblioteca estándar (encoding/json/v2, encoding/json/jsontext, crypto/mldsa, uuid y simd) y tres cambios en el lenguaje. Algunas cosas las vas a querer desde el primer día, como los métodos genéricos y un paquete uuid que elimina una dependencia de la mayoría de los archivos go.mod. Otras harán fallar tus tests antes de que sepas siquiera que la novedad existe: compress/flate genera bytes distintos, los literales de función reciben otros nombres de símbolo y un puñado de vías de escape de GODEBUG ahora rompen la compilación en lugar de restaurar el comportamiento anterior.
Índice
- Métodos genéricos y lo único que todavía no pueden hacer
- Un paquete uuid en la biblioteca estándar
- ¿Tienes que cambiar algo con encoding/json/v2?
- Firmas poscuánticas con crypto/mldsa
- El paquete experimental simd
- Rendimiento que ganas solo con actualizar
- Detectar fugas de goroutines y leer tracebacks con etiquetas
- Mejoras menores del lenguaje y la biblioteca
- ¿Qué cambió en el toolchain de Go?
- Testing y net/http
- ¿Qué se rompe al actualizar a Go 1.27?
- Preguntas frecuentes
- Fuentes
- Sigue aprendiendo
Métodos genéricos y lo único que todavía no pueden hacer
Desde que los genéricos llegaron en Go 1.18, los parámetros de tipo funcionaban exactamente en dos sitios: las funciones de nivel superior y las declaraciones de tipos. Los métodos quedaban fuera, así que si querías una transformación que cambiara el tipo de los elementos de tu colección, tenías que escribirla como función a nivel de paquete, y las funciones a nivel de paquete no se pueden encadenar.
Seguramente has escrito algo parecido a esto. Un pipeline de métricas que convierte lecturas de sensores en etiquetas para mostrar necesita dos transformaciones, y cada una tiene que envolver la llamada anterior:
example.gogotype Metric struct { Sensor string Celsius int } type List[T any] []T // Package-level, because a method couldn't declare U. func Map[T, U any](l List[T], f func(T) U) List[U] { out := make(List[U], len(l)) for i, v := range l { out[i] = f(v) } return out } func main() { metrics := List[Metric]{ {Sensor: "cpu0", Celsius: 61}, {Sensor: "cpu1", Celsius: 58}, } temps := Map(metrics, func(m Metric) int { return m.Celsius }) labels := Map(temps, func(c int) string { return fmt.Sprintf("%dC", c) }) fmt.Println(labels) // [61C 58C] }
En Go 1.27, Map pasa a ser un método de verdad que declara su propio parámetro de tipo, independiente del receptor:
example.gogo// U is the method's own type parameter, new in Go 1.27. func (l List[T]) Map[U any](f func(T) U) List[U] { out := make(List[U], len(l)) for i, v := range l { out[i] = f(v) } return out } func main() { metrics := List[Metric]{ {Sensor: "cpu0", Celsius: 61}, {Sensor: "cpu1", Celsius: 58}, } labels := metrics. Map(func(m Metric) int { return m.Celsius }). Map(func(c int) string { return fmt.Sprintf("%dC", c) }) fmt.Println(labels) // [61C 58C] }
Poder encadenar es la mejora evidente. La menos evidente tiene que ver con los nombres: MapSlice, MapSet y MapBox se reducen a un único Map por tipo, porque el receptor ya indica a qué tipo te refieres. El receptor también ayuda al compilador: List[Metric] fija T, así que solo queda por inferir el tipo de salida. Y los métodos aparecen cuando escribes un punto en tu editor. Un helper a nivel de paquete solo te sirve si ya sabes que existe.
La propia biblioteca estándar usa la novedad. math/rand/v2 ahora declara (*Rand) N[Int intType](Int) Int como método junto a la función N a nivel de paquete, algo que las reglas anteriores no permitían.
Hay un límite, y es fácil pasarlo por alto. Según las notas de la versión:
Así que esto no compila:
example.gogotype Transformer interface { // Compile error: interface methods can't declare type parameters. Map[U any](f func(Metric) U) any }
El despacho de interfaces ocurre en tiempo de ejecución, así que el compilador no puede saber qué instanciaciones de un método genérico debe generar para una llamada con despacho dinámico. Si tu API se basa en una interfaz, las transformaciones siguen necesitando la antigua función a nivel de paquete. Que los métodos genéricos le sirvan a tu código depende sobre todo de esto, así que revisa tus interfaces antes de planificar una refactorización. Si quieres practicar, el Go Generics Masterclass cubre las restricciones y la inferencia con ejercicios en el navegador.
Veredicto: adóptalo ya para tipos concretos. No toques las APIs basadas en interfaces.
Un paquete uuid en la biblioteca estándar
Casi todos los servicios de Go que hablan con una base de datos importan github.com/google/uuid. Go 1.27 añade un paquete a la biblioteca estándar con la ruta de import "uuid", siguiendo la propuesta #62026.
UUID se define como [16]byte, lo que significa que los valores se pueden comparar con == y usar directamente como claves de un map. Incluye tres generadores:
example.gogopackage main import ( "fmt" "uuid" ) func main() { fmt.Println(uuid.New()) // reach for this when you don't care how it's made fmt.Println(uuid.NewV4()) // 128 bits, 122 of them random fmt.Println(uuid.NewV7()) // 48-bit timestamp first, so values sort by creation time requestID, err := uuid.Parse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6") if err != nil { return } fmt.Println(requestID, requestID == uuid.Nil()) // false, it parsed fine }
New es la opción por defecto y hoy devuelve un V4. NewV4 es completamente aleatorio, así que nadie puede adivinar el siguiente. Eso es lo que quieres en un ID de petición público. NewV7 coloca delante una marca de tiempo de 48 bits, de modo que los valores más nuevos se ordenan después de los antiguos, lo que los convierte en buenas claves primarias porque las inserciones caen cerca del final del índice en lugar de repartirse por todo él.
Ten en cuenta que Nil() y Max() son funciones, así que la comparación es id == uuid.Nil() con paréntesis. Nil() sirve como centinela para «todavía sin asignar». Max() es el valor con todos los bits a uno, que resulta útil como límite superior cuando recorres un rango de claves V7 ordenadas por tiempo.
Veredicto: adóptalo ya en código nuevo. Migrar un servicio existente es cuestión de buscar y reemplazar, pero lee las preguntas frecuentes antes de eliminar la dependencia.
¿Tienes que cambiar algo con encoding/json/v2?
No. Ese era el objetivo del diseño. encoding/json ahora se apoya en la implementación v2, y las notas de la versión lo dicen claramente: «El comportamiento de serialización y deserialización se mantiene, pero el texto exacto de los mensajes de error puede variar.» Obtienes la velocidad del nuevo motor sin cambiar nada de código. Según las notas de la versión, «el rendimiento de Marshal está en general a la par con la implementación anterior, mientras que el de unmarshal es significativamente más rápido.»
Solo optas por el nuevo comportamiento si importas encoding/json/v2 por su nombre. Ese paquete tiene valores por defecto más estrictos, elegidos para coincidir con lo que esperan otras implementaciones de JSON: rechaza UTF-8 no válido en las cadenas JSON, rechaza nombres duplicados dentro de un objeto JSON, compara los nombres de campo distinguiendo mayúsculas y minúsculas, serializa un slice nil como [] en lugar de null y serializa los maps en un orden no determinista, donde v1 garantizaba uno determinista. El cambio en los slices nil es la distinción entre nil y vacío de var vs make en Go, y con v2 puedes verla en la salida.
example.gogoimport "encoding/json/v2" type Event struct { ID string `json:"id"` Action string `json:"action"` } // v1 would match the key "ID" against the tag `json:"id"`. v2 won't, // and it skips the mismatched key silently rather than erroring. var e Event json.Unmarshal([]byte(`{"ID":"evt_01H","action":"checkout"}`), &e) fmt.Println(e.ID, e.Action) // "" checkout // v2 marshals maps in a non-deterministic order. Ask for a stable one // explicitly when you hash or snapshot the output. inventory := map[string]int{"widget": 12, "gadget": 3, "gizmo": 7} b, _ := json.Marshal(inventory, json.Deterministic(true))
Para recuperar un comportamiento de v1 con v2, no cambias de import. Pasas una opción para ese comportamiento concreto: Deterministic(true) para un orden estable en los maps, MatchCaseInsensitiveNames(true) para una coincidencia flexible de campos, FormatNilSliceAsNull(true) para obtener null en lugar de []. El paquete v1 incorporó las mismas opciones, así que puedes adoptar la semántica de v2 comportamiento a comportamiento sin hacer una migración completa. La lista completa está en la sección Migrating to v2 de la documentación del paquete v1.
El tercer paquete, encoding/json/jsontext, se ocupa de la sintaxis a más bajo nivel. Expone el JSON como una secuencia de tokens y valores, con una máquina de estados que mantiene la salida válida. Los códecs de streaming se construyen sobre él. La mayoría del código de aplicación nunca lo importa directamente.
Para decidir cuál importar:
flowchart TD A["Which JSON import?"] --> B{"Upgrading existing code?"} B -- Yes --> C["encoding/json<br/>v2 engine, v1 behavior"] B -- No --> D{"Want strict defaults<br/>and faster decoding?"} D -- No --> C D -- Yes --> E["encoding/json/v2"] E --> F{"Need raw tokens<br/>or streaming syntax?"} F -- Yes --> G["encoding/json/jsontext"] F -- No --> H["You're done"]
Árbol de decisión para elegir entre encoding/json, encoding/json/v2 y encoding/json/jsontext en Go 1.27.
Veredicto: adopta el motor por defecto ya (en realidad ya lo tienes) y adopta v2 por su nombre más adelante, cuando hayas leído las notas de migración que afectan a tus struct tags. El curso Data Formats cubre las reglas de las etiquetas que deciden cuánto te afecta todo esto.
Firmas poscuánticas con crypto/mldsa
RSA y ECDSA son seguros hoy porque ninguna computadora puede factorizar números grandes ni resolver logaritmos discretos lo bastante rápido. Una computadora cuántica lo bastante grande sí podría. crypto/mldsa implementa ML-DSA de FIPS 204 con tres conjuntos de parámetros, 44, 65 y 87, siguiendo la propuesta #77626.
Otros paquetes ya lo admiten. crypto/x509 analiza y verifica claves y firmas ML-DSA, y crypto/tls acepta los tres conjuntos de parámetros en un handshake de TLS 1.3. MLKEM1024 se suma a los intercambios de claves admitidos para un acuerdo de claves resistente a la computación cuántica, y se activa añadiéndolo a Config.CurvePreferences.
Firma y verificación completas de una versión de firmware:
example.gogosk, err := mldsa.GenerateKey(mldsa.MLDSA65()) if err != nil { return err } firmware, err := os.ReadFile("firmware-v2.1.0.bin") if err != nil { return err } // Context is a domain-separation label. Sign and verify must pass the same one. opts := &mldsa.Options{Context: "acme/firmware-release"} sig, err := sk.Sign(nil, firmware, opts) if err != nil { return err } fmt.Println(mldsa.Verify(sk.PublicKey(), firmware, sig, opts) == nil) // true fmt.Println(mldsa.MLDSA65().SignatureSize()) // 3309 firmware[0] ^= 1 // flip one bit fmt.Println(mldsa.Verify(sk.PublicKey(), firmware, sig, opts) == nil) // false
Verify devuelve nil cuando la firma es válida, así que compararlo con nil te da un booleano. La desventaja es el tamaño, y la diferencia es grande:

Fuente: tamaños de firma según FIPS 204 (ML-DSA) y las especificaciones de cada algoritmo.
Una firma ML-DSA-65 ocupa unas 46 veces más que una de Ed25519, así que solo merece la pena donde la firma tiene que durar. El firmware y los artefactos de publicación en los que un dispositivo tendrá que seguir confiando dentro de diez años valen 3 KB. También los valen las raíces de certificados pensadas para sobrevivir a la criptografía actual y los servidores TLS a los que preferirías no cambiarles las claves cuando lleguen las computadoras cuánticas. Para tokens de sesión de corta duración que emites a miles por segundo, el tamaño extra es una sobrecarga que todavía no necesitas.
Un cambio de crypto/x509 que no tiene relación con esto cae en la misma área. SystemCertPool ahora respeta SSL_CERT_FILE y SSL_CERT_DIR en Windows y Darwin, no solo en Linux. Cuando están definidas, Go carga las raíces desde disco y usa su propio verificador en lugar de las APIs de la plataforma. Define GODEBUG=x509sslcertoverrideplatform=0 para mantener el comportamiento anterior.
Veredicto: adóptalo para firmas de larga duración e ignóralo para todo lo demás.
El paquete experimental simd
Una instrucción normal de CPU trabaja con un solo valor. Una instrucción SIMD trabaja con un vector entero de valores en un solo paso, así que el mismo resultado requiere muchas menos instrucciones. Eso ayuda cuando aplicas la misma operación matemática sobre un slice grande de muestras de audio o de píxeles de imagen, o cuando calculas un producto escalar. El código que sobre todo mueve structs y strings no se beneficia.
Cada arquitectura expone SIMD de forma distinta. El nuevo paquete simd es portable y no presupone un tamaño de vector. Está disponible en todas las arquitecturas y usa instrucciones de hardware donde existen. Actívalo con GOEXPERIMENT=simd al compilar. Es experimental, así que la API puede cambiar.
Mezcla de dos pistas de audio, varias muestras por paso:
example.gogo//go:build goexperiment.simd // out[i] = trackA[i] + trackB[i], several samples at a time. out := make([]float32, len(trackA)) lanes := simd.LoadFloat32s(trackA).Len() // how many float32s fit in one vector for i := 0; i+lanes <= len(trackA); i += lanes { va := simd.LoadFloat32s(trackA[i:]) vb := simd.LoadFloat32s(trackB[i:]) va.Add(vb).Store(out[i:]) } // A plain scalar loop handles the leftover tail.
Len indica cuántos elementos caben en un vector en la máquina donde se ejecuta el programa, así que el bucle nunca tiene que fijar a mano un ancho de vector. Cada paso carga esa cantidad de muestras de ambas pistas, las suma elemento a elemento en una sola operación y guarda el resultado.
Veredicto: ignóralo por ahora, salvo que ya estés perfilando un bucle numérico crítico. La API está detrás de un GOEXPERIMENT y puede cambiar.
Rendimiento que ganas solo con actualizar
Algunas mejoras de velocidad no requieren nada más que recompilar. La más grande es la asignación de memoria especializada por tamaño, que las notas de la versión describen así:

Fuente: notas de la versión de Go 1.27, asignación de memoria más rápida.
Esas dos cifras miden cosas distintas. El 30 % es el coste de una sola llamada de asignación pequeña. El 1 % aproximado es lo que nota un servicio completo. Si te encuentras con una regresión, GOEXPERIMENT=nosizespecializedmalloc la desactiva, y se espera que esa vía de escape desaparezca en Go 1.28.
Además, tres optimizaciones del compilador vienen activadas por defecto. Un análisis de flujo de datos de bits conocidos rastrea qué bits de un valor son con certeza 0 o 1 y elimina la redundancia resultante. El movimiento de código invariante de bucle saca del cuerpo del bucle los cálculos cuyo resultado nunca cambia, de modo que se ejecutan una vez en lugar de en cada iteración. Y las sentencias switch ahora se compilan como tablas de búsqueda cuando los casos lo permiten, también con fallthrough, y saltan directamente al caso que coincide en lugar de probar cada uno.
compress/flate también va más rápido, con una salvedad: la salida codificada exacta puede diferir de la de Go 1.26. DEFLATE está por debajo de archive/zip, compress/gzip, compress/zlib e image/png, así que los tests golden que comparan byte a byte la salida de cualquiera de ellos pueden fallar. La salida sigue siendo correcta, así que regenera los fixtures.
Veredicto: adóptalo ya. Te llega al actualizar de todos modos.
Detectar fugas de goroutines y leer tracebacks con etiquetas
El perfil goroutineleak ha pasado de experimento a disponibilidad general, y el GOEXPERIMENT goroutineleakprofile desaparece. Se expone a través de runtime/pprof y como el endpoint /debug/pprof/goroutineleak de net/http/pprof.
Una goroutine fugada es una goroutine bloqueada en una primitiva de concurrencia que nunca podrá desbloquearse. El runtime las encuentra mediante el recolector de basura: si la goroutine G está bloqueada en la primitiva P, y P no es alcanzable desde ninguna goroutine ejecutable ni desde nada que estas pudieran desbloquear, entonces G nunca podrá despertar.
example.gogofunc startJob() { result := make(chan int) // unbuffered: the send waits for a receiver go func() { result <- expensiveWork() // blocks forever, nobody receives }() // returns without receiving, so result becomes unreachable } func main() { startJob() runtime.GC() // the detector scans during a GC cycle pprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1) // goroutineleak profile: total 1 // ... main.startJob.func1 ... main.go:14 }
Este enfoque tiene un punto ciego. El runtime puede pasar por alto fugas en las que la primitiva bloqueante es alcanzable a través de una variable global o de las variables locales de una goroutine ejecutable. Un channel guardado en un registro a nivel de paquete cuenta como alcanzable, así que el perfil no lo detecta. Concurrency Fundamentals repasa los patrones de fuga que los producen y cómo cerrarlos con context.
La segunda mejora de depuración no requiere ningún código. En los módulos cuya directiva go sea 1.27 o posterior, los tracebacks ahora incluyen las etiquetas de goroutine de runtime/pprof en la línea de cabecera. Las etiquetas que ya pones para el profiling aparecen en los volcados de fallos, en las trazas de SIGQUIT y en la salida de runtime.Stack. Cuando dos goroutines tienen pilas idénticas, la etiqueta suele ser la única forma de distinguirlas:
example.gogopprof.Do(ctx, pprof.Labels("request", id), func(ctx context.Context) { work() }) // goroutine 34 [running]: // labels: {"request":"req-42"} // main.handle.func1 ...
Define GODEBUG=tracebacklabels=0 para desactivarlo si tus etiquetas llevan información que no quieres ver en un volcado de fallos.
Veredicto: adóptalo ya. Ninguna de las dos te cuesta nada.
Mejoras menores del lenguaje y la biblioteca
Selectores de campo en literales de struct
Una clave en un literal de struct ahora puede ser cualquier selector de campo válido para el tipo, no solo el nombre de un campo de primer nivel (propuesta #9859). Si embebes un modelo compartido, ya no tienes que escribir el literal anidado:
example.gogotype Model struct { ID int64 CreatedAt time.Time } type Post struct { Model Author string Likes int } p := Post{Model: Model{ID: 42}, Author: "Patrik"} // before p := Post{ID: 42, Author: "Patrik"} // Go 1.27
Inferencia generalizada de tipos de función
La inferencia de tipos de función ahora se aplica en cualquier contexto en el que una función genérica se asigna a un tipo de función compatible o se convierte a él (propuesta #77245). Antes solo funcionaba con una declaración de variable con tipo:
example.gogofunc ascending[T cmp.Ordered](a, b T) int { return cmp.Compare(a, b) } func descending[T cmp.Ordered](a, b T) int { return cmp.Compare(b, a) } orders := []func(int, int) int{ascending[int], descending[int]} // before orders := []func(int, int) int{ascending, descending} // Go 1.27
El tipo de los elementos del slice es func(int, int) int, así que Go infiere que T es int. Lo mismo funciona ahora con las conversiones y al pasar una función genérica sin instanciar como argumento.
strings.CutLast y bytes.CutLast
Cut divide por el primer separador. CutLast divide por el último y devuelve la parte anterior, la parte posterior y si encontró el separador (propuesta #71151):
example.gogodir, file, found := strings.CutLast("internal/store/user.go", "/") fmt.Println(dir, file, found) // internal/store user.go true
Antes llamabas a LastIndex, comprobabas si devolvía -1 y cortabas dos veces a mano.
Rand.N, maphash.Hasher y big.Int.Divide
math/rand/v2 incorpora Rand.N como método genérico (propuesta #77853), así que puedes obtener un valor aleatorio acotado de una fuente con tu propia semilla en lugar de la global, que no admite semilla. Así un test que falla se repite con las mismas entradas.
hash/maphash añade una interfaz genérica Hasher[T] para estructuras basadas en hash, como maps personalizados y filtros de Bloom (propuesta #70471). Combina una función hash con una comprobación de igualdad, y los valores iguales deben producir el mismo hash. ComparableHasher[T] es la implementación lista para usar en tipos comparables y compara con ==. go/types ya la usa e incluye un Hasher que respeta la relación Identical.
math/big.Int recibe un método Divide que calcula el cociente y el resto a la vez con el modo de redondeo que elijas: Trunc, Floor, Round o Ceil. Quo y Mod siempre truncan hacia cero, así que el código financiero y numérico que necesita otro redondeo ahora tiene una opción integrada.
Unicode 17, database/sql y el resto
El paquete unicode y todo lo que se apoya en él pasan directamente de Unicode 15 a Unicode 17, así que los caracteres añadidos en las dos versiones intermedias ahora se clasifican correctamente. database/sql incorpora ConvertAssign, que permite a los drivers reutilizar las conversiones de tipos que hace Rows.Scan en lugar de escribir las suyas.
El resto son cambios pequeños:
cryptoañade el valor hashMLDSAMu, un mecanismo de señalización para la firma ML-DSA con external mu.crypto/ecdsaahora comprueba que la longitud del hash sea correcta enPrivateKey.Signcuando pasas unasSignerOptsdistintas de nil.crypto/x509exponeRawSignatureAlgorithmenCertificate,CertificateRequestyRevocationList, lo que te da el AlgorithmIdentifier codificado en DER incluso cuandoSignatureAlgorithmesUnknownSignatureAlgorithm. El análisis apkix.Nametambién acepta más tipos de valor, y los desconocidos acaban enasn1.RawValue.crypto/tlsañadeConnectionState.LocalCertificate, la cadena que presentaste al otro extremo, además deQUICConfig.ClientHelloInfoConn.Config.Randqueda obsoleto en favor detesting/cryptotest.SetGlobalRandompara tests deterministas.nethace que los métodos de lectura deUnixConndevuelvanio.EOFdirectamente en lugar de envolverlo en unnet.OpError.runtime/secretpropaga el modo secreto a las goroutines creadas dentro de él.go/constantañadeStringLen,go/scannerañadeScanner.Endygo/tokenle da aFileun métodoString.- En cuanto a plataformas, el port de PowerPC de 64 bits big-endian en Linux pasa a la ABI de sistema ELFv2, lo que habilita cgo, los ejecutables independientes de la posición y el enlazado externo en esa plataforma, y requiere un kernel Linux 3.13 o posterior. En Plan 9,
syscall.Errnoya está definido e implementaerror, así que el código portable que lo usa compila sin restricciones de compilación. El linker también acepta-macosy-macsdkpara fijar las versiones que se escriben en el load commandLC_BUILD_VERSIONde macOS.
¿Qué cambió en el toolchain de Go?
go fix incorpora cuatro modernizadores. embedlit simplifica las referencias a campos embebidos en literales compuestos, algo que hace posible la nueva regla de literales de struct. atomictypes sustituye los tipos básicos en las llamadas a sync/atomic por tipos atómicos, slicesbackward reescribe los bucles hacia atrás con slices.Backward y unsafefuncs sustituye la aritmética de punteros unsafe por llamadas a funciones. Con ellos llegaron dos cambios de mantenimiento: fmtappendf se eliminó por cuestiones de estilo y waitgroup pasó a llamarse waitgroupgo. Ejecútalo una vez después de subir la directiva go:
example.bashbashgo fix -diff ./... # preview go fix ./... # apply
Para ver el catálogo completo de lo que reescribe go fix, consulta todos los modernizadores de go fix 1.26.
go test ahora ejecuta por defecto la comprobación de vet stdversion. Esta informa de los símbolos de la biblioteca estándar que son más nuevos que la versión de Go vigente para el archivo, según la directiva de tu go.mod y las build tags. Si tu módulo declara go 1.25 y alguien usa strings.CutLast, la ejecución de los tests lo detecta antes que un usuario con 1.25. go test -json también anota las líneas "Action":"output" con un campo opcional "OutputType", que por ahora vale error, error-continue o frame, lo que resulta útil si analizas la salida de los tests en CI.
go doc ahora acepta la sintaxis package@version, así que go doc rsc.io/[email protected] muestra la documentación de una versión exacta sin tener que hacer checkout. Un nuevo flag -ex lista los ejemplos ejecutables de un paquete, y si nombras uno imprime su código fuente.
go mod tidy impone una estructura de dos bloques en los módulos con go 1.27 o posterior y fusiona los bloques require dispersos en un bloque directo y otro indirecto. Los bloques de comentarios asociados a dependencias se conservan, y un comentario que abarca un conjunto mixto pasa al bloque directo.
go tool trace -http=:6060 ahora escucha solo en localhost, igual que go tool pprof, así que pasa una dirección completa como -http=0.0.0.0:6060 si necesitas acceder desde otra máquina. Las herramientas compile, link, asm, cgo, cover y pack también aceptan archivos de respuesta (@file) en un formato compatible con GCC, lo que ayuda a los sistemas de build que superan los límites de longitud de la línea de comandos.
Testing y net/http
httptest.NewTestServer crea un Server sobre una red falsa en memoria, pensada para usarse con testing/synctest, así que no interviene ningún puerto TCP real. Su complemento es synctest.Sleep, que hace time.Sleep y synctest.Wait en una sola llamada: avanza el reloj falso y luego deja que las goroutines se estabilicen. Con los dos, los tests HTTP dejan de depender de los tiempos. Professional Go Testing cubre el modelo de synctest en el que se basan.
example.gogosrv := httptest.NewTestServer(t, handler) // in-memory, cleanup auto-registered
En el lado de net/http, el servidor HTTP/2 ahora acepta las señales de prioridad del cliente definidas en RFC 9218 y sirve primero los streams de mayor prioridad. Define Server.DisableClientPriority = true para volver al reparto round-robin anterior.
El cambio que más probablemente notes está en HTTP/1: cerrar un Response.Body leído a medias ahora consume el contenido restante hasta un límite conservador para que la conexión se pueda reutilizar. Para la mayoría de los programas no cambia nada o supone una pequeña mejora de velocidad. Si cierras antes de tiempo para abortar una descarga grande, Transport.DisableKeepAlives = true lo desactiva.
Server.MaxHeaderValueCount limita cuántos valores puede llevar una misma cabecera, lo que protege contra las peticiones que inundan una cabecera. Transport y Server pueden negociar TLS ALPN sobre un net.Conn que proporciones tú mismo, siempre que implemente ConnectionState() tls.ConnectionState, así que HTTP/2 también se activa en conexiones tunelizadas o que pasan por un proxy. Y net/url incorpora URL.Clone y Values.Clone para hacer copias profundas. HTTP and Networking cubre los ajustes del servidor y del transporte sobre los que se construyen estos cambios.
Veredicto: adóptalo ya. Si tienes una batería de tests HTTP inestable, httptest más synctest es motivo suficiente para actualizar.
¿Qué se rompe al actualizar a Go 1.27?
Las notas de la versión reparten los cambios incompatibles en ocho secciones. Esta tabla los reúne, ordenados según la probabilidad de que te los encuentres.
| Qué cambia | A quién afecta | Qué hacer |
|---|---|---|
compress/flate genera bytes distintos | Tests golden que comparan byte a byte la salida de gzip, zlib, zip o png | Regenera los fixtures. La compresión es correcta, solo distinta. |
| Nombres más simples para los literales de función (closures) | Tests que comprueban nombres de símbolos y código que compara por igualdad los punteros al código de funciones | Deja de depender de los nombres de los literales. La comparación de punteros a funciones ya estaba documentada como poco fiable. |
Los GODEBUG asynctimerchan y gotypesalias se eliminan de forma permanente | Cualquier cosa que los fije al valor antiguo en go.mod o en una línea //go:debug | Ejecuta grep -r asynctimerchan . antes de actualizar. Fijar el valor por defecto final sigue compilando, pero fijar el valor antiguo falla. |
Se eliminan cinco GODEBUG de TLS/x509: tlsunsafeekm, tlsrsakex, tls3des, tls10server, x509keypairleaf | Servicios que siguen fijados al comportamiento TLS heredado | La misma regla de arriba: el comando go acepta el valor por defecto final y rechaza el antiguo, así que los detectas al compilar. |
| macOS 13 Ventura es la versión mínima | Runners de CI y máquinas de desarrollo con versiones anteriores de macOS | Actualiza la imagen del runner. Se anunció en las notas de Go 1.26. |
Se elimina el soporte de bzr del comando go | Módulos alojados en servidores Bazaar | Crea un mirror de la dependencia o haz vendoring. |
La nueva directiva //go:linknamestd marca los linknames exclusivos de std, el linker ahora comprueba el acceso por linkname a símbolos de ensamblador y los descriptores de tipo pasan a una sección .go.type | Paquetes que acceden al runtime mediante un //go:linkname no autorizado, sean tuyos o de una dependencia | Actualiza la dependencia. Esto falla al compilar, y sin disimulo. Nada de ello aparece en las notas de la versión, así que te enteras al compilar. |
Response.Body.Close de HTTP/1 consume el contenido no leído | Código que cierra antes de tiempo para abortar una descarga grande | Define Transport.DisableKeepAlives = true en esos clientes. |
| El texto de los mensajes de error de json/v2 es distinto | Tests que comprueban cadenas exactas de error JSON | Compara por tipo de error o por una subcadena en lugar del mensaje completo. |
La última fila es la más difícil de detectar. El comportamiento se mantiene pero el texto no, así que una batería de tests que compara cadenas de errores de unmarshal falla sin una causa evidente. Aísla el problema con dos ejecuciones en una rama que ya haya subido go.mod a go 1.27:
example.bashbashgo test ./... > default.txt 2>&1 GOEXPERIMENT=nojsonv2 go test ./... > nojsonv2.txt 2>&1 diff default.txt nojsonv2.txt
Todo lo que aparezca en el diff viene del cambio de JSON. Lo que falle en ambas ejecuciones es otra cosa de la tabla. Se espera que GOEXPERIMENT=nojsonv2 se elimine en una versión futura, así que úsalo para diagnosticar y después corrige los tests.
Preguntas frecuentes
¿Tengo que reescribir mi código JSON para Go 1.27?
No. El paquete encoding/json ahora está implementado sobre v2, pero el comportamiento de serialización y deserialización se mantiene y la API de v1 sigue teniendo soporte. La única diferencia observable es que el texto exacto de los mensajes de error puede cambiar. Solo adoptas los valores por defecto más estrictos de v2 si importas encoding/json/v2 de forma explícita.
¿Puede un método genérico satisfacer una interfaz?
No. Las notas de la versión indican que los métodos de interfaz no pueden declarar parámetros de tipo y que los métodos de interfaz no pueden implementarse con métodos genéricos. El despacho de interfaces se resuelve en tiempo de ejecución, así que el compilador no puede saber qué instanciaciones generar para una llamada con despacho dinámico. Si tu API se basa en interfaces, sigue usando funciones genéricas a nivel de paquete.
¿Debería dejar github.com/google/uuid por el paquete de la biblioteca estándar?
En código nuevo, sí. En código existente, comprueba antes dos cosas. El UUID de la biblioteca estándar es [16]byte con su propio conjunto de métodos, así que cualquier código que use los métodos extra del tipo de terceros necesita revisión. Además, cualquier dependencia que exporte el uuid.UUID de terceros en su API mantiene ese módulo en tu grafo de dependencias de todos modos.
¿Está crypto/mldsa listo para producción?
Es un paquete estable de la biblioteca estándar que implementa FIPS 204 y está integrado en crypto/x509 y crypto/tls. La limitación práctica es el tamaño de la firma, no la madurez: las firmas ML-DSA-65 ocupan 3.309 bytes frente a los 64 de Ed25519. Úsalo cuando la firma tenga que sobrevivir a la criptografía actual y descártalo para tokens de corta duración que se emiten en gran volumen.
¿Cuál es la forma más rápida de encontrar lo que Go 1.27 rompe en mi código?
Sube go.mod a go 1.27 en una rama y busca con grep asynctimerchan, gotypesalias y los cinco ajustes de TLS eliminados, ya que esos rompen la compilación directamente. Después ejecuta tus tests dos veces, una normal y otra con GOEXPERIMENT=nojsonv2, y compara los fallos. Así separas los cambios en el texto de los errores JSON de los fallos de archivos golden y de nombres de símbolos.
Fuentes
Referencias principales citadas en este artículo (última verificación: 23 de agosto de 2026):
- Notas de la versión de Go 1.27, fecha de consulta: 23/08/2026
- Tour interactivo de Go 1.27, VictoriaMetrics, fecha de consulta: 23/08/2026
- Documentación del paquete
uuid, fecha de consulta: 23/08/2026 - Documentación del paquete
encoding/json/v2, fecha de consulta: 23/08/2026 encoding/json: sección Migrating to v2, con la lista completa de diferencias de comportamiento, fecha de consulta: 23/08/2026- Documentación del paquete
crypto/mldsa, fecha de consulta: 23/08/2026 - Documentación del paquete
simd, fecha de consulta: 23/08/2026 - Documentación del paquete
net/http/httptest, fecha de consulta: 23/08/2026 - NIST FIPS 204: Module-Lattice-Based Digital Signature Standard, fecha de consulta: 23/08/2026
- Documentación del analizador modernize de
x/tools, fecha de consulta: 23/08/2026 - Notas de la versión de Unicode 17.0.0, fecha de consulta: 23/08/2026
Sigue aprendiendo
Los métodos genéricos son más fáciles de usar bien cuando las restricciones y la inferencia te resultan naturales, y esas son las partes que la gente suele leer por encima cuando aprende genéricos. El Go Generics Masterclass trabaja las restricciones, los conjuntos de tipos y la inferencia con ejercicios en el navegador que se ejecutan con el toolchain actual, así que puedes probar la sintaxis de métodos de 1.27 en código real.
¿Empiezas con Go? Arranca con la ruta Go Fundamentals y vuelve luego a las notas de la versión. Y si te saltaste la versión del año pasado, Novedades de Go 1.26 cubre errors.AsType, el recolector de basura Green Tea y new(expr).
