When Rust began gaining popularity, particularly in game development circles with engines like Bevy, I was genuinely intrigued. Rust promised safer code, fewer runtime errors, and tools like cargo
that made managing dependencies and project workflows seamless. On paper, it seemed like a step forward. But despite exploring Rust and Bevy, I found myself consistently drawn back to C++. Here’s why.
Understanding My Background and Perspective
I’ve been a programmer for many years, deeply entrenched in C++ and comfortable with direct control over memory, data structures, and predictable behavior. My workflow is optimized around precise control and clarity, knowing exactly what every line of my code does, and how it interacts with the rest of my application.
When I create something in C++, whether using Qt for applications, Unreal Engine for game development, or even simple libraries like Raylib for prototyping, the execution order and behavior are always explicit and predictable. This level of transparency and direct control is something I value greatly—arguably even more than language-level safety guarantees.
The Appeal of Rust and Cargo
I must admit, Rust brings impressive tooling to the table. Cargo, Rust’s package manager and build tool, dramatically simplifies the developer experience:
- Quickly create projects (
cargo new
). - Easily manage dependencies (
Cargo.toml
). - Compile and run with a single command (
cargo run
). - Simplified testing (
cargo test
).
This streamlined approach reduces friction for new developers and helps experienced developers rapidly prototype ideas. I found this particularly tempting because, in C++, setting up projects often involves wrestling with build systems like CMake, dependency managers like Vcpkg or Conan, and figuring out platform-specific quirks.
But tooling alone isn’t enough to win me over completely.
The Bevy Experience: Powerful Yet Frustrating
When I explored Bevy, Rust’s popular game engine built on the Entity Component System (ECS) pattern, the promise was enticing. ECS architectures are known for their scalability, modularity, and potential performance advantages, especially when combined with Rust’s safety features.
However, this very combination—Rust and ECS—highlighted one of my biggest pain points. ECS inherently abstracts and separates data from behavior, relying on systems scheduled to run without explicit guarantees of execution order unless manually specified. This abstraction, despite being theoretically elegant, felt practically cumbersome. It introduced uncertainty, unpredictability, and, at times, frustration.
Take a simple example:
- In C++, if I change a property of a struct or class, the effect is immediate and predictable. When I call a function that modifies a variable, the outcome is straightforward.
- In Bevy’s ECS, similar tasks require scheduling systems, and changes made by one system might not be immediately visible to another. Execution order, unless explicitly managed, can become unpredictable, making debugging and reasoning about your code significantly harder.
For someone accustomed to clarity and control, this feels like stepping back rather than forward.
The Cost of Rust’s “Safety”
Rust’s safety features are undeniably beneficial. The borrow checker, ownership model, and lifetime management significantly reduce memory errors, race conditions, and undefined behavior.
Yet, these benefits come at a cognitive and productivity cost:
- The borrow checker, though excellent at preventing errors, often feels like it’s fighting you—especially when dealing with complex, real-world application logic.
- ECS frameworks like Bevy amplify Rust’s complexity, adding further abstraction layers. Debugging becomes significantly harder, as you now need to reason about both Rust’s borrow rules and ECS scheduling simultaneously.
C++’s Underrated Strengths
Despite its criticisms, C++ offers unrivaled directness and predictability:
- Immediate feedback: Change data, see results instantly, no scheduling complexities.
- Mature ecosystems: Decades of tooling, libraries, and frameworks with proven stability and performance.
- Fine-grained control: Allocate memory, optimize performance, and manage execution flow exactly how you see fit.
Moreover, modern C++ (C++17, C++20, and soon C++23) introduces safety and ergonomic improvements that mitigate many traditional risks. Combined with powerful tools like CMake, Vcpkg, Conan, and IDEs like CLion, developers can achieve workflows almost as seamless as Rust’s cargo—just with greater control.
Why I’m Staying with C++
Ultimately, my decision to stay primarily with C++ comes down to control, clarity, and productivity:
- I can predictably structure, debug, and reason about my code without hidden abstractions interfering.
- My existing knowledge and experience significantly boost my productivity.
- The occasional overhead in tooling setup is outweighed by the long-term benefit of precise, predictable code execution.
While Rust and Bevy have compelling attributes, the loss of direct control and predictability is simply too great a trade-off for my current workflow and projects. Perhaps for large teams or specific safety-critical domains, Rust’s strengths become more persuasive. But for my professional and personal projects, particularly in areas like game development and UI frameworks (Qt and Flutter), C++ remains unmatched.
Final Thoughts
Programming language choice is inherently subjective and context-dependent. Rust is impressive, and Bevy’s ECS model offers real benefits, particularly at scale. But the clarity, predictability, and direct control that C++ provides align closely with my core needs and productivity preferences.
So, despite Rust’s rise and undeniable strengths, my deep connection with C++ remains strong and, I believe, justified.
At least, for now.
No comment yet, add your voice below!