3 ms·
So, first of all, this was posted on HN last year: https://news.ycombinator.com/item?id=23134688 https://news.ycombinator.com/item?id=23134688 To the point tho
by einpoklum 5y ago
So, first of all, this was posted on HN last year: https://news.ycombinator.com/item?id=23134688 https://news.ycombinator.com/item?id=23134688
To the point though, while some of the points in the article are well-taken, especially how one can pick-and-choose scenarios where one language or the other has a noticeable implementation issue or design misfeature - the article is still problematic for at least two reasons:
1. Mixing up a comparison of the languages and of the results of specific compilations by a specific compiler.
If C++ semantics allow doing something easier, but Rust semantics do not, e.g. because of avoiding undefined behavior, then regardless of whether a specific compiler makes use of this fact - it is still a benefit, speed-wise. Specifically, in Rust, if I take two non-negative numbers and multiple them using the square() function discussed in the article, then check the result for being negative - with C++, the check is redundant and can be dropped in favor of a true value during compilation; with Rust, it cannot. Now, will some version of LLVM do that? I don't know - but it could (and it should).
2. Misrepresenting how the langue affects the behavior of the compiler back-end.
The article says:
> Rust is based on LLVM, which is the same back end that Clang is based on. Therefore, Rust has inherited "for free" and shares with C++ most of the language-independent code transformations and optimizations.
That's simply not true. That is, LLVM is _capable_ of most of the same transformations and optimizations; but the question of which of them it is _allowed_ to use depends on various kinds of semantic information specific to the programming language.
This is apparent even in trivial examples. Here's one:
https://godbolt.org/z/rMW3vsEvo https://godbolt.org/z/rMW3vsEvo
where the same function is compiled in Rust:
pub fn check(num: i32) -> bool {
let y = if num < 0 { -num } else { num };
return y >= 0;
}
and in C++:
auto check(int32_t num) {
auto y = (num < 0) ? -num : num;
return y >= 0;
}
The compilation results are:
example::check:
mov eax, edi
neg eax
cmovl eax, edi
test eax, eax
setns al
ret
and:
check(int): # @check(int)
mov al, 1
ret
respectively. How come? Isn't it all LLVM under the hood? It's even the same LLVM, version, 12.0.1 for both languages... Now, I'm not sure why exactly this happens since I'm no Rust expert. But - LLVM is not _allowed_ to apply the same optimizations to the Rust function as to the C++ function, so the results are different.
- zRedShift 5y agoWell, for one, in the Rust version, check(i32::MIN) will return false if built with overflow checks disabled (the default for release builds). C++ will of course always return true.
- zozbot234 5y agoRight, wrt. C++ the "square" operation on signed integers makes the whole program undefined if overflow occurs, whereas the Rust equivalent is defined to wrap using two's complement arithmetic. And of course the square of INT32_MIN cannot be represented in int32_t.
- einpoklum 5y agoIndeed, but then you are partially "un-rustifying" things. That is, the author specifically boasts in the article that: > In Rust, an undefined-behavior arithmetic issue like that is literally impossible. ... but that's only true if the safeties are on, specifically overflow checking.