Back to News
Advertisement
Advertisement

⚡ Community Insights

Discussion Sentiment

67% Positive

Analyzed from 4613 words in the discussion.

Trending Topics

#rust#code#more#performance#performant#compiler#compile#language#level#checks

Discussion (128 Comments)Read Original on HackerNews

jandrewrogers2 days ago
I would summarize it thusly: Rust is roughly as performant as C. This matches my experience and Rust is more ergonomic than C in many regards. The caveat is that modern C++ is notably more performant than C and by implication Rust. This also matches my experience for both C and Rust.

I think most of this is attributable to the ergonomics of compile-time expressiveness. C++ can effortlessly do things that require mountains of ugly boilerplate and macros in C or Rust. In principle they can express the same things but no one wants to write or deal with that ugly boilerplate so the equivalency is never realized in real code bases.

Zig is interesting because it slots in as a C-like language with a competent and expressive compile-time story. I don’t use Zig but I recognize its game.

afdbcreid2 days ago
Is C++ more performant than C? I find this hard to believe. C++ does not have any construct that cannot be replicated, or is not common, in C. The only candidate is using virtualization and void* pointers instead of monomorphized generics which some C code does for the lack of better options, but that's not a problem in Rust as well.

If anything, Rust has the potential to be more performant than C due to its aliasing rules (C has `restrict` but it's rarely used, standard C++ does not have even that). The current perf stats show it does make Rust code faster but just a little bit, although we don't utilize the full optimization potential currently (LLVM does not do many possible optimizations here, and `noalias` is weaker than Rust's aliasing rules). It can also affect autovectorization, and if it does the effect could be dramatic.

jandrewrogers2 days ago
Modern C++ metaprogramming materially impacts performance in practice. I’ve done performance engineering for decades in both C and modern C++ and I would assert that the difference isn’t arguable.

The poor applicability of auto-vectorization is another area where C++ is strong. You can transparently codegen e.g. AVX512 from intrinsics directly in C++ in contexts that would be opaque to auto-vectorization and difficult to generalize in C. This allows you to get some degree of “auto-vectorization” where the compiler can’t see it because it works at the wrong level of abstraction.

With sufficiently heroic efforts you can write C that matches the performance of C++. I’m not arguing that. Virtually no one writes C to that standard, including myself when I was writing high-performance C because the effort was too high, so it is a bit of a strawman.

It is the difference between theory and practice. All code bases have a finite budget. C++ can do a lot more optimization in the same budget as C.

globalnode2 days ago
So youre saying the metaprogramming facilities of C++ allow the compiler to better optimise high level human readable code more effectively than C. Thats a fair point and one I'd never even thought of before, I always thought C was faster because of things like v-tables and all that stuff.
amelius2 days ago
> I find this hard to believe. C++ does not have any construct that cannot be replicated, or is not common, in C.

But this is not a valid argument, as all languages are Turing complete, and most modern languages can do low level stuff at optimum speeds. As an extreme example, in Java, you could just allocate a large chunk of memory and run an allocator inside of it and sidestep the GC entirely.

With a programming language the question is thus not what can you do with it and how fast can it run with infinite effort, but what are the ergonomics, and what performance will you get in practice.

loeg2 days ago
C++ you get templated generic algorithms that in practice no one really does with C because macros suck too much. So in C typically you'd have a runtime generic routine that doesn't inline. A classic example here is qsort() vs std::sort().
flohofwoe2 days ago
> So in C typically you'd have a runtime generic routine that doesn't inline.

With LTO you get many of the same advantages as C++ template code, there's nothing magic about C++ template optimizations, it's all about whether the compiler can see all function bodies in a call hierarchy.

afdbcreid2 days ago
I explicitly acknowledged that:

> The only candidate is using virtualization and void* pointers instead of monomorphized generics which some C code does for the lack of better options, but that's not a problem in Rust as well.

But in fact, if speed is a concern to you, even in C you will use "templated" sorting (via macros or code generation).

nicoburns2 days ago
Rust also has these advantages of course
bluGill1 day ago
It is not uncommon to realize your C program is valid C++ and get a performance improvement just by building with a C++ compiler no other change. The difference is small but C++ has a stronger type system which allows the compiler a few more optimizations. Of course it is possible that resulting program no longer does what you want but actually needing weaker types is rare.

Restrict could make things go different but I've never heard someone say otherwise.

Note that we are talking about differences that are tiny here. They can be measured if you are careful but they are almost guaranteed to not be something anyone would notice if they were not measuring

dwaite2 days ago
> Is C++ more performant than C? I find this hard to believe.

At the compiler level, no. But as you write projects, you will for instance run into things you can do with templates which are infeasible to attempt with macros.

One example might be qsort() - a C compiler _could_ catch cases where it could create an intrinsic qsort based on the data type and function pointer being passed. However, in C++ you have the facilities to create a type safe, genericized sort that will be inlined based on the data structure.

smallstepforman2 days ago
c++ uses rich type system to avoid aliasing when it can, as well as template meta programming.

Eg: delete_scene(void *arg) vs delete_scene<T>(T *arg)

fithisux2 days ago
You can write C style C++ and enjoy the same benefits.

In Twitter a user explained me that it is common in embedded space.

You do not need the OOP, RTTI, exceptions.

Like C with most use cases of preprocessor replaced by generic programming.

afdbcreid2 days ago
So? How is that an argument that C++ is more performant than C? It's only an argument that it's not less performant.
root-parent1 day ago
>> The caveat is that modern C++ is notably more performant than C and by implication Rust.

Please provide proof for this outrageous statement.

mynegation1 day ago
I was also dumbfounded by this claim. The only thing I could think of were C++ monomorphic templates that will avoid the penalty of some indirection and DIY dynamic typing.
pjmlp1 day ago
And compile time programming, meaning that you can prepare some algorithms and data structures at compile time, at the expense of executable size.

Compile time reflection will make this even easier.

musicale1 day ago
Is it outrageous because "performant" is kind of a vague term. Does it mean... Fast? GPU-friendly? Scalable? Energy-efficient? Reliable? User-friendly? Maintainable? For what kind of applications?

Modern Fortran has a lot to offer for scientific and numeric computation - easier to learn than C++, and easier to optimize in many cases. Scales from small systems to supercomputers, and there is even CUDA Fortran.

Asraeliteabout 23 hours ago
> GPU-friendly? Scalable? Energy-efficient? Reliable? User-friendly? Maintainable?

Nobody uses "performant" to refer to any of those. It usually means either high throughput, or some aggregate of high throughput + low latency + low memory usage.

api1 day ago
I think they may be talking about math algorithm heavy code, where C++'s looser almost-just-a-substitution generics system (really "templates" not even quite generics) can be used to create abstractions that compile everything down to inlined maximally performant code.

This type of code tends to be hard to maintain though.

AFAIK you can get there in Rust but it's a little more cumbersome. You have to implement a lot of operators, and for that type of code you might actually benefit from #[inline(always)] which is discouraged in normal Rust.

pjmlp1 day ago
> This type of code tends to be hard to maintain though

Depends on which C++ version one needs to support, in C++20 and later, it is relatively maintainable with concepts, constexpre, and reflection.

cogman101 day ago
> modern C++ is notably more performant than C and by implication Rust

I don't think this holds. Rust has the same facilities which C++ has. Rust's metaprogramming capabilities are now on par with C++ (they weren't always). Rust has a similar generics implementation which allows it to do what C++ does in terms of method dispatch and generation. And now Rust has pretty much the same compile time constant generation capabilities that C++ has.

I don't think there's a part of C++ which isn't in Rust at this point. The only thing potentially missing is the experience and investment using those features.

spacechild11 day ago
> Rust's metaprogramming capabilities are now on par with C++ (they weren't always).

Is that really true, though? I haven't really written any Rust code, so I have no idea, but I don't think Rust has static reflection. Also, aren't const generics much more limited? I've also heard there is no template specialization and no "if constexpr". Or what about dynamic allocations in constexpr functions?

cogman101 day ago
> I don't think Rust has static reflection.

Before C++ in fact through procedural macros. You can do everything you can do with C++ static reflection.

Now, it could be better. Proc macros require you to pull in secondary packages for parsing the token stream. But all the sorts of operations you can do via static reflection you can do via proc macros. That's how the most popular rust serialization package like serde works. It's also how some more popular database libs work like sqlx.

> Also, aren't const generics much more limited? I've also heard there is no template specialization and no "if constexpr".

Both have been added and expanded. AFAIK they are now roughly on par with what C++ const expressions can do. What they can't do, proc macros can do.

> Or what about dynamic allocations in constexpr functions?

IDK if that's possible in rust. Const expr capabilities of rust have been rapidly expanding though in the last year.

pjmlp1 day ago
Reflection, Rust community did a very good job driving away the person that cared to do that work, to the point he went back to C and C++ ISO comittees.

Several features on C23 were done thanks to his work.

Also compile time execution is much more rich in C++ than Rust, regardling language features and standard library that can be used at compile time.

Naturally none of the languages is standing still, and they will both improve on that regard.

cogman10about 23 hours ago
I agree. Rust could definitely be more ergonomic and IIRC the main reason it wasn't made that way years ago was because the users of proc macros vetoed the new 2.0 API. IIRC over stilly things like it'd make some of their other crates pointless.
zamalek1 day ago
Last I checked (which was a while ago to be fair), LLVM machine code quality still lagged behind GCC - so things should be slightly more interesting with the GCC back end.

There were also some bugs (hence disabled optimization passes) and missed opportunities from the lack of aliasing Rust precipitates - again, not sure where those sit - and GCC will have to play catch up here (unless there are other languages that exercise this part of the backend).

stephc_int131 day ago
C++ being more performant than C is not something that I've seen in any benchmarks or personal experience.

In practice, some of the cases about specialization that was made possible with C++ constructs is also achieved by modern C compilers.

flohofwoe2 days ago
> The caveat is that modern C++ is notably more performant than C and by implication Rust.

This really needs more realworld evidence to back up the claim. In the end the important optimizations happen down in the Clang optimizer passes on the LLVM IR, and those optimizations are the same across C, C++, Rust (or Zig for that matter) - assuming of course that the optimizer can see all function bodies, which in C can be achieved via LTO or alternatively via 'unity builds'.

If the output of one of those languages differs so much (on an LLVM-based compiler) that there are noticeable performance differences I would start investigating whether there's a compile/link setting missing somewhere instead.

logicchains2 days ago
OP said "C++ can effortlessly do things that require mountains of ugly boilerplate and macros in C or Rust". In theory Rust can be as performant but some things are much less ergonomic to do in Rust macros than in C++ metaprogramming, so often end up not being done.
flohofwoe2 days ago
Often that's also because the programmer doesn't know how the optimizer will help them to remove inactive code also in C code. As a simple example, when I have a 'general' bulk-getter function in C which returns a large struct with tons of values but the caller is only interested in one value, the compiler will 'collapse' the entire function call to a single memory access (if it can see the function body, but this is where LTO comes in), e.g.:

https://www.godbolt.org/z/n3Y54Yhqr

This is basically the gist of C++ 'zero cost abstraction', but C-style (the bulk of what enables C++ zero-cost-abstraction doesn't happen up in the language, but down in the optimization passes).

elcritch2 days ago
Nim also has top notch meta programming, probably more so than Zig. You can easily do loop unrolling, specialization, etc. For example Constantine, which is a constant time crypto library that outperforms C, etc.

To me programming Rust feels so limiting due to lack of good compile time meta programming with types. That’s the key.

afdbcreid2 days ago
How can you create constant-time code with Nim when none of its backends support it (e.g. LLVM may turn an apparently-constant-time code into non-constant-time assembly)?
cb3212 days ago
You can see all the details at: https://github.com/mratsim/constantine , but to answer your "how" question briefly here, something Nim shares with most (all?) "systems programming languages" is "easy" integration with assembly languages -- whatever the backend for "most" compiled code is (whatever that "most" even is - weighted by any number of measures of static source size or dynamic instruction counts). Of course, hand-rolled assembly can cost you a lot in portability/effort to port to new platforms/etc.

The entire concept of the "performance of a PLang" in terms of the run-time of programs written "mostly in it" is rather seriously under-specified, TBH. This is (or should be) uncontentious in spite of the slew of articles with titles like the one for this thread.

zamalek1 day ago
I have tried Nim meta programming (to make a tree vaguely like the one used by Zed), and the tooling support of pretty dreadful. I ran into multiple compiler crashes or simply unhelpful and confusing error messages, alongside LSP hangs.
nsajko2 days ago
Julia is another contender. Julia code can be as performant as C++ code, but Julia code may be even more elegant than C++. Even without accounting for Julia's metaprogramming features, the compile-time expressiveness is top-notch.

It shares some of the same drawbacks as C++, though. The language is extremely powerful, so while it is easy to write performant code, it is also easy for non experts to write very suboptimal code.

afdbcreid2 days ago
Julia is not a systems language. Also its design (GC, dynamic typing) does not allow it to reach exactly the same level as C++.
postflopclarity1 day ago
you are right one has to be careful to avoid the GC and dynamic dispatch, but if you do it can for sure reach the same level as C++. with tightly optimized Julia code there is little to no overhead over any other low-level language.
rirze2 days ago
Julia only cares about numerical performance, and in that regime, it’s pretty fast.

So not generally fast, no.

wolvesechoes1 day ago
> Julia code can be as performant as C++ code, but Julia code may be even more elegant than C++

But not at the same time

postflopclarityabout 22 hours ago
depends on the workload. many are elegant and fast. some require a bit of clunkiness to wring out the last drops of performance
vvanders2 days ago
I'm surprised to see someone putting forth the argument that templates are easier to use than macros. I've found the opposite and in many cases the monomorphization of templates to explode code size which has a fairly material impact on performance in my domains. Debugging macros with cargo expand is infinitely easier than debugging template errors.

While you can write high performance C++ my experience is that many people will reach for shared_ptr and their like while Rust will force them into proper structure/ownership as Arc and their like have a lot higher friction.

tialaramex2 days ago
You say you are "summarizing" something but instead you seem to have just injected your opinion that C++ is "notably more performant than C and by implication Rust".

It's true that you can express many things in C++ -- the problem is that the language deliberately doesn't distinguish whether the things you've expressed are nonsense, so you might well have written total nonsense and you only find out when, much later, diagnosing a real world event you discover oh, this is nonsense, why did this even compile? Well sorry, it was "more performant" to allow nonsense.

gobdovan2 days ago
Rust is in an awkward position of being already complicated enough that adding proofs for skipping bounds checks probably will not happen for a long time, even though this kind of low-level operation is where a lot of optimisation is lost.

Compounding on this, Rust is also unstable underneath, since there is no public, stable contract for carrying high-level semantics from HIR into MIR. Because these high-level invariants are lost during compilation, the compiler cannot easily use them to prove and eliminate low-level safety checks. But even if the frontend was perfect, Rust relies on LLVM's language-neutral SCEV, which operates purely on low-level math and cannot reason about high-level language semantics.

Ultimately, a lot of things would need to change for Rust to pay no performance for safety features.

aw16211072 days ago
> Compounding on this, Rust is also unstable underneath, since there is no public, stable contract for carrying high-level semantics from HIR into MIR. Because these high-level invariants are lost during compilation

Not sure if I'm just out of the loop, but I'm having a hard time following this line of reasoning. Why is a public and/or stable contract needed to carry high-level semantics from HIR to MIR? Neither seems necessary to me; from what I understand HIR and MIR are rustc-internal so public contracts shouldn't matter, and the lack of stability means the Rust devs aren't precluded by backwards compatibility from modifying the IRs to add the ability to carry such invariants.

gobdovan2 days ago
Whoops! Although there is no public contract between HIR and MIR, the public part was not relevant here. What I wanted to highlight is that if they'd want to add proper proof machinery to eliminate low-level safety checks, they'd have to do it at: surface language, which is already complex enough; then HIR->MIR boundary with clean provenance (which I think current MIR would collapse too aggressively) and which may require a much clearer contract; then, even if they do the full front- and mid- ends properly, if you leave it up to LLVM, it ends up in SCEV, which is language neutral and would not be a good fit to support the proof obligations that would be specific to Rust.

I dug up a proposal from 2021 around bounds check hoisting in MIR, and from the discussion, details are pretty thorny [0]. It's narrower than general proofs but the frictions are very similar. The easiest example that shows HIR -> MIR difficulties is the part around `for i in 0..32 { a[i] = 1; }`. At the source level the range fact is super obvious, but after the for-loop/iterator lowering the MIR optimiser has to recover that `i` comes from exactly that range before it can turn 32 checks into the one hot-path check. Then it also would have to check for panic strategy to maintain the correct behaviour after optimisation.

[0] https://github.com/rust-lang/rust/issues/92327

nicoburns2 days ago
Of course you can write the above as:

a[0..32].iter_mut().for_each(|el| *el = 1)

and have per-iteration bounds checks elided in Rust today.

aw16211072 days ago
OK, I think that makes more sense. Thanks for taking the time to explain!
afdbcreid2 days ago
The overhead of bounds checking varies a lot. In the common case it's negligible (few percents), but in some cases, depending on what you build, it can go up to even 20%. And if it prevents autovectorization it can cost even more.

There are techniques to minimize the perf loss, though (safely), and of course you can use unsafe code. If you do it smartly, in the vast majority of cases bound checks do not matter (in fact, even in C++ there is a push for a hardened standard library that does bound checks, and e.g. Google uses that).

Rust will never include full proofs, but it might include ranged integers which can minimize bound checks even more.

peterfirefly1 day ago
You can sometimes just add asserts for the index variable(s) and have the LLVM optimizer go "hmm, I should try to prove that those are true" and then get the range checks optimized away.
IshKebab1 day ago
The benchmarks in this talk show that the bounds checks are mostly insignificant, and actually it's the integer overflow checks that are far more costly.

Actually nm, I forgot those are disabled in release mode. Good decision I guess.

IcyWindows1 day ago
Do they even count towards safety if they aren't in release mode?
IshKebab1 day ago
You can enable them in release mode optionally. But I would say not. Really we need ISAs to provide a no-overhead way to check integer overflow.
encodedrose2 days ago
If I followed, Rust's memory safety guarantee means sacrificing roughly ~3% performance with some worst case paths being ~15% (compared to C++ performance)?
marcosdumay2 days ago
That's on the typical performance for bounds checking in C too.

But no, "memory safety" includes most of the things discussed on the slides, and those number are for bounds checking only.

encodedrose2 days ago
Ah, I was using GH's webui instead of downloading to view the PDF and it stopped loading at slide#47...rereading it now paints a much better picture. Thanks!
Animats2 days ago
There's a discussion of "delayed bounds checking", but not "hoisted bounds checking", where bounds checking is done early. Consider

    let mut tab: [usize;100] = [0;100];
    ...
    for i in 0..101 {
        tab[i] = i;
    }
This must panic at i=100. Panic becomes inevitable at entry to the loop. Is the compiler entitled to generate a check that will panic at loop entry? The slides suggest that Rust does not hoist such checks, and, so, with nested loops, it has trouble getting checks out of the loop, which prevents vectorization.
guerby2 days ago
On https://godbolt.org/ select Ada and compiler option "-O2"

    function Square(num : Integer) return Integer is
        tab : array (0..100) of integer;
    begin
        for i in 0..101 loop 
            tab(i):=i; 
        end loop;
        return tab(100);
    end Square;
The assembly code generated is :

    sub     rsp, 8    #,
    mov     esi, 11   #,
    mov     edi, OFFSET FLAT:.LC0     #,
    call    "__gnat_rcheck_CE_Index_Check"  #
Loop is not run and exeption handler is called directly.

Link : https://godbolt.org/z/qT4TsKPxz

Animats1 day ago
Right, that's the extreme case, where the problem is detected at compile time. Unfortunately, it's not a user-visible error message at compile time.

Need to try an example where the size isn't known until run time.

afdbcreid2 days ago
Currently LLVM cannot do that because the panic message includes the erroneous index. You can do it manually though if you add `_ = tab[100]`.

Even if the panic message would not include the index, LLVM was unable to do that if the previous iterations had side effects (for example if `tab` is not a local variable).

simonask2 days ago
Panics in Rust do not currently time-travel like that (including panics from failed bounds checks), and that's a good thing. The reason is that panicking does not imply terminating the process - they can be caught and handled, just like exceptions in C++. In fact, they use the same stack unwinding mechanism by default.

What the compiler is allowed to do is to shorten the loop by one and unconditionally panic after the loop, but this falls under the purview of the LLVM optimizer.

afdbcreid2 days ago
It's true that panics (unlike UB) cannot automatically time-travel, but your justification is weak. Recovering from panics can only prevent this optimization if the loop have side effects, and LLVM knows when panic=abort is set.
Animats1 day ago
The post-panic situation is a problem in Rust. After a panic, you're in a somewhat abnormal state. Rust panics are not supposed to be a catchable exception system. If something other than program termination is in the near future, that's a problem.

That does create a problem for early panics, panicking when panic becomes inevitable but has not happened yet. This deserves more thought.

simonask1 day ago
I mean, sure, dead code elimination applies to all optimized code. The important thing to understand is that panicking in Rust does not get magic treatment by the Rust compiler. It’s just a function that is declared in the type system to never return.
jojomodding2 days ago
Once it shortens the loop, the compiler can also observe that `tab` is a local variable and therefore move the writes up "to the initializer." It can then see that the variable is unused and delete it, and also delete the loop.
suis_siva2 days ago
I worked professionally with C, C++, Zig and Rust (in that order). My experience is that writing performant code is by far the easiest in C++, and by far the most difficult in C. Most of this, in practical experience, is due to ergonomics, in my opinion.

Templates in C++ benefit from being part of the core language, -- stick a `template` above your `class`, and you're in metaprogramming land. Stick a template specialization, and you've done a niche optimization. You didn't need a separate crate or a whole macro DSL. Variadic templates are also really really nice for monomorphizing N-ary generic functions. The duck typing of templates makes

This is precisely where I struggle with Rust the most -- monomorphization is limited within generics, so you end up going to the `proc_macro` hell, which involves a separate crate, a separate Cargo.toml, etc.

Zig seems like it would fit the bill -- and doing micro-optimizations within zig is surprisingly easy. The language's comptime facilities allow for really good niche optimizations -- however, the language also has some strange decisions. The allocator interface is notoriously a vtable, so a lot of the DOD optimizations that andrewrk has spoken numerously of (and to be clear -- I did learn a lot about DOD from his talks back when I was a wee engineer), raise one of my eyebrows.

C seems like it should be fast, but implementing any data structure, any generic algorithm in C is impossible. Either you're copy-pasting, or you're making macro DSLs. None of which is great.

---

To further talk about the C++ situation -- the monomorphic allocator interface was always awesome. Compared to Zig's vtables and Rust's nothing (up until a couple days ago), having a way to pass custom allocators with types was awesome. The new std::pmr::* interfaces and containers are also really exciting -- monomorphization, as beautiful as it is, does cost a lot -- refactoring it is not easy, compilation times are a mess. Sometimes the right tool is a vtable interface, and, C++ gives you those facilities.

And this is C++'s no1 problem when it comes to performance too -- it's a leviathan -- it'll give you the tools to write REALLY fast code, but it will also give you inheritance -- forget about your caches then.

When I was working at Tesla, there were some pretty gnarly vtable jumps in firmware (of all places), and I suspect part of that could've been alleviated if people knew more about CRTP.

So, here's where I land -- C++ really will give you the tools it can to let you write the fastest code possible. But it will also give you the tools to make your code really slow. Committee language means everyone in the committee needs to be happy.

Rust, on the other hand, is really designed to promote safe-but-very-fast practices -- had the firmware that I discussed used Rust, my guess is that we would've gravitated towards generics and monomorphization, rather than the heavy dynamic inheritance. C++, when it comes to performance, as it does to all other things, is a barreled shotgun. Rust's design almost always promotes the best available pattern and that's why I rarely reach out for C++.

jarym2 days ago
I've been doing more and more Rust. Even with sscache the compile times are not great so for any moderately sized codebase that requires frequent rebuilds I don't know how everyone else is doing it
wongarsu2 days ago
I'd assume mostly by avoiding the need for frequent rebuilds. Incremental builds are pretty fast (at least fast enough for my needs on a moderate codebase), full rebuilds can be brutal

There are also some optimization tricks related to how you split your code among crates, since a unit of compilation is mostly one crate. Putting your FFI code in a separate crate (-sys crates are the norm) and splitting some of your code in libraries that can be compiled in parallel are the common examples

unsolved732 days ago
the linking of the project can take more time than actual compilation.

Use the lld linker instead of the default one, see https://kerkour.com/rust-production-checklist#use-the-lld-li...

culebron21about 21 hours ago
I split into subcrates and also reduce the number of proc macros (e.g. got rid of Serde).
peterfirefly1 day ago
On Windows? Use a dev drive.
cauterize1 day ago
kache helps
Panzerschrek2 days ago
For a couple of years I have written an advanced software rasterizer (like in old PC games) using Rust. With a little bit of unsafe code it was doable and result performance was great. I only used unsafe in places mentioned in the article above, like in tight loops where the compiler's optimizer struggles to remove bounds checks and in a couple of places where CPU intrinsics were used.
DeathArrow2 days ago
I was looking at Zig. It's performant, it's easier to reason about Zig code than Rust code but its api is unstable, there are a lot of breaking changes. Coding agents have a difficult time write proper Zig because of the breaking changes and of the small amount of new Zig code in the wild.
smasher1642 days ago
You end up needing something like refinement types to control the way you statically enforce bounds. That being said, there's stuff like https://flux-rs.github.io/flux/ which uses macros to layer a refinement type system on top of rust's. You can use it to statically eliminate bounds checks.
_alphageek2 days ago
I would have liked to have the checks-off delta plotted across rustc versions - the deck notes this stuff moves non-monotonically, so a trend line would say more than a single-version snapshot.
ozgrakkurt2 days ago
There is no performance of language, it is very dependent on the compiler in any language. I don’t think even clang/gcc can “fully” optimize c
Advertisement
yogthos1 day ago
I find the real issue with Rust is the compiler performance. I decided to use it for a project, and frankly I have huge regrets now that it's grown big enough. It literally takes over a minute to compile on my M1 laptop.

I just don't understand how people find this sort of thing normal. If you implement a feature, and then you want to see it in action, the feedback loop for that is insanely slow. It's incredibly jarring coming from Clojure where you have a live development experience.

I've used Go before, and while you still have to wait for compiling, at least the compiler is actually fast.

I get the problem Rust aims to solve, but the ergonomics are just not there in my opinion.

culebron21about 21 hours ago
I had similar problems. Compilation got 10x faster once I split one crate into workspace with crates inside. The checks are only done on the compiled one. Then, I got rid of serde, because its derive macros are heavy and add some seconds of hickup (other serialization frameworks based on proc macros are slow too: rkyv, musli).
dralley1 day ago
There are a number of compiler performance enhancements (and correctness improvements) that are being worked on that are kind of at a chokepoint behind some other piece of work. Unfortunately it's not that easy to discern what state they're in or how quickly they're making progress.

At some point though a lot of work will be able to start advancing at once, so long as people exist to do the work.

e.g. https://rust-lang.github.io/rust-project-goals/2026/parallel...

pjmlp1 day ago
See languages like D, Delphi, Ada, C++ (with modules/libraries, REPLs like CINT), Haskell (GHCi), OCaml (REPL) for similar complexity levels, and faster compile times.

It is a matter of tooling, eventually they will get there I guess.

imtringuedabout 24 hours ago
There's a tooling problem in the compiler with regards to parallelism, but the real problem is that the Rust ecosystem makes use of intense amounts of code generation features, like serdes, which generates a huge amount of invisible code so that you can serialize and deserialize JSON at speeds that may or may not be practically relevant, but whose impact on compile times is guaranteed.
pjmlpabout 23 hours ago
The relevant part is without proper way to cache it, so rebuilding everything requires lots of external stuff that every single developer has to manually configure.

However as language nerd, I still think there is a possible way out of it.

scns1 day ago
Have you switched to a faster linker [0] yet? Do you use sccache?

[0] https://github.com/rui314/mold

yogthosabout 22 hours ago
oh didn't know about this one, will have to try
DauntingPear71 day ago
Yeah rust has issues. Have you tried splitting your project into multiple crates if possible?
yogthos1 day ago
I haven't tried that yet, I guess that would be an option once I get a few stable pieces that I'm not likely to be touching much.
mswphdabout 20 hours ago
for context: in rust the "translation unit" is a crate (in C++, each source file is its own translation unit). So you only get parallelism across crates when compiling in rust. When you have a single-crate project, this means that

1. you get parallelism across all your dependencies, but

2. your (final) crate is serial.

Splitting your crate can therefore get you parallelism back in step 2, which can be a (compilation) perf win. You can also get a caching benefit when there aren't changes, but even absent that you can get wins.

It can come at the cost of runtime performance though, as rust won't (by default) do things like inline across translation units iirc. You can get back some of this perf back via various tricks (for example link-time optimization).

Looks like the rust performance book has some writing on these tradeoffs. I haven't read these articles, but the table of contents on the left seems reasonable enough.

https://nnethercote.github.io/perf-book/build-configuration....

Again I haven't read that (so I'm assuming it says the following), but you can get "best of both worlds" by configuring debug builds to not do LTO (= faster compilation speed, slower runtime), and release builds to do LTO (= faster runtime, slower compilation speed). There are also variants of LTO that make various tradeoffs you could look into.

That article also links to the `cargo-wizard` subcommand of cargo

https://github.com/Kobzol/cargo-wizard

it won't auto-split crates for you (of course), but it does seem to give you some default configurations of `cargo`, one of which configures for faster compilation speed. could be an easy way to mess around with things.