1 puntos por GN⁺ 2024-02-08 | 1 comentarios | Compartir por WhatsApp
  • La variable del bucle for ahora se crea de nuevo en cada iteración, cambiando el comportamiento para evitar errores accidentales por compartición no intencional; range sobre 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 un vendor del workspace, -mod=vendor pasa a ser el valor predeterminado
  • Se deja de admitir go get fuera de módulos en el modo legacy GOPATH con GO111MODULE=off; otros comandos de compilación como go build y go test seguirán funcionando para programas legacy GOPATH
  • 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, con Read no heredado, un generador global siempre inicializado con semilla aleatoria, fuentes ChaCha8 y PCG, y la función genérica N
  • Los patrones de enrutamiento de net/http.ServeMux ahora 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 con GODEBUG=httpmuxgo121=1
  • La versión mínima predeterminada de los servidores crypto/tls pasa 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 trace se 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
  • vet ajusta las advertencias sobre loop closure para alinearse con la semántica de variables de bucle de Go 1.22, y agrega advertencias para append sin valores, time.Since no diferido dentro de defer, y discrepancias key/value en log/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 Alias y la función Unalias a go/types; con gotypesalias=1, los clientes deberán manejar el tipo Alias
  • slices.Delete, DeleteFunc, Compact, CompactFunc y Replace ponen en cero los elementos del segmento reducido, y Insert ahora siempre hace panic si i queda 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

 
GN⁺ 2024-02-08
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 Result para evitar el infierno de try/catch anidados, 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/find para slices

    • En un boletín para CTOs compararon recientemente los lanzamientos de TypeScript y Go, usando Go 1.22 como ejemplo
      TypeScript se vuelve cada vez más complejo en cada release para usuarios avanzados, mientras que Go agrega cosas que faltaban, como range sobre enteros, para hacerlo más simple de usar
      Se 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
    • Siento algo parecido. Lo que hizo que me quedara en Go fue una toolchain de build sorprendentemente unificada, y lo que se puede hacer con go:embed y go:generate siempre me impresiona cuando lo veo en otros proyectos
      Los paquetes de golang.org/x también son muy buenos, porque tienen implementaciones de casi todos los RFC de internet listas para usarse
    • Mientras llegan map/filter/find para slices, puedes usar la librería lo, inspirada en lodash
      Es una herramienta comodín que uso mucho en proyectos Go: https://github.com/samber/lo
    • Precisamente porque no tiene tantas funciones, reduce las pequeñas decisiones y, de forma contraintuitiva, resulta bastante productivo
    • Trabajé un tiempo con un cliente que usaba TypeScript, y al final terminé llamándolo “Tricks Driven Development”
      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

    • Al principio no entendí de inmediato el problema de compartir variables en bucles for, pero me ayudó ejecutarlo yo mismo y ver un resultado inesperado
    • Los ejemplos de Compact y Replace me confunden; no sé si eso está bien
  • He 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 un StatefulWidget de 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 nuevas
    Segundo, 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 firstWhereOrNull y parámetros opcionales como orElse: () => 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 bucle for al estilo Go da un alivio enorme

    • Como consejo para Dart, es mejor no usar initState para 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 evitarlo
      La 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 UI
      Así 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 usa
    • Coincido en que la simplicidad de Go es su mayor ventaja. range también entra en eso: al final, la cantidad de caracteres que tecleas es parecida a la de una comprensión como forEach(f), solo que al quedar repartido en tres líneas parece más largo, pero si lo escribes de forma explícita no pasa nada
      Eso 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 bool se inicializara por defecto como true
    • initState es un override de StatefulWidget y 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 constructor
      Puede haber confusión sobre cuándo usar un constructor normal o una factory, pero no creo que Go sea superior por culpa de initState
    • Hay que optimizar la capacidad de expertos costosos para comunicarse de forma concisa entre sí. Quitarles herramientas sintácticas potentes a los principiantes solo impide que desarrollen su especialización
  • io.Copy, al copiar de TCPConn a UnixConn, ahora usa la llamada al sistema splice(2) de Linux cuando es posible, y pasa a usar el nuevo método TCPConn.WriteTo
    Gracias 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 io es realmente grande

    • La biblioteca estándar de Go tiene muchísimos ajustes de este tipo. No es necesariamente algo malo, pero estos tratamientos especiales no son extensibles, así que la integración con bibliotecas no estándar termina siendo menos satisfactoria
      Aun 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 campo httpmuxgo121 de GODEBUG, y si configuras httpmuxgo121=1 puedes volver al comportamiento anterior
    Pero 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”

    • La forma de rodear la promesa de compatibilidad de Go 1 es atar este tipo de cambios incompatibles hacia atrás al valor de la directiva go en go.mod
      Si 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
    • Normalmente, el equipo de Go revisa GitHub y programas de código abierto para ver cómo usa la gente algo antes de romperlo. Supongo que concluyeron que casi nadie usa { y } en rutas de handlers HTTP
      Tambié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 for en 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 éxito
    • Me parece un error poco común en Go. Si van a romper cosas según la versión usando go.mod, al menos deberían romperlas de una mejor manera
      Si cambio go.mod a 1.22, preferiría mucho más que el código no compilara a tener que encontrar problemas sutiles en tiempo de ejecución
    • Si no, se pierde un eslogan corto y potente. Es un cambio incompatible, grande o pequeño
  • En 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 range sobre 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 ventajas
    Por la misma razón, también creo que range sobre 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 diferenciador

    • Por ahora esta función es opcional, experimental y hay que activarla explícitamente. Si alguna vez se habilita por defecto, me gustaría que hubiera algún mecanismo para desincentivar su uso cuando no sea algo común
      Uno 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
    • Esta función todavía se siente prematura. Idealmente debería llegar después de que existan lambdas y packs de parámetros genéricos
      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
    • Yo también lo veo medio ambiguo. Ni siquiera reduce la cantidad de líneas de código frente a pasar una función inline
  • La adición de sql.Null[T] me parece buena. Probablemente empiece a usarla en proyectos nuevos
    En 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étodo IsSet() bool que 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 usado

    • Me pregunto cuándo necesitas saber si un null fue intencional. En la base de datos no hay un tipo correspondiente para eso, ¿no?
  • Tengo 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

    • Vine a decir exactamente lo mismo. chi ha sido excelente en algunos proyectos, y de hecho era tan natural que hasta te olvidabas de que estaba ahí
      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 viejo interface{}; también he escuchado que el segundo ya es más “idiomático” en Go

    • En Square normalmente esperamos al primer point release antes de subir la versión base del monorepo de Go. Los dueños de aplicaciones individuales pueden subir antes si quieren
      Esta 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
    • Con las funciones nuevas suelo esperar a experimentar hasta ver un problema donde encajen bien, pero la versión del compilador la actualizo casi inmediatamente después del release
      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
    • En el trabajo, para contenedores en operación, o sea servicios SaaS/API, los desarrolladores pueden subir libremente al build más reciente
      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
    • El CI hace build y test con el contenedor latest de 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 rompedor
      Cada 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
    • Yo me paso de inmediato
      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.7 podrías poner type any = interface{}