Lanzamiento de Go 1.22
(go.dev)- La variable del bucle
forahora se crea de nuevo en cada iteración, cambiando el comportamiento para evitar errores accidentales por compartición no intencional;rangesobre enteros también pasa a estar permitido - La compatibilidad mantiene la promesa de compatibilidad de Go 1, y se espera que casi todos los programas Go compilen y se ejecuten igual que antes
- En workspaces se puede usar el directorio vendor generado con
go work vendor, y si existe unvendordel workspace,-mod=vendorpasa a ser el valor predeterminado - Se deja de admitir
go getfuera de módulos en el modo legacyGOPATHconGO111MODULE=off; otros comandos de compilación comogo buildygo testseguirán funcionando para programas legacyGOPATH - El runtime ahora coloca los metadatos de GC basados en tipos cerca de los objetos del heap, lo que mejora entre 1–3% el rendimiento de CPU de los programas Go y reduce alrededor de 1% la sobrecarga de memoria en muchos programas
- La alineación de direcciones de algunos objetos puede cambiar de los anteriores 16 bytes o más a una alineación de 8 bytes; el código ensamblador que asuma una alineación mayor deberá ajustarse, y se puede usar la solución temporal
GOEXPERIMENT=noallocheaders - Las compilaciones con PGO pueden devirtualizar más llamadas, y al habilitar PGO en un conjunto representativo de programas Go el rendimiento en tiempo de ejecución mejora entre 2–14%
- Se agrega a la biblioteca estándar el primer paquete
v2,math/rand/v2, conReadno heredado, un generador global siempre inicializado con semilla aleatoria, fuentesChaCha8yPCG, y la función genéricaN - Los patrones de enrutamiento de
net/http.ServeMuxahora admiten métodos y comodines, con pequeñas rupturas de compatibilidad en patrones que incluyen{}y en el manejo de rutas escapadas; se puede restaurar el comportamiento anterior conGODEBUG=httpmuxgo121=1 - La versión mínima predeterminada de los servidores
crypto/tlspasa a ser TLS 1.2 cuando no se especifica, y en handshakes previos a TLS 1.3 ya no se ofrecen por defecto cipher suites sin ECDHE - El tracer de ejecución fue rediseñado por completo: usa el reloj del sistema operativo en la mayoría de las plataformas, ofrece traces segmentados con procesamiento en streaming, duración completa de syscalls e información del thread del sistema operativo en el que se ejecutó cada goroutine
- La interfaz web de la herramienta
tracese actualizó como parte del trabajo de soporte del nuevo tracer, y en traces de programas compilados con Go 1.22 o superior admite una vista orientada a threads y la visualización de la duración completa de syscalls vetajusta las advertencias sobre loop closure para alinearse con la semántica de variables de bucle de Go 1.22, y agrega advertencias paraappendsin valores,time.Sinceno diferido dentro dedefer, y discrepancias key/value enlog/slog- El bootstrap ahora requiere como mínimo el último point release de Go 1.20, y se espera que Go 1.24 requiera como mínimo el último point release de Go 1.22
- En
darwin/amd64, el toolchain de Go ahora genera PIE por defecto; Go 1.22 es la última versión que se ejecutará en macOS 10.15 Catalina, y Go 1.23 requerirá macOS 11 Big Sur o superior - Se añadieron el tipo
Aliasy la funciónUnaliasago/types; congotypesalias=1, los clientes deberán manejar el tipoAlias slices.Delete,DeleteFunc,Compact,CompactFuncyReplaceponen en cero los elementos del segmento reducido, yInsertahora siempre hace panic siiqueda fuera de rango- Las funciones de lectura de directorios en Windows ahora leen entradas por lotes para reducir la cantidad de syscalls, mejorando el rendimiento hasta 30%
1 comentarios
Comentarios en Hacker News
Durante los últimos 3 años usé principalmente TypeScript, pero hace poco empecé a usar Go, y al principio me preocupaba un poco la falta de funciones para arreglos o un sistema de tipos menos flexible
Pero al volver a montar un proyecto de TypeScript para un equipo de 4 o 5 personas, terminé gastando demasiado tiempo en linting, elegir una librería de routing para el servidor, elegir el servidor, estándares de código, manejo básico de errores, forzar errores personalizados o tipos
Resultpara evitar el infierno detry/catchanidados, pruebas y mocking, configuración de Prisma, etc.Me gusta TypeScript, pero de verdad se siente muy cómodo usar un lenguaje con una excelente biblioteca estándar, seguridad de tipos bien hecha y estándares de código integrados. Go también tiene sus quirks, pero está bastante bien, y que incluso el routing haya entrado a la biblioteca estándar significa una cosa menos de la cual preocuparse. Ahora solo estoy esperando funciones
map/filter/findpara slicesTypeScript se vuelve cada vez más complejo en cada release para usuarios avanzados, mientras que Go agrega cosas que faltaban, como
rangesobre enteros, para hacerlo más simple de usarSe parece a cuando las secuelas de videojuegos siguen acumulando configuraciones y mecánicas hasta que necesitan un reinicio para atraer a jugadores nuevos, pero como eso es difícil en un lenguaje de programación, está bien que Go sea consciente de ello
go:embedygo:generatesiempre me impresiona cuando lo veo en otros proyectosLos paquetes de
golang.org/xtambién son muy buenos, porque tienen implementaciones de casi todos los RFC de internet listas para usarsemap/filter/findpara slices, puedes usar la librería lo, inspirada en lodashEs una herramienta comodín que uso mucho en proyectos Go: https://github.com/samber/lo
Leías la documentación para hacer algo y, al poco tiempo, alguien te enseñaba un truco utilizable que no aparecía en la documentación
Si las notas oficiales del lanzamiento se sienten un poco secas, hicieron una versión interactiva: https://antonz.org/go-1-22
for, pero me ayudó ejecutarlo yo mismo y ver un resultado inesperadoCompactyReplaceme confunden; no sé si eso está bienHe usado Go por más de 9 años y, por Flutter, durante los últimos 4 años también he usado bastante Dart, y creo que ambos lenguajes tienen posturas totalmente opuestas frente a la complejidad. Dart intenta agregar todas las funciones posibles; Go hace lo contrario
Primero, en Dart hay demasiadas formas de inicializar valores dentro de una clase, y eso hace que uno pierda mucho tiempo. Dependiendo de prefijos como
final/const/static/late, el lugar de inicialización cambia entre constructor, factory,initState()de unStatefulWidgetde Flutter, etc., y cuando refactorizas, muchas veces tienes que rehacer bastante la inicialización. Los getters que parecen variables pero son funciones también agregan confusión en codebases nuevasSegundo, Dart tiene muchas funciones convenientes para meter en una sola línea, como mapas, streams, etc., así que dan ganas de crear rápido código de una sola línea difícil de entender. Pero al depurar, o cuando un desarrollador junior se topa con hacks como
firstWhereOrNully parámetros opcionales comoorElse: () => null, se confunden muchísimo. También pasa seguido que una seguridad de nulls o un error de tipos enrevesado te frena por completo, y cambiar eso por un simple bucleforal estilo Go da un alivio enormeinitStatepara cosas que no estén directamente relacionadas con la UI. A menos que sea algo como configurar el hint de un campo de texto, conviene evitarloLa mayoría de las páginas de Flutter tienen muy poco que hacer, o el padre crea un objeto
MyPageController, lo inicializa como quiera, y luego la página hija actúa según ese controlador. Por ejemplo, si en una página de lista el usuario quiere editar un elemento, puedes crear el controlador, meter ahí el elemento y enviarlo a la página hija; cuando regrese, el padre puede mirar el elemento del controlador o sus variables y callbacks para decidir cómo cambiar la UIAsí puedes controlar con bastante detalle la interacción entre widgets sin tocar cosas como
InheritedWidget. Claro, es mejor usarlo junto con una librería de gestión de estado, pero en la práctica muchas veces no se usarangetambién entra en eso: al final, la cantidad de caracteres que tecleas es parecida a la de una comprensión comoforEach(f), solo que al quedar repartido en tres líneas parece más largo, pero si lo escribes de forma explícita no pasa nadaEso sí, en cuanto a inicialización, ojalá Go tuviera una forma idiomática de inicializar campos de structs con ciertos valores. No me molesta que no haya constructores, pero a veces quisiera que un
boolse inicializara por defecto comotrueinitStatees un override deStatefulWidgety no es diferente de los eventos de ciclo de vida de otros frameworks; es un concepto de Flutter, así que no debería confundirse con un constructorPuede haber confusión sobre cuándo usar un constructor normal o una factory, pero no creo que Go sea superior por culpa de
initStateio.Copy, al copiar deTCPConnaUnixConn, ahora usa la llamada al sistemasplice(2)de Linux cuando es posible, y pasa a usar el nuevo métodoTCPConn.WriteToGracias a otra mejora de interfaces, aumentó una vez más de forma transparente el I/O sin copia. El efecto que logra este patrón en el paquete
ioes realmente grandeAun así, está dentro de lo esperable
Los patrones de enrutamiento mejorados son una buena mejora ahora que el futuro de Gorilla Mux es incierto
Aun así, este cambio rompe la compatibilidad hacia atrás de formas pequeñas: hay partes claras donde cambia el comportamiento de los patrones con
{y}, y otras menos obvias, como el manejo de rutas escapadas. Se controla con el campohttpmuxgo121deGODEBUG, y si configurashttpmuxgo121=1puedes volver al comportamiento anteriorPero me pregunto por qué esto no entra en conflicto con la promesa de compatibilidad de Go 1, que dice que “los programas escritos según la especificación de Go 1 seguirán compilando y ejecutándose correctamente sin cambios durante la vida útil de esa especificación”
goengo.modSi es 1.22 o superior, obtienes el nuevo comportamiento de la librería; si no, conservas el anterior. Habrá que ver qué tan bien funciona esto en la práctica
{y}en rutas de handlers HTTPTambién ofrecen una forma de desactivar el nuevo comportamiento, así que no te obligan a cambiar el código, aunque sí tienes que configurar una nueva variable de entorno
El cambio en la semántica de los bucles
foren esta versión es un caso parecido: en la práctica también es un cambio rompedor. Aun así, todos los programas de Go siguen compilando y ejecutándose; solo cambia un poco parte del comportamiento. Creo que Go tomó un enfoque pragmático, y esa ha sido una de las razones de su éxitogo.mod, al menos deberían romperlas de una mejor maneraSi cambio
go.moda 1.22, preferiría mucho más que el código no compilara a tener que encontrar problemas sutiles en tiempo de ejecuciónEn Go 1.22, cada iteración de un bucle crea una variable nueva
Esto ya se había discutido aquí antes: https://news.ycombinator.com/item?id=33160236 - Go: Redefining For Loop Variable Semantics (2022)
Puede que sea una opinión anticuada, pero no me gusta que se agregue
rangesobre funciones. La comodidad no es tan grande como para justificar la complejidad adicional en el lenguaje, y el estilo funcional parece chocar con el estilo explícito, imperativo y verboso pero con pocas funciones de Go. Creo que eso era una de sus grandes ventajasPor la misma razón, también creo que
rangesobre enteros va en la dirección equivocada. El conjunto reducido de características de Go y su baja carga cognitiva siempre fueron fortalezas clave y un diferenciadorUno de los grandes problemas en el desarrollo de software es impedir que los desarrolladores agreguen complejidad o intenten presumir habilidades. Sobre todo al inicio de la carrera, muchos tienden a complicar en exceso una solución en vez de resolver el problema de forma directa, aunque sea menos elegante y más verbosa
El soporte actual de genéricos en Go no es lo bastante sofisticado como para sostener esto, y por eso aparecen limitaciones extrañas que se sienten forzadas
La adición de
sql.Null[T]me parece buena. Probablemente empiece a usarla en proyectos nuevosEn lo que trabajo ahora dependo de null [0] de sqlboiler, cuya API es muy parecida. Funciona como
sql.Null, pero además tiene un métodoIsSet() boolque te dice si el valor alguna vez se estableció explícitamente, lo que ayuda a distinguir entre “null intencional” y “null porque no se inicializó”. Suena bien, pero en la práctica nunca lo he usadoTengo ganas de volver a revisar el mux de la librería estándar y ver si incluso puedo eliminar chi. Está muy bueno que este tipo de funcionalidad entre a la librería estándar
Cuando este tipo de funcionalidad entra a la librería estándar, sabes que siempre va a mantenerse y que muchos programas en Go usarán el mismo enfoque
Me pregunto si quienes usan Go en producción pueden pasarse rápido a la versión más nueva, o si terminan atados a releases viejos
Viendo proyectos públicos, da la impresión de que muchos evitan funciones relativamente nuevas. Aunque no uses genéricos, igual querría usar
any, introducido en Go 1.18, en lugar del viejointerface{}; también he escuchado que el segundo ya es más “idiomático” en GoEsta vez se enredó un poco, porque el equipo de Go lanzó 1.22 antes de que nosotros subiéramos el valor por defecto a 1.21. Ahora esperar ya es casi una costumbre, y es raro que una release de Go rompa algo
Por ejemplo, con genéricos esperé unos 6 meses antes de usarlos realmente, para confirmar que fueran útiles y que no impactaran mucho la experiencia de desarrollo ni la velocidad de build y test
Pero también distribuimos apps para usuario final hechas en Go, y ahí tenemos que quedarnos en 1.20 por compatibilidad con sistemas operativos cliente antiguos. En 1.21 hubo un corte importante, así que probablemente sigamos en 1.20 durante algunos años
latestde Go, y la suite de pruebas es bastante sólida, así que en la práctica se actualiza solo. Si algo no encaja, se rompe el pipeline, y ahí decidimos si fijarlo a la última versión que funcionó o posponer la actualización hasta corregir el cambio rompedorCada ciertos años aparece además algún bug sutil de caso límite entre una versión nueva y paquetes de terceros, pero por definición esos bugs se escaparon de la suite de pruebas, así que probablemente tampoco los habrías detectado con una actualización manual
Si te preocupa soportar compiladores viejos de Go, puedes poner condiciones de build para versiones anteriores y definir lo que falte. Por ejemplo, bajo
// +build !go1.7podrías ponertype any = interface{}