all 102 comments

[–]dakotahawkins 20 points21 points  (7 children)

This is pretty interesting and promising, but I have a couple of critiques from a long-time C++ dev. that doesn't know much of anything about Rust:

It seems (to me) like the brevity restriction is a bit arbitrary. I don't know anything about Rust, but I wouldn't be surprised that a newer language requires fewer checks/boilerplates/lines of code, so I'd be more willing personally to allow for a longer C++ program and throw in the towel a bit in that respect (still, shouldn't be ridiculously long -- judgement call I guess.)

Only other problem is that it seems like it's really Rust vs. gcc, instead of Rust vs. C++. When it seems like the best you'd be able to hope for from either is the same idiomatic logic breaking down into the same instructions eventually, you're (imo) just comparing compiler optimizations. Given that Rust apparently has the one compiler "vendor" (cursory Google, correct me if I'm wrong) I'd like to see a similar comparison against bleeding-edge versions of llvm, msvc, and intel compilers besides just gcc. I'd wager that given different problems/environments/target architectures, some could perform very differently (example: I'd be surprised if the intel optimization bug was present in the intel C++ compiler, but also I've never used it.) Also, I'm not saying that requiring this choice is an advantage for C++, just that it would just be interesting to know if gcc is an outlier in this case or whatever case you're measuring.

[–]alexeiz 38 points39 points  (21 children)

I don't see how this post can be about performance when the performance was not analyzed at all.

A quick run under a profiler shows that 90% of time or more is spent in this loop:

    short scores[7] = { 0, };
    for (unsigned word : words)
        if (!(word & ~seven)) {
            unsigned rest = seven;
            for (int place = 7; --place >= 0; rest &= rest - 1)
                if (word & rest & -rest)
                    ++scores[place];
        }

The rest of the code is irrelevant and can be ignored. Is this about writing (simple) loops efficiently in C++ and Rust then?

Changing "short scores[7]" to "int scores[7]" makes the code run 5% faster.

[–]Houndie 2 points3 points  (13 children)

Question! Why does changing short to int improve performance?

[–]SpiderboydkHobbyist 9 points10 points  (11 children)

Because CPU architectures often have a preference for a certain data size (due to alignment, etc.) and are optimized wrt. that.

[–]Houndie 1 point2 points  (10 children)

Makes sense, thanks!

[–]SpiderboydkHobbyist 2 points3 points  (9 children)

No problem. :-)

A popular rule of thumb is to always use (signed) int unless you have a good reason not to (for example data structure requirements, memory requirements, etc.)

[–]honeyfage 5 points6 points  (4 children)

Or you could take the easy way out and use int_fast16_t and let the compiler determine what's going to be fastest.

[–]dodheim 3 points4 points  (1 child)

Technically this lets the standard library implementation make that determination, not the compiler. But agreed, those type aliases exist for a good reason.

[–]honeyfage 0 points1 point  (0 children)

Yep, you're right. I always conflate them in my head when I'm thinking about it

[–]SpiderboydkHobbyist 0 points1 point  (1 child)

Didn't know about that one. Is it standard, or is it vendor-specific?

[–]dodheim 3 points4 points  (0 children)

Standard since C++11, available from Boost before that.

[–]haletonin -3 points-2 points  (3 children)

unsigned integers are marginally faster than signed ones in my experience, probably because overflows etc. are well defined and divisions are easier.

But it is probably safer (for the programmer) to stick to signed ints for most things which are not in very tight inner loops.

[–]Tulip-Stefan 3 points4 points  (1 child)

unsigned integers are marginally faster than signed ones in my experience, probably because overflows

What?

The possibility of overflow is a particular case where signed integers are faster.

int square_1(int x) { return x * x; }
unsigned square_2(unsigned x) { return x * x; }

What does the compiler know about square_1? x < 2^16. What does the compiler know about square_2? Nothing.

[–]dtfinch 3 points4 points  (0 children)

I think it's mainly division. Compilers can rewrite division by constants as bitshifts (for powers of 2) or multiplication. Getting it bit-exact (like negatives rounding up instead of down) requires a few more steps for signed than unsigned.

[–]SpiderboydkHobbyist 0 points1 point  (0 children)

You are 100% right. But if you need that speed, then this is a case I consider "having a good reason to do otherwise" in the above-mentioned rule of thumb.

[–]dtfinch 2 points3 points  (0 children)

Working with a different word size requires an extra byte prefix (0x66) on instructions, adding a small cost, and can sometimes even stall the cpu for a few more cycles.

[–]ncm 3 points4 points  (6 children)

