include_bytes("ruta") lee un archivo AL COMPILAR y mete sus bytes en el binario, así el recurso no se puede perder por el camino — el caso real: una interfaz que se degrada a otra tipografía sin avisar porque alguien copió solo el ejecutable y no la carpeta de assets. La ruta es un literal, se resuelve desde la raíz del proyecto y nunca desde el archivo que la usa: con una variable el compilador la rechaza con NYX1033.
// Nyx by Example: un recurso binario que viaja DENTRO del ejecutable. // // El caso real, en chico: un ERP que promete instalarse copiando un solo // ejecutable servía sus tipografías desde disco. Si alguien copiaba únicamente // el binario, la interfaz se degradaba a otra tipografía SIN AVISAR — el // servidor respondía 200 con un cuerpo vacío. `include_bytes` lee el archivo AL // COMPILAR y mete sus bytes en el binario, así que el recurso no se puede // perder por el camino. // // Lo que esta receta muestra: // 1. que los bytes llegan intactos (NUL incluidos — un .woff2 está lleno); // 2. que el valor sirve TAL CUAL como cuerpo de una respuesta HTTP; // 3. que dos llamadas a la misma ruta comparten un solo global. // // No abre un puerto a propósito: arma la respuesta en memoria y la describe, // así la receta corre sola bajo `make test-examples`. import "std/web" // La ruta es un LITERAL y se resuelve desde la raíz del proyecto, nunca desde // este archivo. Con una variable el compilador la rechaza con NYX1033: la ruta // tiene que conocerse al compilar. fn tipografia() -> String { return include_bytes("examples/by-example/assets/tipografia.woff2") } fn servir_tipografia() -> Response { let cuerpo: String = tipografia() var r: Response = response_new(200, cuerpo) // headers_flat es intercalado [nombre, valor, nombre, valor, ...]. r.headers_flat.push("Content-Type") r.headers_flat.push("font/woff2") // Un recurso empotrado es inmutable por definición: cambia solo si se // recompila, así que puede cachearse para siempre. r.headers_flat.push("Cache-Control") r.headers_flat.push("public, max-age=31536000, immutable") return r } fn main() { let datos: String = tipografia() // str_byte_length y NO length(): length() cuenta codepoints UTF-8, y esto // son bytes arbitrarios. Es el gotcha `strings-are-bytes` del repo. println("bytes: " + int_to_string(str_byte_length(datos))) // La firma del formato sobrevive, y el byte 8 es un NUL: si algo tratara // el recurso como cadena de C, el cuerpo se cortaría justo acá. println("firma: " + datos.substring(0, 4)) println("byte 8 (NUL): " + int_to_string(datos.charAt(8))) // La identidad byte a byte, medida: este sha256 es el del archivo en disco // (sha256sum examples/by-example/assets/tipografia.woff2). println("sha256: " + sha256(datos)) // La respuesta HTTP lleva el recurso entero, sin leer nada del disco. let resp: Response = servir_tipografia() println("respuesta: " + int_to_string(resp.status) + " con " + int_to_string(str_byte_length(resp.body)) + " bytes") // Dos llamadas a la MISMA ruta comparten un solo global: el recurso ocupa // sus bytes una vez, no una por uso. let otra: String = tipografia() if str_byte_length(otra) == str_byte_length(datos) { println("segunda llamada: el mismo recurso") } // Para leer un archivo AL EJECUTAR —algo que el usuario elige en tiempo de // corrida— lo que corresponde es read_file/try_read_file. include_bytes no // existe en el intérprete ni en el REPL: ahí responde NYX3007. }
bytes: 776 firma: wOF2 byte 8 (NUL): 0 sha256: 0d4f9fe0394d9008f67caddb00171ffe7a3a63aaac4ec225d66f5fadcc79c1d2 respuesta: 200 con 776 bytes segunda llamada: el mismo recurso
Cómo funciona
Los bytes llegan intactos, NUL incluidos: un .woff2 real está lleno de ellos, y por eso la receta mide con str_byte_length y no con length() — length() cuenta codepoints UTF-8, esto son bytes arbitrarios (el gotcha strings-are-bytes del repo). El byte 8 del archivo es un NUL de verdad, y sigue siendo NUL después de embeberlo.
El valor sirve TAL CUAL como cuerpo de una respuesta HTTP: servir_tipografia lo asigna directo a Response.body, sin leer nada del disco en tiempo de ejecución. Como el recurso es inmutable por definición — cambia solo si se recompila —, la respuesta puede cachearse para siempre (Cache-Control: public, max-age=31536000, immutable).
Dos llamadas a la misma ruta comparten un solo global: el recurso ocupa sus bytes UNA vez en el binario, no una por uso. Para un archivo que el usuario elige en tiempo de ejecución corresponde read_file/try_read_file, no include_bytes — que además no existe en el intérprete ni en el REPL, donde responde NYX3007.