Skip to content

Hello! 👋 My name is Nathan Weir. This is a fun personal project for using AI to build a bespoke, domain-specific programming language. It is not a serious, professional project. This site and the language itself are largely generated via Claude Code. If you find yourself programming with Weir, have fun - but use at your own risk!

C FFI

Weir can call C functions directly via extern "C" declarations. This is how Weir interacts with system libraries like OpenGL, GLFW, SDL, and the C standard library.

Use extern "C" to declare C function signatures. Multiple functions can be declared in a single block:

(extern "C"
(defn abs ((n : i32)) : i32)
(defn SDL_Init ((flags : u32)) : i32)
(defn SDL_CreateWindow ((title : String) (x : i32) (y : i32)
(w : i32) (h : i32) (flags : u32)) : Ptr))

The type checker registers these signatures and makes them available for calling.

Calling extern functions requires an unsafe block — C code can violate all of Weir’s safety guarantees:

(defn main ()
(unsafe
(println (abs -42))
(println (abs 7))))

Without unsafe, the compiler produces an error:

;; Compile error: calling extern function 'abs' requires unsafe block
(println (abs -42))

unsafe is narrower than in Rust. It means: “I’m calling code that the Weir compiler can’t verify.” Specifically:

  • Calling extern "C" functions
  • Calling Rust functions explicitly marked unsafe

Safe Rust runtime functions (like println, str, len) do not require unsafe.

Ptr is a raw pointer type for FFI. It represents an opaque pointer to C memory:

(extern "C"
(defn malloc ((size : i64)) : Ptr)
(defn free ((ptr : Ptr)) : Unit))

Ptr is represented as i64 internally (pointer-sized). It is not GC-managed — the developer is responsible for memory.

C types map to Weir types:

C TypeWeir Type
int / int32_ti32
long / int64_ti64
unsigned intu32
floatf32
doublef64
char* / const char*String
Any pointerPtr
void (return)Unit

The mechanism depends on the execution mode:

ModeResolution
JIT (weir run)dlsym(RTLD_DEFAULT, name) — looks up symbols in loaded shared libraries
AOT (weir build)System linker (cc) with -l flags
Interpreter (weir interp)libffi + dlsym for dynamic C function invocation

For JIT mode, shared libraries must be loaded before symbols can be resolved:

Terminal window
# Via --load flag
weir run game.weir --load /usr/lib/libglfw.so
# Via package system (automatic)
cd demos/tetris && weir run # reads native.link from weir.pkg

For AOT builds, pass library names with -l:

Terminal window
weir build game.weir -l glfw -l GL -l m

Or declare them in weir.pkg for automatic linking:

(package
(name "my-game")
(version "0.1.0")
(main "game.weir")
(native
(link "glfw" "GL" "m")))

Packages can include C source files that are compiled automatically:

(native
(sources "gl_helper.c" "audio.c")
(link "glfw" "GL" "m"))

See Native Libraries for details.

The OpenGL Tetris demo uses C FFI extensively:

;; Declare OpenGL/GLFW wrapper functions
(extern "C"
(defn gl_init ((w : i64) (h : i64) (title : String)) : i64)
(defn gl_should_close () : i64)
(defn gl_begin_frame () : Unit)
(defn gl_draw_rect ((x : f64) (y : f64) (w : f64) (h : f64)
(r : f64) (g : f64) (b : f64) (a : f64)) : Unit)
(defn gl_end_frame () : Unit)
(defn gl_cleanup () : Unit))
;; Use in game code
(defn main ()
(unsafe
(gl_init 800 600 "Tetris")
;; game loop...
(gl_cleanup)))

The expected pattern is for C FFI to live behind safe wrapper libraries. Most developers should never write unsafe:

;; In weir-gl library (wraps the unsafe calls)
(pub defn create-window ((w : i64) (h : i64) (title : String)) : i64
(unsafe (gl_init w h title)))
;; In game code (safe — no unsafe needed)
(let ((window (create-window 800 600 "My Game")))
...)