According to this comment: <a href="https://old.reddit.com/r/rust/comments/p0ul6b/when_zero_cost_abstractions_arent_zero_cost/h8b7b2w/" rel="nofollow">https://old.reddit.com/r/rust/comments/p0ul6b/when_zero_cost...</a> , `rustc`/`llvm` is indeed able to optimize the wrapped example to a big `memset`. Sure in this specific case, it is different from the first one which delayed the zeroing, but that is not unintelligently `clone` many times as claimed in the article.
I don't really feel like this is an issue of "zero cost abstractions". The baseline expectation should be that it will do something reasonable, which in this case is probably `if Copy do bit-setting with memset/bzero if possible, otherwise clone() loop` which is what it appears to do according to a reddit comment linked to elsewhere.<p>I'm not really sure it follows, though, that you should ever expect more than that. It's nice if you can get it but the presence of a very specific optimization shouldn't be taken as the new "zero cost" baseline, and I don't think it should be taken as given that a newtype will inherit all possible optimizations of its enclosed type.
> the desire to optimize one specific case while neglecting the general case<p>This feels like a recurring pattern in Rust's design whether we're talking about performance, what's accepted by the compiler, etc. I'm not sure it's a bad thing exactly, but it leads to a lot of unintuitive footguns. Of course the consequence of these footguns is de-optimization or a compiler error, instead of a runtime exception or a memory error. But nevertheless it can make for a confusing landscape of behavior to navigate.<p>Should they <i>not</i> try and spot-optimize common cases where they can, just for the sake of predictability? No, I don't think so. And so I don't know what the answer is. I just often (and even more so when I was new to it) find myself surprised by Rust's general-rules-that-actually-have-notable-exceptions. Though who knows, maybe it's just not possible to make a language this powerful that doesn't run into this problem.
There are many useful and interesting comments in the discussion about this on r/rust.<p><a href="https://old.reddit.com/r/rust/comments/p0ul6b/when_zero_cost_abstractions_arent_zero_cost/" rel="nofollow">https://old.reddit.com/r/rust/comments/p0ul6b/when_zero_cost...</a>
Some months ago I ran into the same problem in slightly different clothing<p><pre><code> // 1st-way
vec![0; len];
// 2nd-way
std::iter::repeat(0).take(len).collect::<Vec<_>>();
</code></pre>
As told about in the blog post the first one will be able to be optimized via specializations to a single calloc call. The other here cannot use the same specialization as it does not seem to be able to specialize on the type of iterator yet. This means it will be a malloc followed by a memset when compiled.<p>godbolt: <a href="https://godbolt.org/z/8bM7zz4E7" rel="nofollow">https://godbolt.org/z/8bM7zz4E7</a><p>the specific specialization: <a href="https://github.com/rust-lang/rust/blob/master/library/alloc/src/vec/spec_from_elem.rs#L35..L48" rel="nofollow">https://github.com/rust-lang/rust/blob/master/library/alloc/...</a>
What does that post prove except that "allocating memory that must be zeroed on use but is never used" is million times faster than "allocate memory and zero it right away"?<p>Sure, I can see how the code doesn't immediately tell you the difference but <i>after</i> you understood it, isn't it completely logical?<p>The title seems clickbaity and not accurate when you dig into the article.
> The point of this post is not to bash the Rust team, but rather to raise awareness. Language design is a difficult process full of contradictory tradeoffs. Without a clear vision of which properties you value above all else, it is easy to accidentally violate the guarantees you think you are making. This is especially true for complex systems languages like C++ and Rust which try to be all things to all people and leave no stone of potential optimization unturned.<p>This paragraph is worth highlighting. Say yes to everything and vision becomes meaningless. Can't move in one direction when you're pulled by an infinite number of stakeholders on all sides.
The article is not about what you think it's about.<p>1. Turns out that zeroing u8 is a special case so if you allocate 17 GB of your custom structure Rust is gonna init that for you.<p>2. Okay, so you don't understand how RefCell works? The caller to this function should be holding r, not passing it into the function. Of course that will just correctly Panic, so it's not wonderful. But that is the point of RefCell.<p>Generally more stuff runs fast in Rust than in other languages, but you can always find bad counterexamples in any language.
> However, when v has an arbitrary type, it will instead just clone() it for every single element of the array, which is many orders of magnitude slower. So much for zero cost abstraction.<p>Arguably the WrapperType, i.e. any arbitrary type is the <i>general case</i> and thus the baseline performance. `u8` is the special case (hence specialization) that performs the alloc_zeroed optimization. So it's not really that the abstraction adds a cost. It's just that the special case removes a cost paid by everything else.<p>In the future the vec initialization might be fixable, but this requires turning potentially undefined values into some valid if arbitrary bit patterns (i.e. llvm's freeze) to compare it against zero.
There are no zero cost abstractions. (According to Chandler Carruth, and he makes a convincing point). <a href="https://www.youtube.com/watch?v=rHIkrotSwcc" rel="nofollow">https://www.youtube.com/watch?v=rHIkrotSwcc</a>
Personally, this is why I think specialization is a bad idea. It means that minor refactors can have very strange impacts on performance, and that every API now has an (often undocumented) set of types for which it is unexpectedly much more performant.
Arguing against zero cost abstractions should use as a counter argument an abstraction that is advertised as zero cost.<p>I do not think wrapped types are so advertised.
The 'zero cost' attitude is one of C++ problems. Zero cost abstractions are never really entirely zero cost. You still pay, often in something that you didn't bother to measure, which (in C++) is usually compile time or programmer productivity or programmer cognitive cost or compiler complexity.<p>There's a reason a lot of the complaints about C++ mention bloat or the difficulty of practically choosing a safe subset (you could use a subset, but you're very likely dependent on coworkers, existing codebases and library authors which may not share your subset). A lot of people added 'zero cost' stuff, and all of that has a cost.<p>For example, lets say Rust's newtype pattern worked perfectly everywhere, and all of the author's examples run fine as u8 with no runtime cost. There would likely still be a small cognitive cost to learn this, a small cost to unwrap the types (for other programmers reading the code), and a tiny cost in compile time. Typedefs are worth it, and it's all very reasonable to pay this!<p>But there's still a cost, and when people never believe there's a cost a language ends up like C++.
This is .. amazing? Using what essentially should amount to a strong typedef .. causes code to be millions of times slower.<p>This is worth bookmarking.