“Yet another language” is a fair thing to be skeptical about. There are plenty of options already, most of them backed by companies an order of magnitude larger than whatever I can put behind GX. So before anything else — before the syntax, the backends, the module system — I owe you an honest answer to the obvious question: why does this exist?
The short version: after years of writing games, graphics code, and systems utilities, I kept running into the same ceiling. Every language I reached for had a specific thing it wouldn’t let me do cleanly. GX is the language I wanted to already exist.
This post walks through what I ran into, and where I think the gap is.
The landscape, in one paragraph each
C
C is still the baseline. It compiles everywhere, interoperates with everything, and gives you exact control over memory and layout. But it’s also 50+ years old and it shows. No modules — just #include and the preprocessor. No real strings — a char* is a pointer to a byte, possibly null, possibly not terminated, possibly pointing at something that was freed five function calls ago. No slices. No type-safe generic containers. The preprocessor is a text-substitution system that runs before the type checker even knows what a type is, which means macros don’t compose and errors come out the other side as incomprehensible cascades. Writing safe, large-scale C is possible — a lot of the world’s software proves it — but it takes discipline, a lot of conventions, and a lot of defensive code. The language doesn’t help you.
C++
C++ solves some of C’s problems by adding features. A lot of features. The modern language has lambdas, templates, concepts, constexpr, modules (finally, kind of), ranges, smart pointers, move semantics, and more. The good parts are genuinely good. But the cost is the rest of the language. Every line of C++ is written in a dialect of C++, and picking the dialect — which exceptions to allow, which RAII patterns, whether to use std:: containers, which build system and which ABI — is a months-long organizational decision before anyone writes a line of actual code. Compile times scale with template depth. Debug builds are slow. And most of the footguns C has are still there, now sharing the language with a hundred new ones.
Rust
Rust genuinely solves memory safety without a garbage collector, and the ecosystem is good. I’m glad it exists. But writing Rust, I don’t enjoy it. It’s strict in a way that stays strict — it doesn’t level off, it levels into a different plateau where you spend energy expressing the lifetime structure of your program rather than the problem you’re trying to solve. For some domains (OS kernels, browser engines, anywhere a memory bug is a CVE) that trade is clearly worth it. For a lot of what I do — games, graphics, tools, prototypes — it isn’t. I don’t want to fight the compiler about who owns the level editor’s undo buffer. I want to write the undo buffer.
Compile times are also real. Cargo is excellent and the rebuild story is fine for small crates, but a clean build of a non-trivial Rust project still takes minutes. That compounds across a day of iteration. And even when the compiler is finally happy, I’m not — I’ve spent the hour pleasing the compiler, not building the thing.
Odin
Odin is the closest to what I want. It’s an amazing language. Clean syntax, genuinely ergonomic, no GC, great C interop, built-in vector and matrix math, implicit context, distinct types without the type-theory vocabulary — it gets a lot of decisions right, and writing it feels good. If someone put a gun to my head and told me to use an existing language, this is the one I’d pick.
Where Odin runs into a ceiling, for me, is portability as an application platform — not portability as a systems language. Those are different problems, and the distinction is worth being explicit about.
Odin is portable as a systems language. It targets the major desktop platforms and has solid vendor bindings for the APIs you actually want to call: Metal, MetalKit, CoreVideo on Darwin/macOS; Direct3D on Windows; Box2D and the usual graphics stack cross-platform. If you’re building a C-style engine, a custom renderer, a tool, or a desktop game, Odin is a genuinely pleasant choice.
Where it gets harder is shipping an app — especially on mobile. iOS and Android aren’t just “compile to ARM and link”. The app ecosystem is the work: project generation, app lifecycle, signing and provisioning, Objective-C/Swift interop on iOS, Java/Kotlin/JNI/Gradle integration on Android, UI framework bindings, asset packaging, store deployment, debugging and profiling integration. Odin doesn’t have that layer, and without it “portable” means something narrower than most people read when they see the word. For mobile app work today I wouldn’t reach for Odin unless I was essentially writing a custom engine and was comfortable writing the platform glue myself; for normal mobile apps, Flutter, Kotlin Multiplatform, Swift/Kotlin native, or even C++ with platform wrappers are more practical.
I mention all of that because it’s the exact problem GX is trying not to stop short of. The current GX surface is a systems language — and a good one — but the longer-term direction is to close that app-platform gap: editor and tooling story, platform glue, asset pipelines, mobile targets, all treated as part of the language’s commitment, not a problem left to the user.
Zig
Zig is next closest. Its philosophy — no hidden control flow, no hidden allocations, explicit everything — is the right philosophy, and I’m rooting for it. But writing Zig day-to-day, I don’t feel it. It’s verbose. It’s in your face. Every detail the language is proud of making explicit is one more thing you have to type out, on every line, forever. After a while I realised I wasn’t having fun. The philosophy is right; the moment-to-moment experience wore me down.
I respect Zig enormously. GX is, in a lot of ways, what happens when you agree with Zig’s philosophy but want the day-to-day experience of writing the language to feel lighter.
Go
Go is great for the things it’s great at: network services, CLIs, anything where you want to ship a single binary and not think about memory. But it has a GC, and that’s a deal-breaker for the domains I care about. A 5 ms GC pause is catastrophic in a 16.6 ms frame budget. Generics arrived, but the ecosystem is still stitched around their absence. The language is deliberately small, which is a feature, but the result is that you write a lot of code that a stronger type system would let you not write.
The gap
So here’s what I kept wanting:
-
Transpiles to C. Not because C is beautiful, but because C is ubiquitous. If I ship C, I can compile for anything with a C compiler — which is everything. I inherit every debugger, every profiler, every linker, every target that C has reached. That’s 50 years of tooling I don’t have to rebuild.
-
Modern syntax, modern ergonomics. Modules instead of
#include. Fat strings with O(1) length. Slices. Tagged unions. Enums that aren’t just integers. String interpolation. Pattern matching. Defer. The features that have been standard in good languages for 20 years. -
No garbage collector, no hidden allocations. Every
mallocis visible. Every free is visible. No destructors running behind your back. You know what your program is doing. -
No borrow checker gatekeeping. Safe defaults — const-by-default pointers, functions that can’t return pointers to local variables — but if you need a cycle in your data structure, you just write it. The compiler trusts you.
-
Compile-time is a real tier of the language. Not a macro system, not template metaprogramming. Actual compile-time functions, compile-time loops, reflection over struct fields — usable from day one, debuggable like regular code.
-
Build configuration lives in source. If a file needs
-lopengl32on Windows, that’s a property of the file, not of a separate build script that the build server has to keep in sync.@link,@cflags,@cfilego in the.gxfile that needs them. -
First-class C interop without a bindings generator. If I have a C header I want to use, I should be able to
@c_includeit, write anexternblock, and go. No codegen step, no build-system plumbing, no versioning a generated file.
No single existing language does all of this. C does (1) and (3) and (7). Rust does (3) and (5) and (7) but not the way I want. Odin does most of it beautifully on desktop but doesn’t yet reach into the app-platform story I want GX to eventually own. Zig agrees in philosophy but is heavier to write. Go does (2) but not (3). Each of them is a reasonable set of trade-offs for something, but none of them is the set of trade-offs I want for this.
GX’s position
Here’s the honest core of it: I spend roughly a third of my life programming. That’s not a neutral fact — it means the language I use is, in practice, one of the rooms I live in most. I don’t want that room to be a room I fight with. I want it to be one I can relax in, move fast in, and still take seriously.
So the bar GX is aiming at is specific:
- Simple. As simple as it can be without lying about what the machine is doing.
- Concise. Write as little code as the problem requires — no ceremony, no repeated shape, no typing out the same four lines everywhere.
- Serious and powerful. Not a toy. Real types, real compile-time, real C interop, real performance. Suitable for shipping games, engines, tools, systems code.
- Not restrictive. It trusts you. Safe defaults, but if you need a pointer cycle or a weird memory layout, you just write it.
- Fast iteration. The edit/compile/run loop is seconds, not minutes. The bundled TCC path means a clean rebuild of a whole project still finishes faster than you can context-switch.
- Fast at runtime. Zero GC, zero hidden allocations, C-compatible layout. With
-O2/-O3through clang or gcc when you need it. - Portable to anywhere C reaches. Windows, macOS, Linux, BSDs, embedded targets, WASM via Emscripten — wherever someone has ported a C compiler, GX runs.
- Enjoyable. This one is the soft one and also the real one. Writing GX should feel good. If it doesn’t, the rest doesn’t matter; I’ll stop using it, and so will you.
GX isn’t trying to be a better Rust. It’s not trying to replace C++. It’s not competing with Go for backend services. It’s specifically trying to be C with quality-of-life features — the language you reach for when you’d reach for C today, but you want the last 30 years of language design to have happened, and you want the writing of it to be fun.
And honestly — if you come from Python, JavaScript, or Ruby, GX is probably more for you than you’d expect. It’s easy to write. Types are optional at the declaration site; you don’t have to spell them out for every variable. Strings are first-class, with real string manipulation built in — no char* dance. Arrays, dynamic arrays, and hash maps are part of the language, not a library decision. defer makes cleanup obvious and memory management dramatically easier than C or C++. If you’ve been writing dynamic-language code and you’ve ever wished you could drop into something fast and native without surrendering an entire summer to the borrow checker, GX is a reasonable on-ramp. With a bit of effort you get a language that’s faster, more predictable, and still comfortable to read a week later.
If you mostly write safety-critical software and you want the compiler to prove absence of memory bugs, Rust is probably a better fit. If you mostly write web backends with heavy concurrency, Go is probably a better fit.
The honest flaw right now is the ecosystem. GX is young — three years in development — and the library situation reflects that. The mitigation is the C ABI: because GX speaks C natively, the entire C ecosystem (decades of libraries, every graphics API, every database client, every codec) is already reachable through @c_include and extern. That also means the GX-native ecosystem can grow quickly, because most of what a new module needs to do is wrap a C library that already exists.
Where GX is going
This post is mostly about why. But some of the “why” only makes sense once you see where the language is actually pointed, so a quick sketch of the road:
Editor and tooling. There’s already a first-class IntelliJ plugin (syntax highlighting, completion, go-to-definition, find-usages, debugger integration). An LSP is next, which brings VS Code, Neovim, Sublime, Helix, Zed — the rest of the editor landscape — to parity. Tooling is a first-class part of the language, not a retrofit.
Two native backends, today. GX already ships a C transpiler (default, bundled TCC, zero-dependency) and an LLVM IR backend (--backend llvm, clang-based). Same language, same modules, same output. The C path gives portability and fast iteration; the LLVM path gives native vector IR and the full clang optimization pipeline.
GPU as a real tier of the language. This is the one I’m most excited about. The plan is to make GPU compute part of the language, not a separate shader dialect: you write the kernel in GX, mark it, and the compiler handles dispatch. Same syntax, same types, same type checker. No GLSL/HLSL/WGSL context switch, no manual buffer marshalling plumbing in user code. The backend abstraction that already separates C and LLVM is the same abstraction a GPU compute backend slots into.
Vector-first numerics. GX already has vec2/vec3/vec4 and mat2/mat3/mat4 as built-ins with operator overloading and swizzle. The next step is a unified numeric model — batched types like f32x8, boolx8, vec3x8, mat4x8, with if accepting mask conditions and lowering to predicated execution. Scalar code stays scalar. Batched code lowers to real vector IR on LLVM, to structured loops on C, to TypedArrays on JS. One language, one set of semantics, three lowering strategies — with SIMD performance as an implementation detail of the backend, not a separate dialect the user has to learn. That plan also lines up naturally with the GPU direction and with future AI/tensor workloads.
Application platform, not just systems language. Back to the Odin section — systems portability is the starting point, not the end goal. Over time the language story has to include the less glamorous glue: project scaffolding, platform wrappers, packaging, signing, deployment. Those are where most languages for this domain stop short, and they’re the reason “portable” often ends up meaning less than it sounds like it does.
None of this is promised for a specific date. It’s the direction. The language surface already there — modules, fat strings, hash maps, defer, allocators, compile-time reflection, two working backends — is the foundation; the list above is what it’s foundation for.
If you write games, graphics, engines, tools, systems utilities, embedded code — if you live in that space where C is still the default answer and you’ve been making peace with it for longer than you’d like — GX is trying to be a better default answer. One you actually enjoy opening the editor for.
What’s next
The next few posts go into the actual decisions. Post 02 is about why GX transpiles to C (and what that means for the LLVM backend we just shipped). Post 03 is about the memory story — how we replace a garbage collector and a borrow checker with something simpler. Post 04 is about strings. Post 05 is about compile-time.
If you want to skip the theory, the Playground runs real GX in the browser and the docs cover the full language surface.
Discussion: What frustrated you about your current language for systems or game dev? What did you give up on and route around? I’d genuinely like to hear — those frustrations are the actual shape of the problem GX is trying to solve.