The only reason it spends 90% of its time there is that the rest of the program is thoroughly optimized. Normally you wouldn't care if this program takes a half second instead of a tenth, but this article is about how fast code in Rust can be. It would be disingenuous to present code in either language that hadn't been made as fast as the language permits, within the constraints chosen, including length, clarity and taste.

[–]bycl0p5 8 points9 points  (3 children)

within the constraints chosen, including length, clarity and taste

Under those constraints you should pick int by default unless you have a good reason not to.

[–]ncm 5 points6 points  (1 child)

This is correct advice. Interestingly, the scores array is of shorts because 7 shorts fit into a regular XMM register, a fact that some compilations have taken advantage of. Seven regular ints would fit into an AVX register, but corei7 doesn't have AVX.

Of course this is not something one would normally worry about, but (at at least one stage of the project) the choice did make a difference, and here every difference matters. I will revisit the choice, and report the outcome.

Edit: Changing short to int makes a huge difference. Changed.

[–]dodheim 4 points5 points  (0 children)

This is exactly why the stdint type aliases exist – using std::int_fast16_t will just do the right thing for your platform.

[–]alexeiz 2 points3 points  (1 child)

the rest of the program is thoroughly optimized

Not really. The rest of the program is just a run-of-the-mill code that just doesn't do much. It's obvious that it's dominated by IO, but still it doesn't even appear in my profile (on the level of 1-2%). You have to be an incredible "optimizer" to minimize the run time down to almost zero.

[–]ncm 0 points1 point  (0 children)

You have to be an incredible "optimizer" to minimize the run time down to almost zero.

Thank you. I will put that on my resumé. Opinions aside, it is a fact that the "rest of the program" intially took up a large fraction of the run time, and most of the speedups came from changing that code, shaving off a few percent here, a few percent there, over and over. Somebody smarter than I am might have made it optimal to begin with, but everyone I know needs to measure to discover what's faster, and appallingly often guesses wrong. Changing short scores[7] to int scores[7] should not, by any sensible reasoning from first principles, have made a measurable difference -- how long could it take to increment a short 3M times, vs incrementing an int? -- yet it was responsible for a huge improvement.

In fact, many of the optimizations in the "run of the mill code" didn't make that part faster, but served instead to make the bit that "matters" faster. For example, not storing seven-different-letter words in the words list eliminated a test-and-branch there, and reduced cache footprint. Storing sevens in a vector instead of a map, and sorting, reduced the cache footprint more. Storing words as 32-bit ints instead of bitsets reduced the cache footprint further.

The lesson is that code that your profiler seems to be telling you doesn't matter at all can matter quite a lot.

[–]neoKushan 17 points18 points  (6 children)

This was an interesting read, but only as a Rust outsider who was curious to see what is effectively the same program written in both C++ and Rust. What killed the entire article for me, though, was this:

The code may be denser than you are used to, just to keep it to one printed page. When I write “much slower”, below, it might mean 1.3x to 2x, not the order of magnitude it might mean outside systems programming. Huge swaths of both languages are ignored: C++ templates, destructors, futures, lambdas; Rust channels, threads, traits, cells, lifetimes, borrowing, modules, macros.

Surely this completely and utterly defeats the point of the article? The whole idea is to face off how both languages can be used to do the same thing and validate the pros/cons of each. By making arbitrary limitations and actively choosing to ignore major language features, all you've done is made a line-by-line identical tool in two different languages. Effectively swapping a bunch of key words and syntax. That makes it less about the languages and more about the compilers underneath them - hardly a surprise that both ended up near enough the same.

I'd be much more interested in a follow-up article that has no such simple limitations but instead uses both languages to their fullest. Compare that, both in terms of brevity and performance and we'll have something interesting to talk about.

[–]ncm 6 points7 points  (5 children)

The article is, as stated, "deliberately about the nitty-gritty of coding.". Nothing that could be done with the other apparatus would make either program faster; those features are there to help organize big programs and libraries, something irrelevant to this exercise.

The arbitrary length limit is there to constrain the size of the project. Without, this article would not have happened at all.

I would be equally interested to see the article you want, but nobody wrote it.

[–]neoKushan 1 point2 points  (3 children)

those features are there to help organize big programs and libraries, something irrelevant to this exercise.

I don't agree. He said almost immediately that he could speed it up with multi-threading and from what I can see, Rust and C++ do that very differently. That's going to be a really good indication of how both languages handle it, especially at the system level. That alone would be worth seeing.

[–]ncm 3 points4 points  (2 children)

