Why I Built Wyn: A Programming Language That Compiles to C
I’ve spent years writing Python for DevOps tooling and Go for services. Python is a joy to write but painfully slow for anything compute-heavy. Go is fast but verbose — error handling alone accounts for a third of my code. Rust is powerful but the learning curve is brutal for the kind of tools I build daily.
So I built Wyn.
Wyn compiles to C, produces 49KB binaries, builds in under a second, and has a syntax that feels like Python with types. No garbage collector, no VM, no runtime. Just native code.
This isn’t a toy language. It has 850+ stdlib methods, a built-in web framework, database drivers (SQLite, PostgreSQL, Redis), a package manager, LSP support, and real OS thread concurrency. I use it in production.
The Core Idea: Compile to C
Wyn’s compiler translates your source code to C, then invokes your system’s C compiler (gcc/clang) to produce a native binary. This gives you:
- Portability — runs anywhere C runs (Linux, macOS, Windows, ARM, embedded)
- Performance —
fib(35)completes in 33ms, 85x faster than Python - Tiny binaries — a hello world is 49KB
- Fast builds — full compilation in ~400ms for dev builds
The C output is readable too. You can inspect what your Wyn code actually does at the machine level.
Memory Management Without a GC
Wyn uses Automatic Reference Counting (ARC). Every allocation has a reference count that’s incremented when shared and decremented when a reference goes out of scope. When it hits zero, the memory is freed immediately.
This means:
- Deterministic destruction — resources are freed the instant they’re no longer needed
- No GC pauses — critical for latency-sensitive applications
- No manual memory management — you don’t write
free()calls
The tradeoff vs. tracing GC is that reference cycles can leak. Wyn mitigates this by design — channels replace shared mutable state between threads, and the stdlib avoids cyclic structures. In a year of production use, I haven’t hit a cycle leak. If you’re building a graph database, use a different language. If you’re building web services and CLI tools, ARC is perfect.
Concurrency That Actually Works
This is where Wyn diverges most from other “simple” languages. It uses real OS threads with a thread pool:
fn fetch_user(id: int) -> string {
return http_get("https://api.example.com/users/${id}")
}
fn main() -> int {
var f1 = spawn fetch_user(1)
var f2 = spawn fetch_user(2)
var f3 = spawn fetch_user(3)
println(await f1)
println(await f2)
println(await f3)
return 0
}
Key design decisions:
- No async/await coloring — any function can be spawned, no special annotations needed
- 6μs spawn overhead — lock-free futures, slab-allocated and recycled
- True parallelism — 4x speedup on 4 cores for CPU-bound work
- Channels for message passing between threads
I specifically avoided the async/await pattern because it fragments ecosystems. In Go, everything is concurrent by default. In Rust and JavaScript, you have sync and async worlds that don’t compose cleanly. Wyn takes Go’s approach — spawn anything, await when you need the result.
The Pipe Operator
Small feature, massive impact on readability:
var result = data
|> parse_json
|> extract_users
|> filter_active
|> sort_by_name
Instead of nested calls like sort_by_name(filter_active(extract_users(parse_json(data)))), you read transformations top-to-bottom. This is borrowed from Elixir and F#, and it’s one of those features that once you have, you can’t go back.
Batteries Included
I got tired of languages where “hello world” is easy but “make an HTTP request” requires pulling in three dependencies. Wyn’s standard library includes:
- HTTP client and server with routing, middleware, JSON parsing
- Database drivers — SQLite, PostgreSQL, Redis
- Crypto — bcrypt, JWT, SHA-256, AES
- File formats — JSON, CSV, TOML
- Networking — TCP/UDP sockets, SMTP
- GUI — native desktop apps and SDL2 graphics
- CLI — argument parsing, colored output, progress bars
This means a Wyn project with zero dependencies can build a web API with auth, database access, and background jobs. The binary is still under 5MB.
Pattern Matching and Error Handling
Wyn has Result and Option types with exhaustive pattern matching. This is where it borrows from Rust — but without the borrow checker complexity:
enum Shape {
Circle(int),
Rect(int, int)
}
fn area(s: Shape) -> int {
return match s {
Circle(r) => r * r * 3
Rect(w, h) => w * h
}
}
fn safe_divide(a: int, b: int) -> Result<int, string> {
if b == 0 { return Err("division by zero") }
return Ok(a / b)
}
fn main() -> int {
match safe_divide(10, 0) {
Ok(v) => println("result: ${v}")
Err(e) => println("error: ${e}")
}
return 0
}
Guards make complex matching readable:
fn classify(n: int) -> string {
return match n {
0 => "zero"
_ if n < 0 => "negative"
_ if n > 100 => "big"
_ => "normal"
}
}
Building a Web Server
Here’s a complete HTTP API in Wyn — no dependencies, no framework to install:
fn handle(method: string, path: string, body: string, fd: int) {
var h = Web.match(method, path)
if h == 1 {
var users = Db.query("SELECT name, email FROM users")
Http.respond(fd, 200, "application/json", Json.encode(users))
} else if h == 2 {
var user = Json.decode(body)
Db.exec("INSERT INTO users (name, email) VALUES (?, ?)",
[user.get("name"), user.get("email")])
Http.respond(fd, 201, "application/json", "{\"status\": \"created\"}")
} else {
Http.respond(fd, 404, "text/plain", "not found")
}
}
fn main() -> int {
Db.open("app.db")
Web.get("/api/users", 1)
Web.post("/api/users", 2)
var server = Http.serve(8080)
println("listening on :8080")
while true {
var req = Http.accept(server)
spawn handle(req.split_at("|", 0), req.split_at("|", 1),
req.split_at("|", 2), req.split_at("|", 3).to_int())
}
return 0
}
Compile this and you get a ~75KB binary with SQLite, HTTP routing, JSON parsing, and concurrent request handling. No Docker image with 200MB of runtime dependencies.
A Practical CLI Tool
This is the kind of thing I build with Wyn daily — a tool that fetches data from multiple APIs concurrently and outputs a report:
struct ServiceStatus {
name: string
healthy: bool
latency_ms: int
fn display(self) -> string {
var icon = if self.healthy { "✓" } else { "✗" }
return "${icon} ${self.name} (${self.latency_ms}ms)"
}
}
fn check_service(name: string, url: string) -> ServiceStatus {
var start = Time.now_ms()
var resp = http_get(url)
var elapsed = Time.now_ms() - start
return ServiceStatus{
name: name,
healthy: resp.starts_with("2"),
latency_ms: elapsed
}
}
fn main() -> int {
var checks = [
spawn check_service("API", "https://api.example.com/health"),
spawn check_service("DB", "https://db.example.com/ping"),
spawn check_service("Cache", "https://redis.example.com/ping"),
]
println("Service Health Report")
println("─────────────────────")
for c in checks {
var status = await c
println(status.display())
}
return 0
}
Methods live inside the struct body — no separate impl blocks like Rust, no receiver syntax like Go. The struct is the complete unit.
Generators
Lazy evaluation without the complexity of iterators:
fn fibonacci() -> int {
var a = 0
var b = 1
while true {
yield a
var tmp = a + b
a = b
b = tmp
}
}
fn main() -> int {
var fib = fibonacci()
for i in 0..10 {
println(next fib)
}
return 0
}
Generators produce values on demand with yield. Combined with the pipe operator, they enable memory-efficient data processing pipelines.
Write Python Libraries in Wyn
This is one of the features I’m most excited about. You can write performance-critical code in Wyn and call it directly from Python — no C extension boilerplate, no ctypes, no Cython:
// mathlib.wyn
fn factorial(n: int) -> int {
if n <= 1 { return 1 }
return n * factorial(n - 1)
}
fn fibonacci(n: int) -> int {
if n <= 1 { return n }
return fibonacci(n - 1) + fibonacci(n - 2)
}
Build with one flag:
wyn build mathlib.wyn --python
This produces a shared library (.so/.dylib/.dll) and an auto-generated Python wrapper. Use it from Python immediately:
from mathlib import factorial, fibonacci
print(factorial(20)) # 2432902008176640000
print(fibonacci(35)) # 9227465 — runs in 33ms, not 2.8 seconds
The type mapping is automatic — int ↔ int, float ↔ float, string ↔ str, bool ↔ bool. No manual FFI declarations.
This means Python developers can keep their existing codebase and surgically replace hot paths with Wyn. No rewrite required. The Wyn function runs 85x faster and the Python code doesn’t change beyond the import.
Cross-Compilation
Since Wyn compiles to C, cross-compilation is straightforward — you just need a C cross-compiler for the target:
wyn cross linux-x64 app.wyn # Linux x86_64
wyn cross linux-arm64 app.wyn # Linux ARM64 (Raspberry Pi, AWS Graviton)
wyn cross windows-x64 app.wyn # Windows
wyn cross macos-arm64 app.wyn # macOS Apple Silicon
wyn cross ios app.wyn # iOS (requires Xcode)
wyn cross android app.wyn # Android ARM64 (requires NDK)
I build on macOS and deploy to Linux ARM64 (AWS Graviton) daily. The binary is self-contained — no runtime dependencies on the target. Copy it, run it.
What the Compiler Actually Outputs
People ask what the generated C looks like. Here’s a simplified view of what wyn build produces for a basic program:
// Generated from: hello.wyn
// fn greet(name: string) -> string {
// return "Hello, " + name + "!"
// }
WynString* greet(WynString* name) {
WynString* _t1 = wyn_str_concat(wyn_str_lit("Hello, "), name);
WynString* _t2 = wyn_str_concat(_t1, wyn_str_lit("!"));
wyn_rc_dec(_t1);
return _t2;
}
The ARC operations (wyn_rc_dec) are inserted automatically. The C output is readable, debuggable, and you can step through it with gdb/lldb if needed. This is the advantage of compiling to C rather than LLVM IR — the intermediate representation is something humans can actually read.
Real-World Benchmarks
Beyond the fib microbenchmark, here are numbers that matter for actual applications (Apple M4, clang -O2):
| Benchmark | Wyn | Python 3.12 | Go 1.22 |
|---|---|---|---|
| fib(35) | 33ms | ~2,800ms | ~55ms |
| Sort 100K ints | 2ms | ~30ms | ~15ms |
| 1M string appends | 6ms | ~200ms | — |
| 100K method chains | 8ms | ~50ms | — |
| HTTP server (hello world) | ~75KB binary | — | ~7MB binary |
| Compile time | 411ms | — | ~500ms |
| Spawn overhead | 6μs/task | — | ~1μs/goroutine |
Memory usage is also tight: 1M mixed string operations peak at 1.4MB RSS. Zero memory leaks verified with AddressSanitizer across all test patterns.
The honest comparison: Go’s goroutines have lower spawn overhead (1μs vs 6μs), and Go has a mature ecosystem. But Wyn produces binaries 100x smaller and compiles just as fast. For the use cases I target — CLI tools, small web services, data processing — the tradeoffs work.
What Wyn Is Good For
After a year of using it, these are the sweet spots:
CLI tools — fast startup, tiny binaries, easy cross-compilation. I replaced several Python scripts with Wyn and deployment went from “install Python 3.11 and pip install requirements” to “copy one binary.”
Web services — the built-in HTTP server with PostgreSQL and Redis support means you can build a complete API without any external dependencies. 49KB base binary + your logic.
Data pipelines — Python-like ergonomics for data transformation but 85x faster execution. List comprehensions, generators, and the pipe operator make data processing code readable.
DevOps tooling — same niche as Go, but with less boilerplate. Pattern matching on error types, string interpolation, and methods-in-structs reduce the ceremony.
What Wyn Is Not
I’m honest about the limitations:
- Not for massive teams — the ecosystem is young, IDE support is partial (VS Code and Neovim via LSP), and you won’t find Stack Overflow answers
- Not for systems programming — no manual memory control, no inline assembly, no
unsafeblocks - Not a Python replacement for ML/AI — no NumPy, no PyTorch, no Jupyter notebooks
- Not battle-tested at scale — it has few production users compared to Go or Rust
Try It
Install from source or via the install script:
# Option 1: Install script
curl -fsSL https://wynlang.com/install.sh | sh
# Option 2: Build from source
git clone https://github.com/wynlang/wyn.git && cd wyn && make
# Option 3: Download binary directly
# https://github.com/wynlang/wyn/releases
Then create a project:
wyn new myapp
cd myapp
wyn run
Or try the online playground without installing anything.
The documentation covers everything from basic syntax to building web servers and cross-compiling for ARM.
Where It’s Going
Wyn is MIT licensed and on GitHub. The immediate roadmap:
- Mobile compilation (iOS/Android) — in progress
- WebAssembly target
- Broader package ecosystem
- More IDE integrations
If you’re a Python developer frustrated by performance, or a Go developer frustrated by verbosity, give Wyn a look. I built it for myself, but I think it fills a real gap in the language landscape.
I’m the creator of Wyn. This article reflects my genuine experience building and using the language. Visit wynlang.com for docs, playground, and source code.