30 puntos por GN⁺ 2025-12-06 | 1 comentarios | Compartir por WhatsApp
  • A partir de las diferencias de filosofía y valores entre los tres lenguajes, se compara qué problemas intenta resolver cada uno
  • Go se describe como un lenguaje que prioriza la simplicidad y la estabilidad, minimizando funciones para facilitar la colaboración y el mantenimiento
  • Rust busca seguridad y rendimiento al mismo tiempo, y garantiza la seguridad de memoria mediante un sistema de tipos complejo y una estructura de traits
  • Zig se presenta como un lenguaje experimental que otorga control total al desarrollador mediante gestión manual de memoria y diseño orientado a datos
  • Los enfoques contrastantes de los tres lenguajes revelan el sistema de valores que implementa un lenguaje de programación, y el criterio de elección pasa por con qué filosofía se identifica cada desarrollador

Perspectiva para comparar lenguajes

  • El autor intenta entender el sistema de valores de cada lenguaje a través de experimentar con lenguajes nuevos, no con el que usa en el trabajo
  • Se enfatiza que, más que comparar listas de funciones, lo importante es qué trade-offs eligió cada lenguaje
  • Go, Rust y Zig comparten muchas capacidades a nivel funcional, pero los valores priorizados por sus diseñadores son distintos
  • Al comprender la filosofía de cada lenguaje, es posible juzgar para qué entornos y objetivos resulta adecuado

Go — un lenguaje centrado en la simplicidad y la colaboración

  • Go se distingue por su minimalismo, con la característica de que “puedes llevar todo el lenguaje en la cabeza”
    • Los genéricos se añadieron después de 12 años, y todavía no existen funciones como tagged unions o syntactic sugar para manejo de errores
  • Es muy cauteloso al agregar funciones, por lo que hay bastante código repetitivo, pero a cambio ofrece estabilidad y legibilidad en el lenguaje
  • Los slices de Go abarcan funciones equivalentes a Vec<T> en Rust o ArrayList en Zig, y el runtime administra automáticamente la ubicación en memoria
  • Fue diseñado con el objetivo de ser simple y de compilación rápida, como respuesta a la complejidad de C++ y a la lentitud al compilar
  • Da prioridad a la eficiencia de colaboración en entornos corporativos, favoreciendo código claro y consistencia por encima de funciones complejas

Rust — complejo, pero con seguridad y rendimiento poderosos

  • Rust se presenta con la idea de “abstracciones de costo cero”, y es un lenguaje maximalista donde se combinan muchos conceptos
  • La razón por la que es difícil de aprender es su alta densidad conceptual, con un sistema de tipos complejo y una estructura de traits
  • El objetivo central de Rust es compatibilizar rendimiento y seguridad de memoria
    • Para evitar UB (Undefined Behavior), realiza verificaciones en tiempo de compilación
    • Bloquea comportamientos impredecibles causados por referencias inválidas de punteros o doble liberación, entre otros casos
  • Para que el compilador pueda entender el comportamiento del código en runtime, el desarrollador debe definir explícitamente tipos y traits
  • Gracias a esta estructura, hay mayor confianza en el código de otras personas, y el ecosistema de librerías se mantiene muy activo

Zig — control total y diseño orientado a datos

  • Zig es el lenguaje más reciente de los tres; está en la versión 0.14 y casi no tiene documentación de la biblioteca estándar
  • Adopta gestión manual de memoria, por lo que el desarrollador debe llamar directamente a alloc() y elegir un allocator
  • A diferencia de Rust o Go, permite crear variables globales con facilidad, y detecta “illegal behavior” en runtime para detener el programa
    • Con 4 modos de release seleccionables al compilar, es posible ajustar el equilibrio entre rendimiento y estabilidad
  • Excluye deliberadamente las funciones de programación orientada a objetos (OOP)
    • No hay campos private ni dynamic dispatch, y ni siquiera std.mem.Allocator está implementado como interfaz
    • En su lugar, apunta al diseño orientado a datos (data-oriented design)
  • También en la gestión de memoria, en vez del control detallado por objeto al estilo RAII, recomienda una estructura que asigna y libera periódicamente bloques grandes de memoria
  • Zig se describe como un lenguaje de carácter libre y antisistema, que elimina la forma de pensar de la OOP y maximiza el control en manos del desarrollador
  • Actualmente el equipo está concentrado en reescribir todas las dependencias, y la versión estable (1.0) sigue sin fecha definida