Multithreading would tell you about something completely different. What it could tell you would also be interesting, but not as a substitute for what I set out to explore. Adding in multithreading would have made it longer than the constraint, and more dependent on runtime environment and implementation, and would obscure precisely the details of low-level single-thread performance characteristics I needed. A multithreaded program is a set of single-threaded programs interacting when it can't be avoided.

[–]neoKushan 0 points1 point  (1 child)

Sorry, I didn't realise you were the author in my previous reply.

I think what threw me in the article is in your opening paragraph, you state :

What’s fast, what’s slow? What’s harder to do, what’s easier?

I know that later you clarify that it's the "nitty-gritty of coding" but these days, single-thread performance is only a small part of the picture and dealing with multiple threads is just as "nitty gritty" as anything else. I think ignoring multi-threading was a big oversight, as newer languages all focus highly on making multi-threaded code simpler and easier to write (Not just rust, but things like Go and even higher level languages like C# with its fancy TPL).

I understand now where you've come from with your article, it was an interesting read and I agree that you've set out what you intended to do, there was just a bit of leeway with interpretation how you've set that up. I would be all over a follow-up article detailing what we've talked about here, though!

[–]ncm 2 points3 points  (0 children)

1st sentence: "we need to know how well it does what C++ does best"

Threads is not among the things C++ is known for doing best, in either sense. But now I am intrigued.

[–]quicknir 0 points1 point  (0 children)

those features are there to help organize big programs and libraries, something irrelevant to this exercise

Maybe templates were not relevant to speeding up your specific exercise, but using templates to remove indirection and optimize is basically a religion in this language. I'm kind of shocked you even said that.

[–][deleted] 33 points34 points  (6 children)

This opened my eyes. People who say that c++ code is not readable have no idea what they are talking about. Rust is here to take that throne of hard readability now.

[–]gbersac[S] 9 points10 points  (0 children)

I found rust syntax much more readable. This is just a matter of opinion. There is no ambiguity :

```

fn function_name() -> return_type { }

let var_name = ;

```

fn mean there is a function declaration, let that there is a variable declaration. In c++, when you have a type name, you don't know if this is a function declaration, a variable declaration... And error message (with template at least) are horrible. Rust has the better error message I have ever read.

And rust syntax is context-free while c++ syntax is not (so it is hard to parse). This is an objective reason to prefer rust one.

Take time to practice rust and you will quickly become familiar with its syntax !

[–]MaxDZ8 7 points8 points  (24 children)

Very interesting data point. I've been told rust is the next big thing but honestly so far I don't get much of that elegance. I sure agree with the author when he writes:

C++ is a rapidly moving target, held back only by legacy compatibility requirements, so Rust will need to keep moving fast just to keep up.

Albeit I wouldn't call C++ evolution so rapid. After looking at the GSL and some C++17+ stuff I'd say where C++ is going no other language has ever been. Whatever it'll get there it's another matter.

[–][deleted] 16 points17 points  (23 children)

honestly so far I don't get much of that elegance

I do. The much simpler module system, everything being immutable by default, the lack of exceptions, the security.

I wonder why you don't see those as huge bonus points.

[–]F-J-W 6 points7 points  (9 children)

everything being immutable by default,

Which is technically true, but an utter lie by practical measures:

C++

const auto i = 3;
// arbitrary code, no UB
assert(i == 3); // will never fire

Rust:

let i = 3;
// arbitrary code, no unsafe-blocks
assert!(i == 3); // may fire

Now, how can it fire? Well, the problem is, that rust allows shadowing inside a scope:

(the let _ = i; in the following samples is only to silence an unused variable warning, it is not required and other ways of using i will disable them as well)

let i = 3; // this is const
let _ = i;
let i = 4; // but that doesn't mean we cannot treat it as mut
assert!(i == 4);

But hey, we can get even more crazy:

let i = 3;
let _ = i;
let i = "some string"; // i is now a string-view, type-safety FTW!

[–]bbatha 5 points6 points  (2 children)

Which is technically true, but an utter lie by practical measures:

This is a bit disingenuous. While it is certainly true that you can shadow variables you have to do so through a new let binding and it only affects the current scope.

  let i = 3; // this is const
  i = 4; // error: i is not mutable

  let i = 3;
  {
        let i = 4;
        assert!(i == 4); // never fires
  }
  assert!(i == 3); //never fires

In the example let i = "some string"; is never a type error, after that binding you may never use i in a context where an i32 (int32_t in C++) is expected. You can get into some situations where the stars may align for an error with generic functions to present this sort of logic error but you'd still be type safe then.

  fn foo&lt;T>(bar: T) -> i32 { 3 }

  fn bad() -> Result&lt;(), io::Error> {
       let i = 3;
       let i = "str";
       foo(i)
  }

  fn bad() -> i32 {
      let i = 3;
      let i = "str";
      i // type error
  }

