1,050 lecturas
1,050 lecturas

¿Deberías aprender Rust y Zig?

por Ace — The JS Hater9m2025/04/02
Read on Terminal Reader

Demasiado Largo; Para Leer

El valor de los lenguajes de programación que no ocultan los detalles.
featured image - ¿Deberías aprender Rust y Zig?
Ace — The JS Hater HackerNoon profile picture

Lenguajes que exponen los detalles

Cuando la mayoría de las personas empiezan a programar, se sienten atraídas por lenguajes que simplifican las cosas. Python, JavaScript y otros lenguajes de alto nivel abstraen los detalles complejos de la gestión de memoria, las llamadas al sistema y la interacción con el hardware. Esta abstracción es poderosa: permite a los principiantes crear programas útiles rápidamente sin atascarse en los detalles de implementación.


Pero los lenguajes que te obligan a afrontar estos detalles tienen un valor significativo. Lenguajes como Rust, C y Zig no solo te convierten en un mejor programador en esos lenguajes específicos, sino que también profundizan tu comprensión del funcionamiento real de las computadoras. Esta comprensión te hace más eficaz en todos los lenguajes que usas, incluso en los de alto nivel.


Para demostrarlo, tomemos un concepto simple como leer la entrada del usuario y almacenarla en una variable. Luego, demostremos cómo se haría desde lenguajes de nivel superior hasta lenguajes de nivel inferior. Empezaremos con el de nivel superior:

Pitón

 name = input("What is your name?\n") print(name) #Ah, the classic I/O example


Para un estudiante, ¿cuáles podrían ser las preguntas y el aprendizaje aquí? Recuerde, no solo intentamos generar código, sino tener una idea clara de lo que está sucediendo:


  • Variables y memoria: Tenemos “variables” que contienen datos.

  • Tipos de datos y memoria: Tenemos tipos de datos, y las cadenas son simplemente texto normal. Un estudiante curioso podría incluso aprender sobre los demás tipos de datos con esta pista.

  • Llamadas de función: Podemos llamar funciones con argumentos y almacenar los resultados de esas funciones en una variable.

  • Los programas Python del entorno de ejecución se pueden ejecutar llamando al intérprete y con el programa (suponiendo que tengamos Python instalado; no voy a insistir en el tema del control de versiones, las dependencias y la instalación de Python). Esto podría llevar a un descubrimiento sobre lenguajes interpretados y compilados.


No están mal; creo que el mayor conocimiento sobre computadoras provendrá de ese pequeño '\n' en la cadena. Explorar un poco esto nos permitirá aprender sobre ASCII, UTF-8 y la representación de texto en la computadora como bytes. Probablemente sería demasiado para un principiante, pero le daría una idea de cómo el texto se convierte en 0 y 1. También hay una pequeña lección sobre intérpretes y compiladores, pero eso requeriría mucha investigación.

Javascript/Typescript (Node)

 import readline from 'readline/promises'; const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); const name = await rl.question('What is your name?\n'); rl.close(); console.log(name); //Oh, js, so horribly wonderful in your ways


Además de los conocimientos anteriores, evaluemos lo que un estudiante curioso podría observar simplemente explorando este código:


  • Flujos de entrada/salida : Observamos referencias explícitas a la entrada estándar (stdin) y la salida estándar (stdout). Una simple exploración de estos nos llevaría a los flujos de archivos stdin y stdout en entornos Unix, e incluso quizás a los descriptores de archivo y al principio de que «todo es un archivo» en sistemas Linux.


  • Procesos: Ver el objeto Proceso podría despertar la curiosidad de una persona interesada en los procesos y vislumbrar el proceso de ejecución de los sistemas operativos modernos. Aunque no lo comprendan del todo, ahora tienen una idea.


  • E/S asíncrona : await y Promises introducen al alumno a cómo las computadoras gestionan operaciones que no se completan inmediatamente, e incluso plantean la pregunta de por qué no se ejecutan de forma directa (como en Python). Estos elementos impulsan el aprendizaje sobre:


    • Ejecución sincrónica y asincrónica, E/S sin bloqueo y, tal vez, concurrencia.

    • Promesas, la cola de microtareas y la cola de tareas en Node, programación basada en eventos y sus beneficios.


  • Creación de interfaces y gestión de recursos: la creación y el cierre de una interfaz llevan a cuestionar y comprender la gestión de recursos, especialmente para recursos importantes como los flujos de E/S.


  • Palabras clave de declaración ( let , const ): estas no se asignan explícitamente a conceptos más profundos, pero enseñan buenas prácticas para controlar la mutabilidad.


  • Entorno de ejecución: Los programas JS se ejecutan mediante un entorno de ejecución (Node, Bun, Deno, etc.). La función del entorno de ejecución es proporcionar a V8 (el motor de JS) las funciones adicionales que lo convierten en un lenguaje completo. Cabe preguntarse qué proporcionan exactamente estos entornos de ejecución al motor V8, lo que llevaría a la implementación de E/S asíncronas.