Conclusión — las diferencias de valores que revelan los lenguajes

  • Go toma como valores centrales la colaboración y la simplicidad, Rust la seguridad y el rendimiento, y Zig la libertad y el control
  • Las diferencias entre los tres no son una simple comparación de funciones, sino el reflejo de una elección filosófica sobre el desarrollo de software
  • Los desarrolladores terminan eligiendo un lenguaje según con qué valores se identifican

1 comentarios

 
GN⁺ 2025-12-06
Opinión de Hacker News
  • En Rust no es difícil crear una variable global mutable
    solo hay que usar unsafe o un smart pointer que provea sincronización.
    Rust es re-entrant por defecto y garantiza la seguridad entre hilos en tiempo de compilación.
    Si no te importa la seguridad estática entre hilos, se puede hacer tan fácilmente como en Zig o C.
    La diferencia es que Rust ofrece más herramientas de garantía sobre el comportamiento del código en tiempo de ejecución.

    • Habiendo usado Rust durante varios años, creo que las variables globales mutables son el ejemplo típico de “que se pueda hacer no significa que se deba hacer”.
      Cuando vuelvo a otros lenguajes y veo que esto se usa con total naturalidad, me parece una locura desde el punto de vista de la seguridad.
    • Ese tipo de frase de “es trivial, solo necesitas ~” también la he oído en C++, Perl y Haskell.
      Pero cuando se acumulan estas “cosas simples”, dejan de ser simples.
      Rust ya cruzó esa línea y ahora ya no es trivial en absoluto.
    • Me pregunto si el compilador de Rust detecta race conditions entre hilos en tiempo de compilación.
      Si es así, me parecería más atractivo que C.
      También quisiera saber cómo se maneja cuando dos variables siempre tienen que bloquearse juntas.
    • Si yo diseñara un lenguaje, las variables globales mutables estarían prohibidas desde el principio.
      Al depurar, casi siempre terminaban siendo la raíz del problema.
  • Sobre el comentario de que Rust tiene una alta densidad conceptual, en la práctica creo que con solo conocer el 5% ya se puede usar de forma productiva.
    Llevo más de 12 años usando Rust y nunca he tenido que usar algo como #[fundamental].
    En Rust también se puede hacer arena allocation, y existe el concepto de allocator.
    Simplemente hay un allocator por defecto, y normalmente se usan asignaciones explícitas en heap como Box::new.
    Una global mutable se puede crear como static FOO: Mutex<T> = Mutex::new(...), y el mutex es necesario por seguridad de memoria.
    El sistema de tipos de Rust está diseñado para garantizar no solo la seguridad de memoria, sino también la seguridad semántica del código.

    • Pero como otros desarrolladores pueden usar otro 5~10% de conceptos distinto, al colaborar terminas teniendo que aprender más conceptos.
      En C hay menos de esta complejidad.
      La complejidad al final sí importa.
    • Es cierto decir que “Rust también permite arena allocation”, pero la mayoría del código en Rust/Go toma como camino principal muchas asignaciones de tamaño pequeño.
      No se trata solo de si se puede o no, sino de una diferencia en el estilo básico de programación.
    • Si en Rust el allocator es un tipo, me pregunto si en un modelo de hilos m:n se puede dar una arena separada a cada solicitud.
    • También preguntan si el allocator de Rust es global, y si todas las asignaciones en heap usan el mismo allocator.
    • Mencionan el video sobre batch allocation de Casey Muratori, y señalan que algunos desarrolladores malinterpretan eso para criticar el RAII de Rust.
      También hubo un caso en que la Zig Software Foundation citó incorrectamente comentarios de Asahi Lina sobre Rust.
      No me gusta mucho la actitud de marketing de Zig de rebajar a otros lenguajes.
  • Lo que me gusta de Zig es que es un lenguaje que permite manejar el agotamiento de memoria de forma elegante.
    Asume que toda asignación puede fallar y exige manejarlo explícitamente.
    Tampoco trata el espacio de stack como si fuera magia: el compilador analiza el grafo de llamadas e infiere el tamaño máximo.
    En entornos embebidos, este diseño centrado en recursos es esencial.

    • Pero en sistemas operativos como Linux que usan overcommit, en la práctica no ocurre un fallo de asignación.
      No es algo que se resuelva con manejo a nivel de lenguaje.
    • A la pregunta de por qué debería existir Zig si Rust ya existe, yo más bien preguntaría “si ya existe C, ¿por qué Zig?”.
      Al final ambos comparten el mismo problema de la gestión manual de memoria.
      Entonces, creo que es mejor usar un lenguaje con GC.
    • Me pregunto cómo funciona la inferencia del tamaño del stack en Zig cuando hay recursión o llamadas mediante punteros a función.
    • Zig no fue el primero, y conviene revisar la historia de los lenguajes de sistemas desde JOVIAL en 1958.
    • En Rust también se puede manejar bien la preasignación.
      Solo que la biblioteca estándar de Rust usa panic ante OOM, así que existe un ecosistema aparte para desarrollo embebido en entornos no-std.
  • El slice de Go es distinto de Vec<T> en Rust.
    append() devuelve un nuevo slice, que puede compartir la memoria existente o no.
    No hay forma de reducir memoria, y si solo escribes append(s, ...) terminas ignorando el nuevo slice.
    Go tiene una actitud de “haz lo que te digo”, mientras que Rust tiene una de “verifica si hiciste lo que te dije”.
    Es decir, Go permite errores a cambio de simplicidad, y Rust elige reducir errores aunque eso implique más complejidad.

    • En realidad sí se puede reducir memoria con slices.Clip.
      Además, si solo escribes append(s, ...) obtienes un error de compilación, así que el texto original hace una afirmación algo inexacta.
      Go es un lenguaje que evalúa con cuidado el aumento de complejidad al agregar funciones.
    • Como append(s, …) ni siquiera compila, no creo que un principiante pueda cometer ese error.
    • Es interesante que incluso después de que Go añadiera genéricos, tipos como List[T] no se hayan vuelto de uso generalizado.
      Probablemente porque no es tan común pasar directamente listas expandibles.
    • En Go, la mayoría de las trampas peligrosas (foot guns) están claramente documentadas en la especificación y la documentación.
      Muchas veces la sorpresa viene simplemente de no haber leído la documentación.
  • Creo que en la práctica es difícil detectar la UB (Undefined Behavior) de C/C++ con verificaciones en tiempo de ejecución.
    Android también aplicó sanitizers a todos los commits, pero solo después de migrar a Rust comenzaron a disminuir los exploits.

    • Piden la fuente de esa afirmación sobre los sanitizers en Android.
  • Me gustó que el artículo comparativo tratara con honestidad las fortalezas y debilidades de cada lenguaje.
    Aunque fue una lástima que no mencionara a Raku.
    En mi opinión, si C–Zig–C++–Rust–Go forman un continuo de lenguajes de bajo nivel, del lado de alto nivel sería Julia–R–Python–Lua–JS–PHP–Raku–WL.

    • Preguntan qué significa WL.
    • Raku es un lenguaje de propósito general con mucha expresividad, con multidispatch, roles, tipado gradual, evaluación perezosa y un potente sistema de regex incorporado.
      Soporta definición de gramáticas a nivel de lenguaje, lo que facilita crear DSL o hacer parsing de logs.
      Al estar basado en una VM, su rendimiento es más bajo, pero es adecuado para expresar directamente la estructura del problema.
      Como sucesor de Perl, busca ser un lenguaje flexible y coherente.
  • Pensar que en Rust una función que devuelve un puntero provoca automáticamente una asignación en heap es un malentendido.
    Las variables locales están en el stack y desaparecen al retornar, así que el puntero queda invalidado.
    En modo seguro, Rust no permite desreferenciar punteros, y en modo unsafe el desarrollador asume la responsabilidad de garantizar su validez.
    Probablemente se confundió Box::new con una “asignación implícita”.

    • Es difícil entender que se confunda el escape analysis de Go con la asignación explícita en heap de Rust.
      Parece o una mala comprensión del concepto o un intento deliberado de desinformar.
  • La mayor ventaja de Go es su modelo de concurrencia simple.
    Gracias a las goroutines, es fácil escribir código paralelo.

    • Una de las ventajas de Go es que, gracias a la consistencia del código, es fácil explorar bases de código grandes.
      Encontrar implementaciones de interfaces puede ser difícil, pero la legibilidad es alta y eso favorece el trabajo en equipo.
    • La charla de Rob Pike “Concurrency is not Parallelism” explica muy bien la filosofía de concurrencia de Go.
      No hay colored function, y la comunicación basada en canales es simple, así que se puede escribir código concurrente correcto con rapidez.
    • Pero creo que Structured Concurrency es un modelo más fácil.
      Artículo relacionado: Structured Concurrency or Go Statement Considered Harmful
    • La nueva interfaz std.Io de Zig es parecida al modelo de concurrencia de Go.
      La palabra clave go corresponde a std.Io.async, los canales a std.Io.Queue y select a std.Io.select.
    • Si fueras desarrollador de Erlang, probablemente no estarías de acuerdo con que el modelo de concurrencia de Go sea el más fácil.
  • Lo que yo quiero es un lenguaje que combine la simplicidad de Go con el manejo de resultados/errores/enums de Rust y mejores genéricos.

    • Yo también estoy de acuerdo. Hay una gran demanda de mercado por un lenguaje nativo con GC y un sistema de tipos más potente.
      He visto OCaml, D, Swift, Nim y Crystal, pero todavía no hay ninguno que domine el mercado.
    • He oído que el OCaml moderno tiene un modelo de concurrencia muy potente y que su rendimiento puede competir con Go.
    • El lenguaje Borgo intentó algo así, pero fue abandonado.
      En su lugar, valdría la pena mirar Gleam.
    • La propuesta de error de Go me pareció interesante.
      Ojalá surja alguna mejora que resuelva este problema recurrente.
      Los genéricos probablemente seguirán siendo un desafío difícil.
    • La alternativa más cercana es C#, pero sigue siendo un lenguaje centrado en OOP.
  • Me gustó el tono general del artículo porque transmitía la pasión y curiosidad de un desarrollador principiante.
    Creo que la ausencia de genéricos en Go no fue un simple minimalismo, sino el resultado de pensar en los trade-offs.
    Los lifetime de Rust fueron el mayor obstáculo para mucha gente, y la innovación del lenguaje está en la combinación de conceptos existentes.
    La gestión manual de memoria en Zig se basa más en la filosofía de Data-Oriented Design (DOD) que en excluir OOP.
    Charla relacionada: presentación de Andrew sobre DOD

    • Como planteó Russ Cox en 2009 en “The Generic Dilemma”,
      la cuestión central era “qué prefieres: programadores lentos, compiladores lentos o ejecución lenta”.
      Parece que el equipo de Go finalmente encontró un punto medio satisfactorio.