There's one significant thing that's in C that's not in Rust: alloca. If you need that, well, we've talked about it, but haven't accepted a design, and some people never want to gain it directly.
EDIT: another comment pointed out goto; I have no idea what the performance implications are but I’ll have to add that to my list of “stuff C has that Rust doesn’t.”
Sometimes, you need nightly-only features. This means that, in some sense, today's Rust is slower, but tomorrow's Rust may not be.
Sometimes, "equivalence” is the issue. You can turn off bounds checking for array access, for example, but most Rust code doesn't, for hopefully good reason. Sometimes those checks are elided, and so it's exactly the same. Sometimes they're not. Sometimes they're not and that inhibits other optimizations. Is unsafe Rust "equivalent" to the C? Or does that not count? (Unsafe Rust is not always faster than Safe Rust...)
At the theory level, I can't think of anything off the top of my head that would inherently make Rust slower than C, generally speaking. There's also some degree of argument that in theory, Rust should be faster than C, thanks to how much more we know about aliasing, etc. That's a whole other can of worms...
> Sometimes those checks are elided, and so it's exactly the same. Sometimes they're not.
This is something I've personally thought about a lot and I have no idea how I'd actually want it implemented either on the backend or in the syntax, but I think it'd be useful to have some way to say "I want these bound checks to be elided, if they can't be then make that an error". Similar to how you'd decorate a pure function in other languages to say that it can't cause side effects or depend on side effects for what it's doing.
I think that's probably a high level ask that's probably significantly more difficult that it seems initially too.
Quite possibly that's what would work. I haven't tried any of them in any kind of serious capacity before.
Edit: Looks like there's been an RFC in the past to add it to rust, but it got punted because the const generics stuff went in first and they wanted to see how that would shake out before trying to fully tackle this. https://github.com/ticki/rfcs/blob/pi-types-ext-2/text/0000-...
Note that ATS has a limited version of dependent types as compared to Idris/Agda/Coq/etc. As I understand it, you can't run arbitrary (recursive, etc) functions at the type level. But even this 'lite' version lets you express preconditions like "n is a multiple of 4" or "the source array and destination array must not overlap in memory".
> EDIT: another comment pointed out goto; I have no idea what the performance implications are but I’ll have to add that to my list of “stuff C has that Rust doesn’t.”
On that front, one interesting thought would be to reimplement the CPython bytecode interpreter in Rust and see if you can match the performance of C using computed goto.
In principle, there's no fundamental reason Rust couldn't optimize "loop around match" exactly the same way, without needing the computed goto. (For that matter, so could C.) Doing that would help cases like this.
Well, the answer should be "no" for unsafe Rust, since you can mechanically translate any C to unsafe Rust and vice versa.
For safe Rust, sure, there are limitations: you can't turn off bounds checks, for instance. But all Rust is partially unsafe Rust, because the standard library uses unsafe code ⃰. So it's a fuzzy distinction.
⃰ ⃰You wouldn't want it any other way. Implementing low-level functionality as unsafe code allows us to write code, not compiler-code-that-generates-code.
In a sense you can mechanically translate between any Turing complete languages, but there's not a "naive" translation, so to speak, because C can have irreducible CFGs and Rust cannot. It's not clear to me that reflowing a CFG can always be done without a performance impact.
The compiler was obviously not able to unroll the relooped code into the original CFG.
A specific example: in principle, the compiler could see the exact target of every continue in the Rust code, so the continue on line 34 could go directly to line 27 (or, better, as in C++ the basic blocks could just be laid out adjacently), but the compiler does not actually do this and there are a bunch of unnecessary tests on the path between those two lines.
It's unfortunate that the enum-based solution doesn't work, but that particular example can be made "optimal" (at least, as good control flow as LLVM can get this code: the same as clang, not GCC), by using a rather unpleasant series of loops and labelled breaks: https://godbolt.org/z/sQ1nH8 (rustfmt'd version: https://play.rust-lang.org/?gist=b1091e0c583b88f27bf1eaeae3a...).
This doesn't work in general, and is ... impossible to maintain, but it's not an unreasonable approach for generated code.
Note that Rust has "forward goto" in the form of "labelled break" (which can even carry a value), so I suspect some cases might not even need converting to a loop-match "state machine".
Not only true goto but also switch and computed goto. AFAIK C/C++ are essentially the only games in town for these. No modern language has even corrected the purely syntactic limitation that prevents nested switches (by eg. allowing you to attach label names to switch blocks and cases).
Not qualified to answer, but I did notice recently that Rust appears to generate more compiler code than equivalent C, but I don't think this realistically has a negative performance impact. You can check this out on godbolt (https://godbolt.org/)
It's possible to set compiler flags to optimize for size. That said, I've also seen large binaries, and monomorphization can contribute to that. Some projects are taking that more seriously, for example miniserde is designed to explicitly minimize monomorphization and this improve both code size and compile time.
Bounds checking will inhibit vectorization, panicking will bloat code, maybe I'll make a comparison against a rust coreutils bin vs something like sbase.