why?
One of my friends was actively pitching Rust as new shiny thing i should try. I am in search for a better language for my gamedev needs, and after a week of attempts on integrating C++23 modules into my renderer (for faster compile times without headers), which totally failed - bugs, ICEs, docs so bad that reading source is more useful, - i realized how much time I spend on CMake (and earlier Make - I love slashes btw), dependency management, resolving compiler differences and other non-programming stuff, and thought that maybe Rust - which is praised for tooling - could solve that
So, i ported my C++ Vulkan renderer to Rust
If you are a Rust dev, i would really appreciate code review of it (link). Suggestions about compile times are especially welcome.
(first impression) positives
Cargo is a build system that I don't think much about (i like that). Tooling is amazing: rust-analyzer, clippy, fmt (Rust is the only language where a formatter almost does not annoy me), plus community tools like cargo-asm and lots of others.
And Rust has a lot of nice features!
- Enums
- backtraces (i basically dont use a debugger)
- built-in checks (out-of-bounds, alignment, overflows / zero-div). I wish they were more optional though
- No constructors (seriously, there are more constructors in
C++than there are math subjects).Click to see C++ constructors meme
- built-in tests, benchmarks (
#![feature(test)]+black_boxmakes it soooo easy) and docs (fn arg docs when?) - stack slices with nice syntax, default trait functions, associated types - a lot of expected qol features
include_bytes!()(i wish it was auto-aligned)- proc_macro let you rewrite language if something is missing
- simple wasm builds (though you do need some extra tooling (and
--target-cpu=mvpnow)).Click to see my renderer running in web! (right here)
(first impression) negatives
- I'm not sure if it clicked or not. I frequently wonder if im still missing a fundamental piece of
Rustphilosophy - Sometimes i cant find any good examples. I thought i understood something and tried to utilize move semantics only to get mistmatch between drop (which takes
&mutand notmut) and the fact that i need to take out a thing from a struct. I had to either wrap it inOption<_>or useMaybeUninit Rustis not really good with math - you need big libraries for basic quaternions and matrices, and syntax is still ugly (i have some plans on improving it with a bunch of proc_macro trics, but... it could have been just integrated into langauge, like slices.)- Gamedev needs safe code that does not fall under safe
Rustquite often. And vice versa, actually - gamedev writes "unsafe" code that falls under safeRust- there's a trend to trick the borrow checker - usingVec[index]as "virtual memory" (effectively disabling a borrow checker) - Sometimes, you are not given control over things - other people decided you should not have it, and now you need to come up with weird solutions (for problems that could have not existed!). You cannot just disable slice bound checks for a scope - rewriting ops with proc_macro wont modify called functions, custom wrapper is ugly and has the same problem, replacing panic hook with unreachable breaks panics everywhere (not just for desired scope). Again, it is absolutely possible to have fine-grain control in
Rust- its just ugly, verbose and unreadable.
some real info
- my binary sizes went from ~600 KB to ~700 KB (compared to
C++) - likely due to stack slices (which is good: why heap-allocate if the stack suffices?). btw, just removing thiserror and anyhow cut ~100kb - performance-wise nothing really changed:
Rustis faster at low opt-levels (not precompiled std, i checked), and on highest opt-levels the gap narrows (with gcc being slightly faster for C++). - compile times are almost the same (~4.5s -> ~4.4s for similar dev build, but i cant really tell since im unable to build my C++ anymore and dont want to go through pain of fixing CMake again), both
C++andRustcompile times hurt iteration speed (i developed a habit of basically not running my code at all for a very long time. But lsps compensate it).
blazingly fast
Performance-wise, Rust is truly a rocket. But not all Rust.
C-like* Rust is a beast. Simple references and struct hierarchy forces you into code that produces insane assembly. Enums for polymorphism are just regular functions. However, wrap everything in Options, Rc Cells and Boxes, dyn traits and deep nested containers, and it's not so fast anymore.
So far, most Rust code i've seen sticks to one of those two extremes - and people dont feel like telling you right away which one it is for a specific library
libraries
There is just not that many great libraries. Initializing OpenGL with winit (whose examples use deprecated code cause they change API so often) and glow is harder than with assembly. Algebra libraries are written by people who dont seem to code at all (compile times, syntax, hello?). Vulkan wrappers changing API and names dealt me even more pain than gltf loader (which is more complicated than thermodynamics). Peak frustration was when I couldn't figure out the MagicaVoxel (.vox) parser docs and ended up porting the C++ parser instead (spent less time on that, huh).
In Rust you don't spend a day trying to make a library compile & link - you spend a day looking for one that doesn't suck. Crates refactor every few months and break APIs for no practical reason (semantic versioning helps). Documentation is often useless (says a lot, explains nothing), and logic is smeared acros dozen layers of tiny functions (std has better docs, but sufffers from unreadable source more) I really miss C libraries where you go to function source and immediately understand what it is doing. And libraries pull dozens of dependencies (which is not a problem by itself, but ...).
overall
Rust seems like a solid language choice, something like local maximum for its "language idea". I might need to throw away most of the libraries and rewrite it with a lot of proc_macro to get nice syntax, but its still not bad - i enjoy writing it, and my code works first try more often. Hope that one day it finally clicks
I think i'll also try language on the other end of the complexity spectrum - something closer to C, likely Odin - more optional "safe", nicer syntax for "unsafe", first-class SIMD, much faster compile times, and more importantly - simplicity as a language idea, which library authors are aware of. I suspect that Rust is still going to be a better tradeoff for me, but why not give other languages a try?