I might be interested on how to build the WASM build more easier with Swift tools. I guess some tools exist to facilitate the final builds, but did not found it...
Do not hesitate to comment if someone experienced this before.
Never ever worked for me. Imagine, you actually learned basic Swift and Raylib, now you want "advanced" features in your game like navigation/pathfinding, model loading, ImGui, skeletal animation (those are actually vital for gamedev). You realize that
- Navigation is only ReCast which is pure C++ library,
- ImGui has C++ API as first-class citizen,
- Decent animation with compression is only open-sourced by OzzAnimation which is pure C++ project.
For gamedev interfacing with C is never enough, half of ecosystem is built heavily on C++. Even C++ interop is not enough (see Dlang). Without all those libraries you are bound to make boring 2d platformers.
Same for Zig, Odin, C3 etc.
Because Apple won't fix Swift's abysmal compile times, and there are languages with similar or better ergonomics without that flaw.
A more interesting question would be how well the C++ interoperability works that was added in Swift 5.9, does it work with all C++ headers, even headers that make extensive use of template code. Also how does Swift extract information needed for lifetime tracking, e.g. C++ APIs that return smart pointers and object lifetime ends on the caller's side. Does this only work for C++ stdlib smart pointer types, or are custom types also supported.
Perhaps if you're completely devoid of imagination.
It is in fact possible to make video games without deferring to open-source libraries for every single aspect of it.
Dear ImGui via the C bindings is actually quite nice and not much less convenient than the C++ API (the only notable difference is that the C API has no overloads and default params).
E.g. here's a control panel UI (from the below ozz-animation sample) with the 'Dear Bindings' approach (using a custom 'ig' prefix)):
https://github.com/floooh/sokol-samples/blob/d8429d701eb7a8c...
Dear ImGui is a bit of an outlier for C++ libraries though, since it is essentially a C API wrapped in a namespace.
OzzAnimation is also fairly trivial to wrap in an (abstracted) C API, for instance I use this in some of the sokol-samples:
https://github.com/floooh/sokol-samples/blob/master/libs/ozz...
Implementation: https://github.com/floooh/sokol-samples/blob/master/libs/ozz...
...used in this sample:
https://github.com/floooh/sokol-samples/blob/master/sapp/shd...
...WASM live version:
https://floooh.github.io/sokol-html5/shdfeatures-sapp.html
TL;DR: quite a few C++ libraries out of the game-dev world are actually quite easy to access from C or languages that can talk to C APIs, mainly because the game-dev world typically uses a very 'orthodox' subset of C++ (no or very restricted C++ stdlib usage, no rtti, no exceptions, ideally no smart pointers in the public API).
Show me a real C++ interop example. Does function, constructor and operator overloading resolve correctly? How about C++26 reflection?
They’re using it in their work on FoundationDB. Looks good, but has limitations. Swift can call C++ and vice versa, Swift classes can inherit from C++ and vice versa, but not for all code, and may need work adding annotations on the C++ side. See https://github.com/apple/foundationdb/blob/main/SWIFT_GUIDE.....
There’s a good video on that work from a few years ago that was discussed on HN in https://news.ycombinator.com/item?id=38444876.
Since Ladybird team abandoned their Swift adoption for the browser I heard a lot of criticism about the Swift ecosystem and the interaction between Swift and C/C++ projects.
My usage of Swift is mainly for command line tools, recreational programming (like Advent of Code 2023 and previous years) or Metal programming.
In my previous experiments I really enjoyed Swift, and actually preferred it to some other programming languages like Rust. However it seems that programmers have wrong opinions about this programming language, especially about its accessibility (no it is not only for Apple platforms) and its actual power wrapping C/C++ libraries.
Today, I will demonstrate how easy I built a very basic Raylib game using Swift, with no FFI, and for macOS and web (using WASI).
This article is for demonstration purposes, and is not a tutorial. To this end I will not explain how to install Swift, the WASM SDK for Swift, etc.
However, if you are interested in reproducing this demonstration at home, you can find the finished project on my github and adapt for your own needs.
Unlike other languages, Swift does not require you to write manual FFI bindings or wrapper layers to interact with C code. FFIs are engineered to be completely invisible and automatic via the Clang importer (more about that later).
This means you can directly drop your C headers in your project, the static C library, and use the power of the Swift Package Manager to tell the compiler how the project needs to be compiled.
The code I want to run is very simple: initialize raylib, a window, and drawing a text.
This is the Swift code:
import CRaylib
let screenWidth: Int32 = 800
let screenHeight: Int32 = 600
#if os(WASI)
InitWindow(screenWidth, screenHeight, "WASM C Raylib from Swift!")
#else
InitWindow(screenWidth, screenHeight, "Raw C Raylib from Swift!")
#endif
SetTargetFPS(60)
let rayWhite = Color(r: 245, g: 245, b: 245, a: 255)
let darkGray = Color(r: 80, g: 80, b: 80, a: 255)
while !WindowShouldClose() {
BeginDrawing()
ClearBackground(rayWhite)
DrawText("It's alive... ALIVE!", 300, 300, 20, darkGray)
EndDrawing()
}
CloseWindow()
Generally a Swift project is constituted like this:
Package.swift
Sources/
ProjectName/
code.swift
All code in Sources will be used by the Swift Package Manager to compile my project.
In my case I want to differentiate what comes from the Raylib C project and my own code. So, my Swift project ends up like this:
Package.swift
Sources/
CRaylib/
macOS/
libraylib.a
WASM/
libraylib.a
raylib.h
MyGame/
main.swift
Everything in Sources/CRaylib is related to raylib itself: header files, static libraries (based on platforms, here macOS and WASM), etc. And everything in Sources/MyGame is my code, that will use the raylib C code.
I actually prefer downloading and replacing directly the files in my project, instead of letting a script, the package manager, or even the end-user, download them automatically. This approach comes directly comes from my experience in game engine development: never trust that the library will be available online in one minute, and never trust that the next version will not end up breaking your project or your dependencies.
This is actually an “anti-web” way to see things, but it actually saved my (dev-)life more than one time.
But how to build that project (let’s call it “MyGame”) now?
Let’s dig into the Package.swift file at the root of the project:
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "MyGame",
targets: [
.target(
name: "raylib",
path: "Sources/CRaylib",
publicHeadersPath: ".",
linkerSettings: [
// macOS
.unsafeFlags(["-L", "Sources/CRaylib/macOS"], .when(platforms: [.macOS])),
.linkedFramework("OpenGL", .when(platforms: [.macOS])),
.linkedFramework("Cocoa", .when(platforms: [.macOS])),
.linkedFramework("IOKit", .when(platforms: [.macOS])),
.linkedFramework("CoreVideo", .when(platforms: [.macOS])),
// WASM only
.unsafeFlags(["-L", "Sources/CRaylib/WASM"], .when(platforms: [.wasi])),
]
),
.executableTarget(
name: "MyGame",
dependencies: ["raylib"]
),
]
)
The interesting part of this demonstration is in how I defined the targets. This target is actually a Clang target, which specifies how the target is named, where is the source code, the header, and which libraries the linker needs to interact with for the linking step.
In this case, I have a target named raylib with the source code in a relative path (Sources/CRaylib) and different libraries depending on the OS (macOS or WASM).
Very easy!
If I try to run my project, I have an issue:
> swift run
warning: 'craylib': ignoring declared target(s) 'craylib, MyGame' in the system package
warning: 'craylib': system packages are deprecated; use system library targets instead
error: no executable product available
Hum, what is going wrong here?
Swift natively has no idea what a header (or .h) file is. As in, you cannot write import raylib.h in a Swift file directly.
To solve this, Apple built the Clang Importer into the Swift compiler. When the Swift compiler compiles Swift code, Swift silently boots up Clang, that parses the C headers, translates them into a format Swift can understand, organizes code into modules, and hands them back to Swift through.
However I actually had to help the compiler making the bridge between this Clang module and Swift, using a module.modulemap file in my CRaylib project:
module CRaylib [system] {
header "raylib.h"
link "raylib"
export *
}
Here, the module.modulemap can be explained like that:
module CRaylib [system]: “Create a brand new Swift module named CRaylib and treat it as a system library…”,header "raylib.h": “Here is the exact file you need to parse to find the C functions and structs…”link "raylib": “Whenever a Swift file imports this module, automatically tell the linker to look for a compiled library named libraylib.a…”export *: “Take every single C function you find and make it publicly available to my Swift project”Our final project structure is like this:
Package.swift
Sources/
CRaylib/
macOS/
libraylib.a
WASM/
libraylib.a
raylib.h
module.modulemap
MyGame/
main.swift
Now, if I try to run it… I have a window! Yeah!

