That... doesn't seem to be idiomatic Rust at all. unsafe and passing pointers everywhere? Reimplementing vectors and linked lists. Also, makes use of a ton of macros.
That's basically writing C++ in Rust.
That... doesn't seem to be idiomatic Rust at all. unsafe and passing pointers everywhere? Reimplementing vectors and linked lists. Also, makes use of a ton of macros.
That's basically writing C++ in Rust.
Do you think this would impact Rust build times?
> Reimplementing vectors and linked lists.
Someone has to implement them.
Note that they're also implemented in the C++ code, so the comparison is fair.
> Also, makes use of a ton of macros.
Can you show an example where the Rust code uses "a ton of macros"? The only place I can think of is the tests, but I thought macros were what you're supposed to use in Rust for assertions.
If you're referring to proc macros, I thought those were very popular in Rust.
The commenter you are so dismissively replying to is the author. He did not "give up only a little beyond something" like you did, he made a serious effort that probably took hundreds of hours, and documented it. I am not sure why you are posting such a comment full of disrespect for his work, after reading a couple of paragraphs of his article.
Is there really reason to believe that would be in advantage of Rust build times?
Ok, you say the proc macros you say.. but it also commented on, and also not unidiomatic?
Code size after transliteration is a race the target language can seldom win; abstractions that also exist (or close enough) in the target language get translated 1:1, abstractions which don't get broken down 1:n, and abstractions which exist in the target language but don't have analogues in the source language generally don't get used at all. It's hard to end up with a shorter program following this approach. Only boilerplate with redundant repetition and ceremony - code which has little functional effect other than to satisfy declaration order rules, symbol visibility and so on - can get eliminated.
Perhaps, because the result is so un-idiomatic, it's unlikely to benefit from tweaks conceived for idiomatic Rust code. The Rust team uses Crater runs (re-compiling all of the public crates) to identify performance changes in the compiler, so if you do stuff that's far enough off the track there's no reason the compiler would get optimised around that.
Making your own vector seems like it'd be fairly up there.
> Someone has to implement them. Note that they're also implemented in the C++ code, so the comparison is fair.
I'm not sure I buy this argument in code like yours which almost invariably should just use the standard library and definitely needs to measure before replacing standard library features with your own hand-rolled equivalents. As I understand it your concern in this work was about development, and having a good standard library to lean on is a huge boon for development.
For example in strolling around this code, I ran into sorted_search. But, isn't this just [&str]::binary_search() except written by hand?
Actually the fact there are "linked lists" and yet there's no concurrency sets off alarms in my head. In highly concurrent software there are a bunch of clever lock-free algorithms for linked lists, so if you need that then you need it. But if you don't need this the linked list is usually going to be a mistake because on modern hardware its performance is abysmal.
Usually this is associated with C programmers, who don't know any better, but you've got other data structures, so, why Linked Lists ?
I did for the C++ code. And for a fair comparison, I ported my C++ vector to Rust.
If I made the C++ code use a custom vector and the Rust code use the standard vector, then people would complain that the Rust code was artificially shorter.
> For example in strolling around this code, I ran into sorted_search. But, isn't this just [&str]::binary_search() except written by hand?
Yes. But the standard binary_search isn't `const`, and mine is. I need to run my `sorted_search` at compile time to map translatable strings to magic numbers. (The C++ code does this too.)
> Actually the fact there are "linked lists" and yet there's no concurrency sets off alarms in my head. [...] why Linked Lists ?
The linked lists are actually linked lists of arrays. They are not grade-school linked lists with one item per node. In the C++ code, the classes are linked_bump_allocator (a memory arena) and linked_vector (a deque-style container).
I wrote linked_bump_allocator so I could use an arena allocator for parser ASTs, temporary strings, etc. I originally used Boost's monotonic allocator, but I wanted a rewind feature, so I wrote my own implementation.
I wrote linked_vector only because std::deque took too long to compile. (I'm not kidding.) So I could have easily used Rust's standard deque.
If you’re benchmarking the two compilers for lines of code, you’ve done a really good job and people shouldn’t complain about the Rust code length.
> then people would complain that the Rust code was artificially shorter.
If you’re trying to benchmark Rust development vs C++ development, then people cannot complain that the Rust code is “idiomatically shorter”.
Arguably right now your benchmark has the Rust code “artificially longer”. Which is fine if that is what you’re intentionally trying to measure.
People would also complain that even though you claim “my C++ code is longer than Rust but compiles just as fast”, you’re not factoring the extra things Rust’s versions do
AFAIK they are very popular but also not recommended unless necessary, as they increase build times considerably.
All languages with macros tend to be abused, and the recommendation, whatever the language, is use as few macros as necessary.
I would expect an idiomatic implementation to drastically go down in lines of code... while compile times probably will go up.
Can you give an example of where something in the project could be, say, 20 lines of code instead of 30? https://github.com/quick-lint/cpp-vs-rust/tree/master/rust
It's also idiomatic for push to return nothing and for pop to return the item (if any - and not crash if not empty!). Similarly back should return an Option instead of crashing on empty (and is called last in Rust). for_each is also unidiomatic - it's much more useful to provide an iterator over the collection.
Here's an example implementation in ~60LOC instead of ~200: https://play.rust-lang.org/?version=nightly&mode=debug&editi...
btw the author is looking to improve the Rust version and is accepting contributions in that repo if you wanna improve the experiment
That said, the pop implementation seems faulty in that it does not appear to deallocate empty chunks. Does Rust have any non-obvious magic here?
[1]: https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees
No, the only magic here is my utter lack of testing for throwaway HN code :) A corrected implementation would be:
pub fn pop(&mut self) -> Option<T> {
let last = self.chunks.last_mut()?;
let ret = last.pop()?;
if last.is_empty() {
self.chunks.pop();
}
Some(ret)
}
This is only correct if we maintain the invariant that we do not keep any empty chunks around, but the rest of the implementation does not violate this.(And further hint, that shouldn't be the significant hook here..)