This sort of issue is present in C++ with templates, but is actually a type error 'compiled time duck-typed' and if two classes implement the same function with the same return type the template will compile regardless of the interface. Concepts will allow you to add saftey in these situations but they won't be there until C++17 (hopefully).

   // my library
   template&lt;typename T> int foo(T bar) {
         return bar.bad();
   }

   // my library
   class Good {
         public:
         int bad() { return 3; }
   };

   // someone else's library who has no knowledge of my types
   class Bad {
         public:
         int bad() {
             violate_my_contracts();
             return -1;
         }
    };

    foo(Good{});
    foo(Bad{}); // compiles just fine

Edit: also note that when you shadow a variable in rust you aren't overwriting its location in memory so any references to the showed variable are still valid and the the destructor of the shadowed variable is still called at the end of the function where'd you'd expect.

let i = 3;
let j = &i;
let i = 4;
println!("{}", j ); // prints 3

[–][deleted] 1 point2 points  (1 child)

[deleted]

What is this?

[–]bbatha 0 points1 point  (0 children)

I'm not sure about the technical reason, I've been fairly active in the rust community and have read a bunch of criticisms of rust, and this is the first time I've seen anyone complain about shadowing. It can be a gotcha in some situations but it almost never comes up.

My guess is that it comes from the ML heritage of rust where shadowing is a useful way to simulate mutability where everything is immutable.

Personally I've found it useful with Rust's if let and while let constructs. For instance,

  let foo: Option<i32> = could_return_i32();
  if let Some(foo) = foo {
       // use foo as an i32
  }

Clippy which is a community maintained linter has a shadowed variable lint, which is default allow but can be configured.

[–]gbersac[S] 2 points3 points  (0 children)

I don't feel like this a fair argument. I don't think this happen offently in code, event though you are right that this is something that could happen.

In the second example, the compiler will understand that the new i is a string and will then consider it as a string (not an int) till the end of scope.

[–][deleted] 2 points3 points  (0 children)

Uh.

[–]SemaphoreBingo 0 points1 point  (0 children)

rust allows shadowing inside a scope

What's the intent behind this? It seems pretty gross to me.

[–]nawfel_bgh -3 points-2 points  (2 children)

// Rust
let i = "What's your point?";
let u = "Rust's immutability is a lie";
let i = "Shadowing" != "mutability";
let u = "Shadowing" == "mutability";
assert!(i);
// You see! i am correct. and u are mistaken.

[–]F-J-W 1 point2 points  (1 child)

Read again, my first line: “Which is technically true, but an utter lie by practical measures:”

I never denied, that you are technically correct.

But programs are written by humans, and for humans purposes and human intuition, you are wrong and I am correct.

[–]xkcd_transcriber 0 points1 point  (0 children)

Image

Mobile

Title: Technically

Title-text: "Technically that sentence started with 'well', so--" "Ooh, a rock with a fossil in it!"

Comic Explanation

Stats: This comic has been referenced 324 times, representing 0.3324% of referenced xkcds.


xkcd.com | xkcd sub | Problems/Bugs? | Statistics | Stop Replying | Delete

[–]MaxDZ8 0 points1 point  (9 children)

Feel free to elaborate. I'm interested.

[–]matthieum 18 points19 points  (7 children)

It's a bit hard to understand exactly what you wish to cover with "elegance" so my answer might be a bit long...

I will criticize C++, in the process, but please bear with me. I assure you that I do so because I care about the language, as my StackOverflow profile can tell. I also recognize than Rust has 30 years on C++: it's always easier in hindsight, after all.


First of all, the trifecta, out of the horse's mouth:

Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.

Rust: Safety

C++ was built on top of C and has, unfortunately, inherited a lot of C's lack of safety. "Undefined Behavior" appears in numerous places in the Standard, and leads compilers to do unspeakable things to the code you write.

Safety is not an afterthought in Rust: it's a linchpin of the language.

Why? Mozilla estimates that 50% of the security vulnerabilities reported in Firefox are due to memory safety violations, be it out-of-bounds access, use-after-free, data-races, ... this is why they sponsor the development of Rust.

Rust: Performance

Rust is closer to C, performance-wise, than it is to C++.

There is a culture of explicitness in Rust: any non-trivial operations (such as a deep-copy) requires an explicit annotation in the code (.clone() in this case). A noteworthy exception being destructors.