Algunas de ellas, como Promesas y Colas, son abstracciones relacionadas con JS a primera vista, pero si una persona finalmente encuentra su camino hacia Libuv ( la biblioteca C que maneja E/S asincrónica para Node.js), aprenderá un poco sobre E/S en sistemas operativos.

Do sostenido

 Console.WriteLine("What is your name?"); string? name = Console.ReadLine(); Console.WriteLine(name); //Surprise!! No public static void Main(string[] args)


Los sospechosos habituales de la codificación de caracteres están aquí, aunque oscurecidos por ReadLine y WriteLine , además de esos, surgen dos cosas importantes:


  • Tipado estático y tipos explícitos : Si bien la inferencia de tipos es una característica excelente para la productividad, considero que escribir tipos explícitamente mejora el proceso de aprendizaje, especialmente para principiantes. Aquí, un estudiante podría obtener una primera idea real de la distribución de la memoria, especialmente al explorar las razones para especificar explícitamente los tipos de variables. Estas incluyen la reserva de bytes específicos para ciertas variables y los errores que ocurren al intentar incluir 64 bytes en 32 bytes de memoria.


  • Tipos que aceptan valores nulos: aprenden que es posible que en una ubicación de memoria no exista un valor válido, lo que mejora aún más la visión de la memoria.


  • Una persona realmente curiosa se preguntaría: ¿Por qué debemos especificar explícitamente los tipos que aceptan valores NULL? ¿Existe algún problema específico al tratar los valores NULL como valores no NULL en los programas? Esto nos lleva a aprender sobre las reglas de protección de memoria.


  • Common Language Runtime (CLR), Intermediate Language (IL) y JIT: el entorno de ejecución de .NET hace que el proceso de compilación sea más obvio al obligar al alumno a compilar explícitamente y luego ejecutar el código.


Forzar al usuario a compilar su código le permite ver el IL generado. Esto nos permite un primer vistazo al ensamblador (o pseudoensamblador, en cualquier caso), las instrucciones y los registros. También existe la posibilidad de aprender sobre la compilación Just-In-Time del CLR si un estudiante profundiza un poco más en el tema.


Si bien estos conceptos existen en otros lenguajes, la diferencia es que exponerlos al usuario le permite profundizar de inmediato y obtener una idea de lo que realmente sucede al ejecutar el código.


Finalmente, la E/S está más abstraída aquí que en JS. No tenemos nada relacionado con los flujos ni la gestión de recursos.

Golang

Lo siento Gophers, pero no puedo cubrir todo o este artículo sería demasiado largo.

Óxido

 use std::io; fn main() { println!("What is your name?"); let mut name = String::new(); io::stdin() .read_line(&mut name) .expect("Failed to read line"); println!("{}", name); } //Almost a 1:1 from The Book


Para un estudiante moderadamente curioso, ¿qué se podría entender sobre los conceptos de sistema?


  • Mutabilidad explícita : la palabra clave mut indica que las variables son inmutables por defecto. Esto permite controlar la mutabilidad de los datos para obtener todos sus beneficios.


  • Manejo explícito de errores : .expect() muestra que la E/S puede fallar y obliga a considerar el manejo de errores. Esto se da casi por sentado en lenguajes avanzados, y un estudiante puede comprender que interactuar con los dispositivos físicos podría generar numerosos errores que no se considerarían si no se hubieran detectado. Por ejemplo, pregunte a un desarrollador de bases de datos si los discos son perfectos.


  • Acceso directo a flujo : io::stdin() revela explícitamente la interacción con los recursos de E/S a nivel de sistema operativo. Al igual que antes, esto permite profundizar en los conceptos de E/S de un sistema operativo; la diferencia radica en que los conceptos son mucho más simples que en JavaScript.


  • Asignación de memoria : String::new() muestra nuestro primer encuentro, aunque pseudoexplícito, con el montón y la pila, dos de los conceptos más importantes de la memoria. Aunque no es muy explícito, ofrece una pista suficiente para que el estudiante curioso pueda comenzar fácilmente a explorar la memoria y plantearse preguntas como: "¿Por qué necesitamos diferentes regiones de memoria?", "¿Qué es el montón?", etc.


  • Referencias y Préstamos : &mut name revela nuestra primera introducción explícita a los punteros . Si bien todos los lenguajes hasta ahora han usado referencias de forma discreta, exponerlas directamente al programador le permite comprender mejor la distribución de la memoria. Aprenden que podemos usar los mismos datos en múltiples regiones simplemente usando referencias, así como las ventajas y desventajas de este enfoque.


  • Compiladores, ejecutables y ensamblaje: Una vez más, requerir explícitamente un paso de compilación hace que el alumno comience a explorar el proceso de compilación, pero esta vez, tiene la oportunidad de explorar hasta el punto de las instrucciones de ensamblaje y un poco sobre el proceso de ejecución de las CPU modernas.