Ok, let’s summarize what I did previously:
Package.swift file in order to declare my project and its dependencies: EASY,module.modulemap file to make the transition between raylib C files and my Swift project: EASY.EASY + EASY + EASY = EASY.
Ok, now that I have a native build… why not building it for WASM?
The Swift community made a ton of improvements the last years to build WASM applications using Swift.
If you are interested with it I would advise you to take a look at the official documentation.
Building the project for WASM was a bit more complicated, maybe because I miss some documentation to build this kind of project using the WASM SDK for Swift.
As shown here I did link the WASM project with the correct static library, in the correct path. And I do not have any modification to make in the module.modulemap file for raylib WASM. Nice.
All the following is just how to build the final WASM project:
> swift build --swift-sdk swift-6.2.4-RELEASE_wasm # i am using Swift 6.2.4 and Swift SDK for WASM has been installed
As I build for browser I have a linking error, but my object file has been built using Swift WASM compiler.
Because Swift’s stdlib was built for a headless server (WASI), it expects a terminal.
I needed to write a tiny C stub to intercept those terminal requests (errno, __wasi_args_sizes_get and __wasi_args_get) so the browser’s WebGL environment (Emscripten) doesn’t panic:
// wasi_stubs.c
#include <stdint.h>
int errno = 0;
int32_t __wasi_args_sizes_get(int32_t *argc, int32_t *argv_buf_size) {
*argc = 0;
*argv_buf_size = 0;
return 0;
}
int32_t __wasi_args_get(int32_t *argv, int32_t *argv_buf) { return 0; }
and finally to compile the project using emcc:
emcc .build/wasm32-unknown-wasip1/debug/MyGame.build/main.swift.o ./wasi_stubs.o \
Sources/CRaylib/WASM/libraylib.a \
-L</PATH/TO/YOUR/SWIFT_SDK>/usr/lib/swift_static/wasi \
-lswiftCore \
-s USE_GLFW=3 \
-s ASYNCIFY \
-o index.html
Important note
Browsers cannot handle infinite while loops without freezing.
In order to avoid rewriting my infinite loop function in main.swift I used the magic Emscripten flag ASYNCIFY to pause the Swift code and let the browser actually draw the frame.
Once I have my web files generated (index.html, index.js and index.wasm) I can spawn a tiny web server, and go to http://localhost:8080 to see…

The hardest thing was using emcc to build our final WASM binary. I did not even need to modify our source code or our project!
Mission completed!
Wrapping C for Swift was pretty straightforward, and I successfully spawned a window and draw some text using raylib. The usage of the Swift Package Manager is useful, and I did not need to dig so much into compiler issues, or build wrappers by hand, to actually interact with the C code of raylib.
So, if you want to build games using raylib, why not learn learning or use Swift for that?