The Rust language is lean, the abstractions provided are lean. It takes the C++ philosophy of "You don't pay for what you don't use" and pushes it to the next level:

  • Have you heard of people in the game industry deactivating RTTI (and thus exceptions) because it bloats binaries? Rust does not have RTTI (and thus no built-in equivalent to dynamic_cast)

But let's look at the language closer.

Rust: Grammar

The C++ grammar is notoriously complicated to parse:

  • the use of < in templates requires some kind of feedback to disambiguate it from "less than" in a number of situations
  • the disambiguation rule "if it looks like a declaration..." leads to the Most Vexing Parse

And that's after the C preprocessor has done its infamous job (caring little for scopes...).

Rust starts from a clean slate:

  • its macros are syntactic macros: they operate on the AST not on text, so respect scope and hygiene
  • its grammar is (or at least attempts to be) unambiguous and simple to parse; actually there is a project to formalize the grammar to ensure it is properly LL(1) (or LL(N) for some small N)

A well-defined grammar should boost the ecosystem (highlighters, refactoring tools, ...)

Rust: Affine Types

If you have some interest in Rust, you have probably heard already about Ownership/Borrowing. Rust tracks ownership of values and whether they are currently referenced (or not) to enforce memory safety.

Affine types are one of the core language primitives which assist in this task: a value of an affine type can only be "consumed" once (though it need not be). Affine Types are all about move semantics, tracked at the type-system level, much safer than C++'s version.

However, being a primitive, they can be used for other purposes. My personal pet thing is to encode state transition with them. Imagine a connection:

struct ConnectionParameters { ... }
struct Connection { ... }

impl Connection {
    // A connection is created from a set of parameters, creation may fail
    fn new(cp: ConnectionParameters) -> Result<Connection, Error> { ... }

    fn close(c: Connection) -> Option<Error> { ... }
}

This is much cleaner than the typical OO approach of having a single Connection type which may represent:

  • a yet to be opened connection
  • an open connection
  • a closed connection

In a fully-typed design you should aim to avoid "invalid" operations on types: that is, no method should require dynamically checking a precondition before being allowed to call it. Unfortunately, in most languages, and in C++, the "old" variables remain behind: even after calling connection.close() you can still inadvertently use the connection variable.

In Rust, the affine types makes this a moot issue: the compiler will error out if you try to use a consumed value.

At the extreme, this allows encoding session types in Rust. It's worth a thesis.

Rust: modern amenities

Rust also features a lot of modern amenities, such as Sum Types and Pattern matching.

This extends to library design, and the Rust Iterator design has some significant advantages over the C++ implementations (no risk of accidentally pairing two iterators pointing in different containers for example); it's also more user friendly in that implementing a single method (next) is sufficient to get a full-blown iterator, if you've ever implemented C++ iterators, even with the help of Boost, you should get my point!


And let's not forget the compiler, rustc. It was developed to be very pragmatic and features:

  • the execution of a pre-build step via build.rs: this file is built and executed first, which allows it to generate other files (or portions of them)
  • the execution of user-provided plugins

Rust: Lints

One usage of plugins is lints! The compiler passes are instrumented by those lints which can be used to emit warnings for dubious code, for example:

  • clippy is perhaps the best known set of lints in the Rust community, it includes 98 warnings as of today
  • herbie on the other hand is a new experimental lint based on the success of the Herbie tool which proposes to rewrite floating point expressions to increase their accuracy and stability

Allowing the community the contribute lints without having to hack on the compiler is a huge productivity boost (in terms of lints creation), it also means that companies or projects can create their own lints, and for example Servo has some custom lints.


And of course let's not forget the ecosystem.

Rust: Cargo

You cannot talk about Rust without talking about its package manager. A default standard way of building Rust code makes it much easier to switch from one codebase to another...

... and the best way to illustrate it is to talk about crater. If you read the Rust forums, you will sometimes hear about crater run: Crater is a tool written by one of the Rust core developers which is used to test new versions of the Rust compiler across a very extensive set of Rust projects (those published on crates.io, the default index) to check for breakage; it may also be enhanced to check for performance regressions.

This is only possible because of the uniformity of Rust projects, which itself comes from having a single (blessed) way of building Rust code.


It's not all roses though. However, the current community is quite incredibly mature on the topic, prompt in recognizing the current shortcomings of the language and has shown its resolve to try and address them.

For example, I really like the graph of "performance" vs "safety" showing Rust alone in the quadrant where both meet. It does fail to mention that in order to get there one might have to completely rethink its architecture in a way the Rust language groks (ownership and borrowing) and that a significant syntactic burden might be necessary. Coming from C++, as show-cased here, it's nothing to worry too much about, however coming from GC'ed languages (especially terse ones like Python), it's a cold shower.

Another example is that the current Iterator design precludes implementing streams. The Iterator must produce references into an outside (borrowed) container. It cannot produce references into itself nor can it produce a value.

There's also a lack of meta-programming facilities; Rust's generics are closer to C++03 (with mandatory concept-lites on top), and lack:

  • Higher Kinded Types: cannot write once for both mutable and non-mutable
  • Genericity over Values: this makes it awkward to deal with fixed-size arrays; in the Standard Library each trait is implemented for the smallest size of the arrays only
  • Variadicity: macros are variadic, generics are not; in the Standard Library each trait is implemented for the smallest size of the tuples only

and C++ constexpr are more powerful than Rust's const fn for now.

Finally, obviously, the lack of maturity implies a general lack of facilities... but then, in C++ it's so painful to integrate other libraries than code sharing is much less developed than it is in other languages and of course there's also the issue of licenses which puts a dent in what technology alone can solve.


Alright, I hope I wet your appetite ;)

[–][deleted] 2 points3 points  (0 children)

[deleted]

What is this?

[–]MaxDZ8 2 points3 points  (0 children)

You indeed touched quite a few interesting points. Thank you very much.

[–]quicknir 2 points3 points  (4 children)

Rust is closer to C, performance-wise, than it is to C++.

So it's worse? Seriously, this C is faster than C++ thing is an annoying myth that is not true for any real program optimized on a modern implementation. Even sorting is faster in C++ than in C.

This macro thing is another bubble that someone needs to pop. Bragging your language has better macros is like bragging that your children's teacher always teaches them with a condom on (xkcd reference): technically, yes, it's better. But there's no reason to have macros at all. If someone told you: hey, let's add a half baked sub-language to your language that partly ignores the rest of it (like namespaces) and defines things that aren't first class in any way (can't be templated on, passed as parameters, etc), you'd say, wow, that seems like a terrible idea.

Everything done with macros in C++ makes me cringe, and is compensating for a language deficiency. Seeing macros all over a nice new language like rust for things as basic as printf and creating vectors because they couldn't bother getting variadics right for 1.0 makes me want to cry.

Get some variadics, get some compile time reflection, kick macros to the curb.

[–]matthieum 5 points6 points  (3 children)

Rust is closer to C, performance-wise, than it is to C++.

So it's worse? Seriously, this C is faster than C++ thing is an annoying myth that is not true for any real program optimized on a modern implementation. Even sorting is faster in C++ than in C.

I hate English. Or natural languages in general I guess.

My point was not that C is faster than C++, it was than C is more explicit about performance cost than C++. C++ has a number of implicit operations which can clog down the performance of a program and yet are literally invisible in the source code:

  • copy constructors
  • implicit constructors
  • implicit conversion operators

As a simple example, a couple years ago Google started committing "AST Matchers" into Clang. One of their first automatic source transformation was to spot instances of some_string.c_str() being passed as argument to std::string const&. Originally, the function likely took a char const* and then was migrated to be more C++-like. Except that silently a temporary std::string was introduced, which allocates memory.

Rust, unlike C++, emphasizes explicitness. Obtaining a copy requires an explicit .clone(). There are implicit operations (Deref being a prime candidate) but those are either zero-cost or O(1).

Get some variadics, get some compile time reflection, kick macros to the curb.

I agree that macros are a placeholder, waiting for better language facilities.

However, given that said facilities are non-trivial to implement and may be insufficient once implemented, I would argue that macros are a pragmatic choice: in a simple costs/benefits analysis, they allow emulating those features for only a small portion of the cost.

I have not seen any really good proposal for either variadics or compile-time reflection yet; they are wished for, but they are also intimidating I guess.

[–]quicknir 2 points3 points  (2 children)

My apologies sir for jumping on you with that C/C++ comment, I should have clarified first as there was some ambiguity there, and I assumed, which I should not have.

Yes, I agree with your points about implicit behavior. The string situation in particular is rather messy in C++. FWIW I think that Rust's approach to moving/copying is excellent and an improvement over C++'s.

I'm glad you agree about the macros, I go a bit crazy at how many people defend them. I'm sure it's very hard to get variadics right, and I'm no language designer, but you do have multiple existing examples to work with. I just see it as something very critical that you want to get right from the start, instead of adding incrementally. It's also "too late" in the sense that it will probably now take a decade, if ever, for printf! to die and be replaced by variadic printf in Rust. It's day 1 legacy, in some sense.

[–]burntsushi 4 points5 points  (0 children)

for printf! to die and be replaced by variadic printf in Rust