Incluso si te sientes cómodo con abstracciones de alto nivel, experimentar con un pequeño elemento en Rust puede revelar todo un mundo de comportamiento del sistema que permanece oculto en otros lenguajes. La mayoría de estos no son nuevos, por así decirlo; la diferencia radica en que aquí se exponen al programador, lo que le obliga a reflexionar y aprender sobre ellos. Esto conlleva una carga de trabajo y dificultad adicionales, pero se ve recompensado por una comprensión más profunda y, en consecuencia, por el control de los recursos del sistema.

Zig

 const std = @import("std"); pub fn main() !void { var debugAllocator = std.heap.DebugAllocator(.{}).init; defer std.debug.assert(debugAllocator.deinit() == .ok); const allocator = debugAllocator.allocator(); const stdout = std.io.getStdOut().writer(); const stdin = std.io.getStdIn().reader(); var name = std.ArrayList(u8).init(allocator); defer name.deinit(); try stdout.print("What is your name?\n", .{}); try stdin.streamUntilDelimiter(name.writer(), '\n', null); try stdout.print("{s}\n", .{name.items}); } //lol, the code block doesn't have support for Zig


NOTA: Dudé mucho si incluir cadenas "crecientes" asignadas al montón o simplemente tener una cadena "estática" muy grande asignada a la pila, pero como he usado cadenas "crecientes" en todos los demás ejemplos, aquí estamos. En resumen, una cadena "creciente" puede crecer con una entrada adicional, mientras que una cadena estática es fija: la única forma de añadir nuevos caracteres es crear una nueva con el nuevo carácter.


¡Caramba! ¿Por dónde empezamos? Si un estudiante viera este código, probablemente se asustaría y saldría corriendo, pero ¿qué se podría aprender sobre los conceptos de sistemas al explorar esto?


  • Asignadores y memoria: Zig nos explica claramente que, cuando necesitamos obtener memoria del montón, debemos declarar nuestra intención de hacerlo; esto no es tan abstracto como en Rust. Aquí lo explicamos claramente. Si bien esto añade más carga inicial, anima al desarrollador a explorar la pila, el montón, la memoria dinámica, las llamadas al sistema del sistema operativo y por qué necesitamos asignar y liberar memoria explícitamente. Esto refuerza aún más la comprensión del desarrollador sobre la estructura de la memoria.


  • Limpieza y detección de fugas: Las llamadas defer explícitas para limpiar la memoria y las comprobaciones de fugas de memoria son buenos puntos de partida para comprender de primera mano los problemas que surgen de una gestión incorrecta de la memoria. En este punto, el desarrollador está lo suficientemente capacitado para profundizar en este tema.


  • Cadenas, segmentos y referencias: Las cadenas son simplemente punteros a una matriz de valores u8 . Esto elimina la última abstracción entre el concepto de "cadena" de alto nivel y el de "matriz de bytes" de bajo nivel.


  • Acceso directo a flujos de E/S: nuevamente, al hacer estas cosas visibles, el desarrollador comprende lo que sucede cuando lee o escribe desde E/S fuera del programa.


  • Errores de E/S y gestión de errores: Las llamadas a dispositivos de E/S, y las llamadas al sistema en general, pueden fallar. El desarrollador debe analizar esto y tenerlo en cuenta.

C y C++

Creo que entiendes el punto al que me refiero: no hay necesidad de insistir en algo que ya está hecho.


Bueno, ahí lo tienen. Una tarea sencilla en varios idiomas. Espero que les haya convencido. Pero antes de irnos, aclaremos algunas cosas:

Entonces, ¿simplemente escribir y reescribirlo en Rust?

No, la respuesta es no, simplemente no. Argumento esto desde la perspectiva de quien quiere aprender más sobre el funcionamiento de las computadoras y necesita exprimir al máximo su hardware (también podría optar por el ensamblaje, como hacen los chicos de FFMPEG , si lo desea).


Pero ¿qué pasa cuando no te importa sacrificar algo de eficiencia por la velocidad de desarrollo? ¿Qué pasa si necesitas escribir un servidor web ligero con cierta lógica en un par de días? ¿Qué pasa cuando eres un desarrollador nuevo y C++ te intimida tanto que quieres dejar de escribir código?


Hay muchas situaciones en las que algo como Go, Elixir, Haskell o lo que sea funciona perfectamente. Solo te pido que, después de eso, te tomes un tiempo para aprender un poco sobre lo que realmente está sucediendo; no tienes que ser un experto en ASM que pueda escribir código sin parar, pero saber qué sucede con las computadoras te ayudará a escribir código mejor y con mayor rendimiento . Y te ayudará a dejar de ver tu computadora como una caja negra. También te prometo que lo disfrutarás.


Hable conmigo en Twitter .

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks