
Debunking that C++ is faster and safer than Rust - payasr
https://www.viva64.com/en/b/0733/
======
saagarjha
> As you can see, the documented behavior and the absence of undefined
> behavior due to signed overflows do make life easier.

Not having undefined behavior does make life easier, but having it be defined
and then giving the example which benefits from the way that Rust chooses to
define it is not really fair.

> With less effort, Rust generates less assembly code. And you don't need to
> give any clues to the compiler by using noexcept, rvalue references and
> std::move. When you compare languages, you should use adequate benchmarks.

Actually, the issue is that C++ just can't match Rust's semantics here. By
default it will allow for exceptions, by default it will copy; if you watch
the talk rvalue references cause the double indirection and fixing this would
require some changes in the language to accommodate the use case.

~~~
mehrdadn
> the example which benefits from the way that Rust chooses to define it

I'm actually struggling to see what the practical benefit is in having it wrap
around. The program is still producing garbage at that point, which you're not
handling, so why not let the compiler just forget about that case just like
you already did?

~~~
steveklabnik
The practical benefit is not in the wrapping specifically. The difference is
in it being UB vs not UB; that is, overflow is a "program error" not UB, and
so the language defines how implementations must handle this error.

You aren't supposed to rely on this semantic, as it's an error. If the checks
get cheap enough, rustc will also check in release.

~~~
mehrdadn
I mean, the post seemed to be arguing that the wrap-around is somehow
beneficial, so I'm not sure you're saying the same thing. But if all you want
is check + crash when that happens then can't you just use a compiler flag or
a different data type or something else for that in C++?

~~~
steveklabnik
I’m not saying that the wrapping behavior is beneficial. I’m saying that
having it be not UB is beneficial.

~~~
mehrdadn
But if all you want is check + crash when that happens then can't you just use
a compiler flag or a different data type or something else for that in C++?

~~~
steveklabnik
Different type? Sure. Disadvantage is that it’s not pervasive. Compiler flag?
Probably, I am not an expert on compiler flags that change language semantics;
we don’t do that in Rust, but I know C and C++ compilers do.

~~~
mehrdadn
> Compiler flag? Probably, I am not an expert on compiler flags that change
> language semantics; we don’t do that in Rust, but I know C and C++ compilers
> do.

What do you mean? Is Rust's -C overflow-checks just a joke then?
[https://doc.rust-lang.org/reference/expressions/operator-
exp...](https://doc.rust-lang.org/reference/expressions/operator-
expr.html#overflow)

~~~
steveklabnik
Okay, so let's review the language's rules here:

* There are two modes for overflow checking: enabled, and default.

* If overflow checking is in "enabled" mode, overflow results in a panic.

* If overflow checking is in "default" mode, the results are two's compliment wrapping.

* Debug builds are "enabled" mode.

(There's a few other details, but this is good enough for our discussion. See
[https://github.com/rust-
lang/rfcs/blob/master/text/0560-inte...](https://github.com/rust-
lang/rfcs/blob/master/text/0560-integer-overflow.md) )

Rustc implements this as follows:

* When debug_assertions is on, it's in "enabled" mode.

* Otherwise, it’s in “default” mode.

* -C overflow-checks turns on "enabled" mode, regardless of other settings.

This is completely consistent with the rules of the language. If these checks
ever get cheap enough, rustc may even start to turn them on by default, which
is also acceptable under these rules. We'll see if that ever happens, though.

~~~
mehrdadn
So let's review where we are right now. C++ left the entire thing _undefined_
, which gave compilers the complete freedom to add flags that define e.g.
wrap-around or trapping or other behavior. I suggested you can use such flags
if you want. You complained about that and said it's "changing" language
semantics... despite the fact that it's 100% consistent with the "rules of the
language". You even went on to claim "we don't do that in Rust". All right. So
I pointed you to a Rust _compiler flag_ that changes the default wrap-around
to a _panic_. Now you're saying that's _not_ changing Rust's language
semantics... and your defense is "it's consistent with the rules of the
language"...?!

~~~
steveklabnik
Undefined behavior is different from implementation defined behavior. Code
that exhibits UB is not a valid program. Flags that take UB and define it are
categorically different than flags that tweak various options, because flags
that define UB expand the set of valid programs, where flags that tweak
options do not.

~~~
mehrdadn
Okay, you're very confused. You're getting it almost backwards.

Undefined behavior is behavior that the language _does not define_ , _not_
behavior that the implementation is _prohibited_ from defining. That's why you
can't treat it like (for example) a random-number generator. Implementations
are well within their rights (i.e. 100% consistent with the language) to
define previously-undefined behavior to be _anything_. They're also just as
welcome to leave them undefined. Both are 100% consistent with the language
and neither is "changing" the language semantics. No program that exhibited UB
is going to misbehave somehow just because someone decided to define the
behavior under UB. The semantics already allowed anything to happen. Whatever
they define falls under "anything could happen".

Implementation-behavior is behavior that the the implementation _is
guaranteed_ to define. Like with UB, the implementation has freedom to choose
a behavior. Unlike with UB, it is not allowed to leave that behavior
undefined. So the program can be sure to have a well-defined output.

It is _completely_ wrong to simultaneously say changing "wrap" to "crash" is
"not changing semantics" and somehow "consistent" with the language rules, but
that changing "undefined" to "wrap" is "changing semantics" and "not
consistent" with the language rules. If the language wraps on overflow, then
changing that to a panic is _actively shrinking_ the set of valid programs;
_valid programs_ that used to behave one way now behave differently. (They
crash!) The latter is merely _expanding_ the set of valid programs; programs
that had _no rights to claim any behavior_ under UB now actually have a right
to claim something under the implementation, but that doesn't change the
behavior of previously-valid programs. They're still provided exactly the same
guarantees they already were.

(P.S., UB isn't even a property of a program, but of an execution. But I'll
leave that out since the simplified version is clearly confusing enough as-
is.)

~~~
steveklabnik
I do absolutely understand that UB does not prohibit implementations from
defining something. That’s part of the core of what we’re taking about! I’m
saying rustc chooses to _never_ do so. C and C++ compilers do choose to do so.
That distinction is what we’re talking about.

(As well as that overflow is UB in one language and not another, of course.)

~~~
mehrdadn
No comment on everything else I wrote beyond that 1 sentence? Your positions
are quite contradictory, which is what I've been trying to explain in the
entire comment.

~~~
steveklabnik
I just don't think we're going to get anywhere, but wanted to correct the
assertion that I do not understand this aspect of UB.

~~~
mehrdadn
I mean, your positions are contradictory, but sure.

------
dathinab
> The bug has been present in LLVM since 2006. It's an important issue as you
> want to be able to mark infinite loops or recursions in such a way as to
> prevent LLVM from optimizing it down to nothing. Fortunately, things are
> improving. LLVM 6 was released with the intrinsic llvm.sideeffect added, and
> in 2019, rustc got the -Z insert-sideeffect flag, which adds llvm.sideeffect
> to infinite loops and recursions. Now infinite recursion is recognized as
> such (link:godbolt). Hopefully, this flag will soon be added as default to
> stable rustc too.

Be aware that this isn't a LLVM bug but a direct consequence of the insanity
of C++ specification (wrt. forward progress induced undefined behaviour).

The C++ rules around forward progress allow C++ compilers to faster eliminate
code which doesn't produce any observable side effects (without the code
triggering undefined behaviour) but it also removes code which intentionally
or not hangs the process in a busy loop or is intended to cause a stack
overflow... (e.g. for testing protections).

The flag currently isn't added to rust as the penalty effect on compiler time
(needs to run more analysis) and runtime (doesn't eliminate all code it
should) is currently pretty high.

So this might take a while until _fully_ fixed (you always can pass in the
flag yourself if you want).

Through some fixes which make it harder to hit the bug until a proper solution
is found _might_ not be so far of (I hope).

~~~
Arnavion
It's indeed not a bug for C++, but to be clear it _is_ a bug for C and Rust
that use LLVM but don't have that same guarantee as C++. LLVM assumes that
guarantee holds for all frontends, and added the sideeffect opcode so that
frontends for languages that don't have that guarantee have a way out.

------
nv-vn
Important to note that PVS-Studio is a static analysis tool for C++. As such
it would definitely be in their interest to have more people use C++, but
since they're arguing in favor of Rust here it definitely speaks to the fact
that their analysis is unbiased.

~~~
steveklabnik
They have also previously posted some negative ones too, so I was surprised to
read this for that reason. I think it also contributed to my confusion around
the title vs text, that others have expressed in this thread.

------
ridiculous_fish
Here's a case I stumble on: Rust seems to generate unnecessary branches.
Compare copying an optional range:

C++: [https://godbolt.org/z/VCf638](https://godbolt.org/z/VCf638)

Rust: [https://rust.godbolt.org/z/jRFiw_](https://rust.godbolt.org/z/jRFiw_)

I think the key difference here is that C++ allows specializing optional on
trivial types - can anyone shed more light?

~~~
sigwinch28
It looks like the C++ version always does the copy, regardless of whether or
not the optional is empty, whereas Rust only bothers copying if there's
anything there.

Is this a remnant of #[inline] on Option's Clone impl methods?

With "larger" types (e.g. an optional several-GiB array) it seems like this
could save some time depending on where things are in memory.

------
aninteger
This article is a bit click-baity. It's more about busting myths by a
particular C++ programmer against rust.

Anyway for a truth (well maybe it is a myth?) that we can't bust yet... Rust
is simply not available on all platforms that C++ is. Two platforms that I
think are missing:

* 16-bit MS-DOS

* 32-bit PowerPC (Linux)

~~~
dathinab
Is anyone besides embedded doing _any_ 32-bit dev outside of maintaining
legacy code?

I mean 64-bit PowerPc is by now around 17 Years old and even 15 years ago some
of the most widespread users of PowerPc (Xbox) switched to using 64 bit
PowerPc architecture...

~~~
monocasa
Xbox 360 still ran in 32bit mode with the exception of the hypervisor. No use
of 64bit pointers on a system with a max of 1GB of RAM other than just wasting
cache space, and there's no real other benefit tacked on like you see in other
64-bit archs.

~~~
dathinab
Thanks I didn't know this. I just looked up since when PowerPc 64-bit is a
think.

~~~
monocasa
Ah. In that case the PowerPC 620 was a 64 bit design from the late nineties.

------
sneeuwpopsneeuw
Very interesting article. Most of the time I do not like myth busting articles
because they are to much focused on opinions and taking things out of context
but this one is very well written and fully based on facts on both sides.
Thanks for sharing.

~~~
kpp
Thank you!

------
acqq
Just looking from afar, I don't have time to analyze every other claim, but
this one:

> Both C++ and Rust have generated identical assembly listings; both have
> added push rbx for the sake of stack alignment. Q.E.D.

seems to be completely wrong: a decent compiler is able to align the stack
without "touching" it. For the variables inside of the function to be pushed
to the aligned stack position, only different offsets have to be calculated.
For the stack itself to get to be aligned, only the register has to be
updated, surely nothing has to be pushed.

So something else must have been happening there, and I don't have time to
analyze what, but I'm sure push is surely not necessary for alignment alone.

~~~
ridiculous_fish
You are correct, it has nothing to do with alignment. rbx is a callee-saved
register, so the callee saves it.

~~~
thayne
Do you mean thar rax is caller-saved?

~~~
ridiculous_fish
rax is indeed caller-saved, but I was referring to rbx. In this code:

    
    
        int bar(int a, int b) {
           int z = a * b;
           foo();
           return z;
        }
    

[https://godbolt.org/z/a8Y35r](https://godbolt.org/z/a8Y35r)

Where to put `z`? It has to be someplace where `foo` won't clobber it, like a
callee-saved register - in this case ebx.

But now `bar` is responsible for saving the value of ebx for its caller. That
explains the push and pop.

Alignment is unrelated; this is just about calling conventions.

------
kibwen
This is an endlessly perplexing headline, as its core assertion, "C++ is
faster and safer than Rust", is what the body of the article spends its whole
time attempting to refute. A more accurate title for the content of this
article would be "Debunking the myths that Rust is not safer or as fast as
C++".

~~~
dang
Ok, we've debunked the title above.

~~~
junke
Are comments about the title of articles offtopic? Because sometimes comments
that discuss titles, without being inflammatory, are flagged, and sometimes
they aren't.

~~~
dang
I suppose they are in principle, but we don't have an explicit rule against
them, and user passions around titles burn so hot that it wouldn't make a
difference if we did. There are also so many of them that you're not likely to
see consistency around what gets flagged vs. not. Are there specific links we
could look at?

~~~
junke
Thanks for replying. I was thinking about the "Make X Y Again" types of
article that, intentionally or not, invites offtopic discussion.

The original comment from simonw
([https://news.ycombinator.com/item?id=23137531](https://news.ycombinator.com/item?id=23137531))
originally did not have many comments but was already flagged when I saw it
first, which was strange to me because the comment basically says "this is a
bad title (it distracts from the content), don't do that". This is quite a
reasonable comment to me.

So under that comment are many exchanges that may or may not be offtopic, but
I was curious in particular about why that parent comment was flagged, and
what recourse users have when something is flagged but maybe shouldn't (wasn't
there a "vouch" action?). My opinion is that if the submission itself brings
political subject into light, even indirectly, it is unfair to flag political
comments as offtopic.

~~~
dang
A bunch of users flagged that comment. We can only guess why users flag
things, but my guess is that in this case they thought it was a specimen of
the very distraction it was worrying about.

~~~
junke
I agree that it's probably what they think. My opinion is that flagging is
unecessary in that case since people can already downvote and hide distracting
comments.

Thanks for your answers.

------
brenden2
Strictly speaking it's the compiler that generates faster code.

~~~
mschuetz
You'll never get faster code out of JS, though. Language design matters a lot.

~~~
dathinab
Well, there are some close to native code speeds for some (very constrained)
use-cases.

They way it's done is that if you use a certain stile of C the compiler will
speculative do assumptions about the code allowing it to basically add all the
C optimizations. Except that it always has to check if the assumptions are
uphold and then fallback and that because it's a JIT it has much less time to
optimize the code and do cross-code-section optimizations.

