Hello World [Red]
Status: ALPHA (unverified). These bindings are constructed from the published Red/System specification and have not been compiled with a Red toolchain (none was available at generation time). The FFI mechanism is sound and cited in
scripts/RED_FFI_FINDINGS.md, but expect to fix small syntax issues on first build, and see the Honest limitations section below.
Introduction
Red is a full-stack language: a high-level, Rebol-inspired dialect (Red) sits on top of a low-level, statically typed, C-like dialect (Red/System). Both compile with the same ~1 MB toolchain into a single, dependency-free native executable.
Azul's binding targets Red/System, because that is the dialect with a
general external-library FFI: the #import directive dlopen-loads libazul at
startup and maps every Az* C symbol into scope. High-level interpreted Red (the
console) cannot load an arbitrary shared library on its own — it reaches C only
through Red/System, via the routine! type and #system-global embedded
blocks. So the honest framing is „Azul from Red/System,“ which is still ordinary
Red code compiled by the ordinary Red toolchain.
Everything lives in a single generated file, azul.reds, with two layers:
Az*— raw#imported C-ABI functions mirroringazul.hone-to-one (AzDom_createBody,AzButton_create, …). Structs are passed and returned by value using Red/System'svaluekeyword, which the spec documents as ABI-compatible with mainstream C compilers.azul-*— a small host-invoker convenience layer:azul-register-<kind>turns a Red/System[callback]function into anAz<Kind>Callbackvalue, andazul-refany-create/azul-refany-getwrap and recover your data-model pointer.
Callbacks dispatch through libazul's host-invoker plumbing: each registered
function gets a handle id, and when the framework fires the callback it calls a
per-kind invoker inside azul.reds that looks the function up and runs it. Wire
those up once at startup with azul-host-invoker-init.
Installation
You need the Red toolchain (a single ~1 MB binary from
red-lang.org — the redc compiler front-end can
build Red/System programs). No package manager or system libraries are required;
Red produces a static, dependency-free executable.
The download set is: the native library, the generated azul.reds bindings, and
the counter example source.
Linux:
curl -O https://azul.rs/ui/release/0.2.0/libazul.so
curl -O https://azul.rs/ui/release/0.2.0/azul.reds
curl -O https://azul.rs/ui/release/0.2.0/hello-world.red
macOS:
curl -O https://azul.rs/ui/release/0.2.0/libazul.dylib
curl -O https://azul.rs/ui/release/0.2.0/azul.reds
curl -O https://azul.rs/ui/release/0.2.0/hello-world.red
Windows:
curl -O https://azul.rs/ui/release/0.2.0/azul.dll
curl -O https://azul.rs/ui/release/0.2.0/azul.reds
curl -O https://azul.rs/ui/release/0.2.0/hello-world.red
Build & run
hello-world.red #includes azul.reds, so you compile a single file:
redc -r hello-world.red -o hello-world # -r = release build
LD_LIBRARY_PATH=. ./hello-world # libazul must be on the loader path
#import loads libazul at executable startup, so the library must be findable
by the OS loader at run time (same directory, or LD_LIBRARY_PATH /
DYLD_LIBRARY_PATH / PATH).
The counter, explained
The full source is in
examples/red/hello-world.red. The
shape mirrors every other Azul binding:
- A
model!struct holds the counter (starting at 5). on-clickis a[cdecl]callback: it reads the model back out of theAzRefAny*viaazul-refany-get, bumps the counter, and writesAzUpdate_RefreshDomthrough theoutpointer.on-layoutbuildsbody > [ div{font-size:32px} > text(counter), button ], registeringon-clickwithazul-register-button-on-click-callback.maincallsazul-host-invoker-init, registers the layout callback, builds the window options, and callsAzApp_run.
Every callback dispatcher takes pointer arguments only (plus one out-pointer
for the return) — no aggregate crosses the callback boundary by value. libazul's
static thunk does the by-value plumbing on the C side, so the Red/System code
only ever sees byte-ptr!s.
High-level Red
To drive Azul from a high-level Red (Red [...] header) program instead, wrap
the pieces you need in routine!s and inject the #import with
#system-global:
Red [Title: "azul from high-level Red"]
#system-global [ #include %azul.reds ]
make-app: routine [ /local ... ][ ... AzApp_create ... ]
routine! bodies are Red/System, compiled to native code; they are the only
place high-level Red values cross into C. This is more ceremony than using
Red/System directly, which is why the shipped example is Red/System.
Honest limitations
- Unverified. No Red toolchain was available to compile-check the output.
- 64-bit integers. Red/System's
integer!is 32-bit and it lacks a portable int64. Host-handle ids stay small (they start at 1) and 64-bit-valued API fields need an int64 shim. Seescripts/RED_FFI_FINDINGS.md. - Tagged unions.
AzOption*/AzResult*/ union types are emitted as opaque blobs pending exact-size wiring of the shared layout pass; construct and inspect them through C-API helpers, never by field access. The counter demo only round-tripsAzUpdate(a unit enum), so it is unaffected. - arm64 by-value aggregates. Red/System claims mainstream-C-ABI compatibility, but the AArch64 rules for large (>16 B) by-value structs are subtle; first verification should be on x86-64.
Common errors
redc: cannot open %azul.reds— run the compile from the directory that holds bothhello-world.redandazul.reds.- Loader can't find
libazul— setLD_LIBRARY_PATH=.(Linux),DYLD_LIBRARY_PATH=.(macOS), or put the DLL beside the exe (Windows). - Window opens but the button does nothing — you forgot
azul-host-invoker-initbeforeAzApp_run, so libazul cannot dispatch into Red. - Counter renders but never updates — the click callback did not write
AzUpdate_RefreshDomthrough theoutpointer on every code path.
Coming Up Next
- Application Architecture — architecting a larger Azul application
- Document Object Model — the Dom tree: node types, hierarchy, and CSS