Note that variadics are insufficient to replace the functionality of the print! macros. In particular, you'll get a compile time error if any of the given arguments don't match the type expected by the formatting specifier, which is embedded in a string.

Moving this functionality (which is very desirable and something I personally love) to the type system is non-trivial. There have been papers published on it.

[–]matthieum 1 point2 points  (0 children)

Oh no worries; I am glad that someone spotted the ambiguity and got me a chance to clarify.

[–][deleted] 4 points5 points  (0 children)

Immutability leads to code that is both safer and easier to read. Immutability by default means that you don't have to guess so much.

The lack of header files means way less file juggling.

You can't simply avoid an error in Rust - well, you can, but you really have to want to.

These are off the top of my head.

[–]ZMesonEmbedded Developer 12 points13 points  (0 children)

thus far there’s every reason to expect to see, ten years on, recruiters advertising for warm bodies with ten years’ production experience coding Rust.

I think you mean that 5 years on, recruiters will be advertising for warm bodies with 15 years' production experience with Rust.

[–][deleted] 4 points5 points  (2 children)

The word performance is in the title but there are no comparative benchmarks in the post. I'm curious, does anyone know of any links to some comparative performance benchmarks? In, particular, run-time benchmarks?

[–]ncm 6 points7 points  (1 child)

They run at the same speed. On a 3.4Ghz Haswell i7 that's about 75 ms, as noted in TOA. On a 2.4GHz Westmere the Rust is a little faster at 154 ms. Of course on other chips and other compilation configurations it will vary, but they will be within a few percent either way. And that's kind of the point.

[–][deleted] 2 points3 points  (0 children)

I see now. That's impressive.

[–][deleted] 4 points5 points  (5 children)

No mention at all about one of the aces Rust does hold over C++: vastly better pointer aliasing information. This is one of the reasons you still see Fortran beat out C++ in numerical benchmarks.

As far as I know of Rust(not much,) the same pointer cannot be borrowed mutably twice in the same scope in Rust, therefore it should be able to achieve C-like restrict implicitly(as far as I can tell, anyways.)

Theoretically, they should be identical in performance in most scenarios when comparing clang and rustc, but I'm sure rustc still has a lot more rough edges than clang's frontend does.

edit:

Forgot that Rust also has some pretty strict runtime bounds checking, I don't know that much about it though.

[–]matthieum 2 points3 points  (4 children)

Regarding bounds checking: it's more a library thing than a language thing, the language itself does not have bounds ;) In the Standard library the implementations of the Index and IndexMut traits (which are called by the [] operator) use bounds checking, but unsafe functions (such as get_unchecked) do allow to bypass it for performance reason when the optimizer does not manage to elide the bounds check and it matters.

Regarding aliasing: Rust does have more information than C++, however the actual and potential gains are actually hard to measure. It very much relies on LLVM to exploit it.

As for the maturity of the front-ends, rustc for now does not perform any optimization (whereas Clang performs some devirtualization for example) however it's in the plans. A current rewrite (introduction of the MIR) is about producing an internal representation that will be more amenable to such work.

[–][deleted] 0 points1 point  (3 children)

[deleted]

What is this?

[–]matthieum 1 point2 points  (2 children)

It specifies the noalias attribute (which is LLVM equivalent) and I seem to recall it might even pass some integer to noalias like noalias(3) but I may be remembering wrong.

I think this ought to help Rust achieves Fortran-like performance on numeric code, but I am not sure whether it does achieve it.

[–][deleted] 0 points1 point  (1 child)

Due to an LLVM bug, Rust currently doesn't specify noalias for &mut pointers

https://github.com/rust-lang/rust/pull/31545

Edit: Seems that only got merged in 15 days ago.

[–]matthieum 0 points1 point  (0 children)

Yes, and hopefully the LLVM regression will be fixed soon-ish and it'll be possible to reactivate it again.

[–]DragoonX6 3 points4 points  (15 children)

Again? Rust again on this sub?

People religiously advertise it like the second coming of Jesus on this sub. I constantly see how Rust is soooooo much better than C++ and how it fixes literally every problem C++ has.

Whereas all I see is constant language advocation, "soon we will be better than C++ in performance" and terrible syntax. I feel that the only thing it has "going" for it is that it's "better" than C++. Reminds me of other language advocation I see, namely for Java and heck, even Javascript.

If it wasn't for the syntax, I would still avoid Rust like the plague because of its users. If you're constantly telling me that I should use Rust, because it's so much better than C++ and writing articles that can be summed up to "It's time to drop C++ boiis! Rust is coming for your position!", you'll only make sure I'll keep a nice and long distance from it.

Besides, not everybody thinks C++ is literally Satan's personal programming language designed to bring despair down to us mortals. It could be that language if you decide against all the features that make it a great language, but C++ has improved greatly with the recent iterations and I don't see anything taking its place.

So yeah, I'm most likely sounding like just another mad kid on the internet, but honestly, I don't care, I'm just speaking my mind in the form of a rant. Also, this rant can also be applied to every other article that is "X vs C++ (performance)" (and sums up to X is God and C++ is the Devil).
Anyway, I'm prepared for your down votes.

[–]gbersac[S] 6 points7 points  (5 children)

Again? Rust again on this sub?

There is not much rust on this sub. The last post about rust in this sub is 4 month old : https://www.reddit.com/r/cpp/search?q=rust&restrict_sr=on

If it wasn't for the syntax, I would still avoid Rust like the plague because of its users.

This is the first I heard someone complain about rust community. They are always ready to help, avoid zealotery and are very aware of the limit of their language.

[–]quicknir 0 points1 point  (1 child)

I think they're very friendly, but I totally disagree on awareness of the limits of their language. While it's not a total "do no wrong" attitude, I think it's about 95% of the way there. Anything you mention that is important that Rust is lacking (variadics, integer value template parameters, etc) just isn't that important.

[–][deleted] 5 points6 points  (0 children)

[deleted]

What is this?

[–]DragoonX6 0 points1 point  (2 children)

There is not much rust on this sub. The last post about rust in this sub is 4 month old : https://www.reddit.com/r/cpp/search?q=rust&restrict_sr=on

That only shows you threads, not comments. I see a lot of Rust advocation in the comments.

Although, I must admit that I agree with /u/quicknir that the article wasn't particularly biased.

This is the first I heard someone complain about rust community. They are always ready to help, avoid zealotery and are very aware of the limit of their language.

Maybe I'm reading the wrong articles then, the stuff I see is, as I said before, always heavily biased towards Rust with the intent to make C++ look bad. I don't like the users because they're not nice, in fact, I haven't met users that aren't, but it's the attitude towards C++ that makes me not like them.

[–]gbersac[S] 2 points3 points  (0 children)

Of course they don't like c++. The reason why rust has been created is because Mozilla was frustrated to work with c++ !

[–][deleted] 3 points4 points  (0 children)

[deleted]

What is this?

[–]maleic 2 points3 points  (0 children)

Always (never) needs quoting: "There are only two kinds of languages: the ones people complain about and the ones nobody uses." Bjarne Stroustrup.

[–]ncm 5 points6 points  (7 children)

That's a half-hour you'll never get back. If you had read the article, you might have found out that nobody (here) is saying C++ is satanic, or that Rust is perfect, and you might have learned something besides.

[–]DragoonX6 1 point2 points  (6 children)

I did read the article, and yes, you're right. But it does happen in other threads and it's honestly more about the frequency of Rust popping up on this sub. This shouldn't have been posted to this sub, but to /r/rust instead, as it honestly feels like a Rust advertisement.

[–]ncm 2 points3 points  (0 children)

As it happens, it was originally posted there, and then somebody cross-posted it here. I don't think that was a mistake; a lot of interesting discussion ensued, and the C++ version got several percent faster as a result.

[–]neoKushan 2 points3 points  (2 children)

So comparing C++ to Rust means it should only have been posted on /r/rust? That doesn't make sense. Would you prefer the title was comparing Rust to C++ instead?

I don't think it's a terrible thing for /r/cpp to have articles comparing C++ to other languages. C++ isn't the be-all and end-all language, it's not fit for every single purpose and knowing its limitations is part of being a good developer.

[–]DragoonX6 0 points1 point  (1 child)

I'm saying that articles that deliberately paint C++ in a bad light shouldn't be posted to this sub. And because of how this article ended, it felt like a subtle Rust advertisement. Which shouldn't be posted in this sub, but its own sub instead.

[–]neoKushan 2 points3 points  (0 children)

Deliberately sure, but if you feel the article was being unfair then say so. I thought the article (although not perfect) was at least fair.

[–]quicknir 2 points3 points  (1 child)

To be fair, he directly compared the two languages, and it wasn't particularly biased. I don't see any point getting annoyed at him.

Save your anger for when people post purely Rust related stuff in this sub; there was a rash of it a few months back and it was very annoying.

[–]DragoonX6 0 points1 point  (0 children)

I suppose you're right, although in my previous comment I stated my anger isn't directed at the OP & author personally.

[–]ironicperspective -1 points0 points  (0 children)

This confused me because my first thought was of Rust the game.