¡Hola, entusiastas de Zig! Hoy vamos a explorar un emocionante campo donde Zig realmente brilla: WebAssembly (Wasm). Prepárate para desbloquear el poder de Zig en el navegador! 🌐💪

¿Qué es WebAssembly? 🧐

WebAssembly es un formato de instrucción binaria diseñado como un objetivo portable para la compilación de lenguajes de alto nivel como C, C++, Rust, y por supuesto, Zig. Nos permite ejecutar código en la web a una velocidad cercana a la nativa, abriendo la web a un nuevo rango de aplicaciones.

¿Por qué Zig con WebAssembly? 🤔

La simplicidad, eficiencia y robustez de Zig lo convierten en una excelente elección para el desarrollo de WebAssembly. La capacidad de Zig para compilar cruzadamente sin problemas es una enorme ventaja, y su fuerte enfoque en la seguridad es perfecto para el entorno protegido de la web.

Empezando con Zig y WebAssembly 🚀

Vamos a crear un simple programa Zig y compilarlo a WebAssembly.

  1. Comienza escribiendo un simple programa Zig. Abre tu editor de código y crea un nuevo archivo llamado hello.zig:

⚠️ ¡Atención, programador! Este post utiliza la versión 0.11.0-dev.3971 de Zig

extern fn print(a: i32) void;

export fn add(a: i32, b: i32) i32 {
    print(1234);
    return a + b;
}

Este código establece una interfaz entre Zig y JavaScript. La declaración extern fn print(a: i32) void; permite a Zig usar la función print de JavaScript, y export fn add(a: i32, b: i32) i32 { ... } proporciona una función que puede ser llamada desde JavaScript.

  1. Para compilar este código Zig a WebAssembly, añade un archivo build.zig
const std = @import("std");

pub fn build(b: *std.build.Builder) void {
    const target = std.zig.CrossTarget{
        .cpu_arch = .wasm32,
        .os_tag = .freestanding,
        .abi = .musl,
    };

    const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });

    const lib = b.addSharedLibrary(.{
        .name = "hello",
        .root_source_file = .{ .path = "./hello.zig" },
        .target = target,
        .optimize = optimize,
    });

    lib.rdynamic = true;

    b.installArtifact(lib);
}

Vamos a verlo con detalle:

  1. const std = @import("std");
    Aquí estamos importando la biblioteca estándar de Zig (std). Esto nos da acceso a todas las funciones y utilidades proporcionadas por la biblioteca estándar. 📚🔍

  2. pub fn build(b: *std.build.Builder) void {
    En esta línea, definimos la función principal build para nuestro script de compilación, que recibe un puntero a una instancia de std.build.Builder. Este es nuestro guía para la aventura de la construcción de código. 🏗️🗺️

  3. const target = std.zig.CrossTarget{...};
    Aquí estamos creando un objetivo de compilación cruzada para WebAssembly con el tag del sistema operativo freestanding y la interfaz binaria de aplicación musl. Esta es la maleta que preparamos para nuestro viaje a WebAssembly Land. 🛄🎯

  4. const optimize = b.standardOptimizeOption(...);
    Aquí definimos las opciones de optimización. Estamos eligiendo la opción de optimización ReleaseFast, que es como decir “¡Vamos a correr como el viento, pero sin tropezar!” 🏃‍♂️💨

  5. const lib = b.addSharedLibrary(...);
    En este paso, le decimos a nuestro guía (el constructor b) que queremos construir una biblioteca compartida llamada hello a partir del archivo de origen hello.zig, con las opciones de objetivo y optimización que definimos anteriormente. Es como pedirle a nuestro guía que prepare los planes para construir un puente a WebAssembly Land. 🌉🛠️

  6. lib.rdynamic = true;
    La opción rdynamic es absolutamente necesaria en Zig 0.11.0, ya que el linker de wasm integrado en Zig no exporta los símbolos definidos. La opción rdynamic sirve para enlazar dinamicamente librerías y es particularmente útil cuando quieres exportar símbolos desde un ejecutable. El uso de esta opción en el contexto de WebAssembly es una solución (workaround) a este problema.

  7. b.installArtifact(lib);
    Finalmente, le decimos a nuestro guía que instale la biblioteca que acabamos de construir. Esto coloca nuestra hermosa biblioteca hello en el lugar correcto para que otros puedan usarla. Es como colocar nuestro puente recién construido en el mapa para que todos puedan encontrarlo. 🗺️🌉

Para construir el proyecto teclea el siguiente comando:

zig build
  1. Tenemos nuestro módulo listo, ahora vamos por el HTML y Javascript. Creamos un archivo index.html:
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="utf-8">
    <title>Zig: ¡Hola WASM!</title>
  </head>
  <body>
    <h3>Zig: ¡Hola WASM!</h3>
    <p>(¡Abre la consola!)</p>
  </body>
  <script src="loader.js"></script>
</html>
  1. Creamos un script de carga para cargar y ejecutar nuestro módulo WebAssembly. Creamos un nuevo archivo en el mismo directorio llamado loader.js:
WebAssembly.instantiateStreaming(fetch('zig-out/lib/hello.wasm'), {
  env: {
    print: function (x) { console.log("Llamada desde zig:", x); }
  }
}).then(result => {
  const add = result.instance.exports.add;
  console.log("Llamada a Zig:", add(3, 5));
});

Este código muestra cómo se puede cargar y ejecutar un módulo WebAssembly en un entorno de navegador. Para ello, se prepara una solicitud HTTP GET al archivo .wasm que se pasa a WebAssembly.instantiateStreaming para descargar, compilar y ejecutar el módulo.

El objeto de importación que se pasa a WebAssembly.instantiateStreaming representa el entorno del host. En este caso, proporcionamos una función de impresión que se puede llamar desde el módulo WebAssembly. Una vez que el módulo se ha instanciado correctamente, podemos acceder a las funciones exportadas y llamarlas como cualquier otra función de JavaScript.

  1. Inicia cualquier servidor HTTP

Debido a las políticas de CORS en la mayoría de los navegadores, es necesario servir nuestro módulo WebAssembly a través de un servidor HTTP. Puedes hacerlo fácilmente con Python ejecutando el comando:

$ python -m SimpleHTTPServer 8001

Navega a http://localhost:8001 en tu navegador, y estarás listo para ver el funcionamiento de tu programa.

¡Y eso es todo! Acabas de escribir tu primer programa Zig para WebAssembly. 🎉

Quiero más 🌟

Atentos a nuestros próximos posts, donde iremos un paso más allá y construiremos juntos y poco a poco un clásico: ¡El juego ese de bloques que caen de arriba! No solo será una oportunidad para divertirnos mientras programamos, sino también una forma efectiva de aprender y profundizar en nuestros conocimientos sobre WebAssembly, Zig, juegos, IA, algoritmos, estructuras de datos, usabilidad y muchos otros.

¡Pruébalo un poco para ir abriendo el apetito!

Ejemplo de lo que viene


Espero que esta publicación te haya proporcionado una sólida introducción a WebAssembly y Zig. ¡Nos vemos en la próxima!