Note: Rust is designed to be as efficient as C++. Well, sort of, since we don't have all of the optimizations yet (we use llvm so we get most of them for free)
But Minecraft itself is in Java. And it does get pretty slow even with low settings as compared to other games on high settings.
Rust uses bounds checking in safe code, and despite idiomatic Rust allowing many bounds checks to be optimized out (eg. iterators instead of C style for loops), bounds checks are not free. It's possible that things like stricter aliasing rules in Rust will allow more optimizations and make up the speed difference, but the design of Rust does not guarantee the possibility of matching C++ performance unless you use unsafe code.
On the other hand, you have no choice but to use unsafe code in C++; that is, there's no line between code that is guaranteed to be memory safe and code that risks corruption and undefined behaviour.
In any case, the route people will hopefully take is to write the code in the obvious way, with bounds checks and all, and profile. Functions which take significant amounts of time can then be optimised more manually (and, bounds checks usually aren't the bottleneck).
At some point the performance margin grows so close that it's irrelevant to almost any application. IMO, it's already there for a broad swath of application classes. For those inner-most-loop, optimizer-has-failed-me cases, Rust can still fall back on unsafe code.
I'll note that this kind of skepticism has been going on since John Backus[1] was heckled over using (ahem, "wasting") then-precious CPU time on compiling code using the first versions of FORTRAN. His detractors (and everyone else) quickly learned the value of conserving programmer time vs. CPU time.
Games are one of the areas where it is a bit foggier as your rendering loop is incredibly sensitive to performance problems.
You have a ton of work to do and cannot take more than 16.7ms (60Hz) to do it in period. Heck given the recent move some have made for 144Hz monitors it leaves you with less than half the time (6.9ms).
Not to say that C++ is the only way to pull it off, just that games are a soft-real-time system which therefore have stricter requirements for performance.
Most inner loops in game code are iterating over collections, whether it be lists of actors, lists of vertices, skeletal animation data, or what have you. Rust does not incur bounds check overhead for iteration.
In my experience not being able to use iterators is rare in most Rust code. Inner loops that have array lookups are overwhelmingly iterating over collections. It's much more common to have to drop down to unsafe code because you need to implement some low-level data structure that Rust's ownership rules can't yet prove safe.
I have personally never seen the bounds checks be a problem except in microbenchmark-like things, and those are almost always compiler bugs involving missed optimizations that the compiler could have done but does not presently. I think "the design of Rust does not guarantee the possibility of matching C++ performance unless you use unsafe code" is too strong a statement.
> Rust does not guarantee the possibility of matching C++ performance unless you use unsafe code.
As with any performance question, this is hard to actually say in general. In the Benchmarks game(1), there are several benchmarks where Rust has no unsafe code and is still faster than C.
1: personal note: ugh, all usual caveats apply, etc.
Bounds checks are obviously not free in theory, but I've heard multiple stories of people who did the measurements on their codebase, and concluded that the cost was so tiny as to be statistically unmeasurable, compared to other noise in run-time measurements. Here's one:
It's not just a matter of optimizations, there are some things you can do in C++ that incur extra pointer indirections in Rust because you don't have move constructors.
Any time you would have backpointers that need to get updated when the pointee object moves. One example would be a pair of noncopyable promise send/receive objects, each with a pointer to the other. Or just one end could be copyable. Or take green threads single-core mutexes and their acquirers. The acquirers should be able to be moved around without getting "lost".
Hematite doesn't implement anything near to the full game, so it wouldn't be a fair comparison. It's mostly just a world visualizer, sort of like Overseer is for Dwarf Fortress ( http://www.bay12forums.com/smf/index.php?topic=63484 ).
And although it would probably make a good jumping-off point for anyone who wanted to develop a Minecraft clone, the original goal of Hematite was only to serve as a proof-of-concept of rendering 3D graphics in Rust.
Are you sure they are caused by GC? I'm asking because GC is an easy "culprit" and when a Java app pauses / jitters, many people immediately blame it on GC without any hard evidence. While in fact pauses / unresponsiveness can be caused by many other reasons, not specific to JVM (e.g. improper thread locking, improper use of dynamic data structures, doing hard work on the GUI thread, etc.). Actually, a well coded game should not use dynamic memory allocation in the game loop, neither in Java nor in C++.