There, I said it.
There, I said it.
* Rust has a package manager.
* Rust has macros.
* Rust makes it relatively simple to choose between compile-time and runtime polymorphism.
* Rust's traits can be more flexible than C++'s classes.
Rust feels like a modern language. Modern C++ feels like it's struggling under the massive weight of decades of cruft.
* While not perfect, C++ has a few package manager that are quite solid (personal favorite being conan).
* What can Rust macros do that C++ TMP (and macros even though that's a last resort IMHO...) can't do?
* From what I've gathered, it's similar in complexity to using the CRTP in C++, maybe a bit less verbose. Am I missing something?
* I feel like "interfaces" as in pure abstract virtual classes can achieve the same effect as Rust traits (though it could be its own thing and not a special case of a class specification), and the newly introduced Concepts goes even further that what can be achieved.
Don't get me wrong, I like the improvements rust brought, especially in terms of safety, but other than that... I don't think C++ is particularly far, or even actually behind for some of these.
A better comparison to Rust macros might be constexpr and template meta-programming.
I'm not sure why you specifically compare Rust macros with C++ TMP. Rust macros are meant to be a safer and more flexible alternative to C/C++ macros, not else. C++ TMP is not a goal but a means to the goal, so if you look for something like TMP in Rust you will see nothing (the closest is generics, but of course it doesn't subsume TMP because it needn't). The goal of TMP is generically a compile-time metaprogramming and Rust has multiple solutions for this, including but not limited to generics, Cargo-directed code generation (via `build.rs`), macros by examples (`macro_rules!`) and procedural macros.
> From what I've gathered, it's similar in complexity to using the CRTP in C++, maybe a bit less verbose. Am I missing something?
CRTP (curiously recurring template pattern) is really aptly named and it is a testament to C++'s accidental complexity. I don't think any new C++ user can discover CRTP in reasonable time. In Rust traits are built-in and well incorporated into the core language.
> I feel like "interfaces" as in pure abstract virtual classes can achieve the same effect as Rust traits (though it could be its own thing and not a special case of a class specification), and the newly introduced Concepts goes even further that what can be achieved.
You have a generally right idea about traits that it is conceptually similar to concepts, but I believe there is currently no equivalent of `dyn Trait` for concepts. There was no effort to harmonize concepts and existing virtual classes.
Also your parenthesized remark is more important than you imagine; `dyn Trait` in Rust is implemented as a fat pointer, which decouples vtables from instances and is generally much less complex than C++ vtables which have to account for multiple inheritance for example. As a C++ programmer you may feel like that you want a choice between vtable and virtual methods vs. no vtable and no virtual methods, but it is a false dichotomy and Rust gives the best of both "choices".
I feel like the problem is that it isn't just a package manager... it insists on simultaneously being not only "a" but "the" build tooling, which is trading way too much flexibility for a very small amount of convenience. And like, OK: maybe this is useful for people who are just getting started, but I would firmly argue that this kind of tooling should consider the needs of the expert and not make severe tradeoffs for the needs of the beginner unless it is trivially and obviously optional and there can be growth through changes in tooling (where maybe people "graduate" from Cargo to a more extensible tool... but since it is also the package manager, that's brutal).
The issues for the tool are then riddled with bugs that have been open for years (I think some of the ones that I've been blocking on might already be at 5 years, which is shocking to me as I don't even think of it being very old) that even often have multiple reasonable pull requests filed but where no progress ever gets made on fixing the problems because, even though it is often trivially shown that there is no way to use the tools correctly currently in the general case, they insist that the behavior of a working tool might break some theoretical user relying on the current inconsistent set of behaviors.
The result of all of this is that there are a ton of workarounds for Cargo that projects have started to have to embed almost as boilerplate into their projects, including a popular one that is going around right now (required for Linux->Linux cross) involving setting an environment variable that explicitly tells the user not to set it ever in the name of the variable but like... Cargo doesn't be have any other way of making this stuff work correctly. It is honestly infuriating and ridiculous and there are good reason why you keep seeing stuff like Google avoiding it entirely as part of their Bazel integration.
> setting an environment variable that explicitly tells the user not to set it ever in the name of the variable but like... Cargo doesn't be have any other way of making this stuff work correctly.
This has nothing to do with Cargo, if you're talking about RUSTC_BOOTSTRAP. This is because the kernel would like to use a stable compiler, but unstable features. This isn't a supported build configuration; the environment variable is an escape hatch for the bootstrap process, and isn't intended for nothing more. It would be, imho, unreasonable to expect Cargo to support something that is explicitly not intended to be supported.
I'm also not fond of making the package manager also kinda the build system, but that's just my preference to keep it flexible.
Linking to a library from another language is rather considered an 'edge/complex case' in most circumstances outside of systems programming and the lib having a C API. And that case is literally three lines of Rust in build.rs if you use e.g. bindgen.
While Rust is a systems programming language the primary use cases, judging from the ecosystem of crates, are actually /not/ drivers or desktop apps. The latter are an 'edge/complex case' that just happens to be also nicely covered.
And not doing that (i.e. operating solely inside the language ecosystem) is what most users of such ecosystem would reserve the term 'basic' for.
To understand where I'm coming from: would you consider 'linking' to a Python or Erlang libray from C++ 'basic' or an 'edged/complex case'?
* do not exist yet
* struggle to provide full bindings
* do not keep up
* have stopped being updated
One of the most glaring example for me is Assimp, which still does not have its Rust counterpart (and unsurprisingly, because Assimp has a weird interface to begin with).
I didn't stumble upon bindgen, but from what I gather it's for library developers, not consumers, right?
> the primary use cases, judging from the ecosystem of crates, are actually /not/ drivers or desktop apps
Well, in that case (but not aiming specifically at you) why do I see so much claims that "Rust is the new C++"?
> would you consider 'linking' to a Python or Erlang libray from C++ 'basic' or an 'edged/complex case'?
I would consider it complex, but this is wildly different IMHO than Rust with C or C++ (but mostly C to keep it sane), which, while more "complex" than pure Rust, still falls within the reasonable expectations. The fact that there are so many crates wrapping around C (and C++ to some extent) libraries means that for the most part, that "complexity" has been offset by crates developers, but many Rust use-cases still seem to require a lot of C/C++ code to even exist (even if abstracted away by glue crates).
Rust has two kinds of macro with very different properties, so lets talk about that briefly (reader: if you are here to learn about this C++ Proposal paper then er, bye?)
1. The macro_rules! "By example" macros. These are a bit like the simpler C macros you might have used, except that they really care about syntax and aren't just gluing text together without understanding it. These macros are hygienic (if a macro uses its own variable named "count" and your function has a variable named "count" Rust knows those are different variables and everything works fine whereas with a C or C++ macro that will cause trouble) and they're fairly powerful for solving those annoying "Don't Repeat Yourself" problems in programming when you can't quite factor out the repetition in the Rust code per se.
These macros are very safe, I would trust a novice to write and use them, worst case maybe you need to show the novice how to ask Rust to apply the macro and show the results so they can see what they screwed up, because e.g. "suck it and see" doesn't work very well to figure out what you did wrong writing a macro.
2. Procedural ("proc") macros. There are several specific flavours of proc macro but they're all Rust code, which you wrote, running inside the compiler during the build, taking bits of the program syntax tree as input parameters.
This can do anything. For example Mara wrote whichever_compiles! a proc macro which forks the compiler, attempting each of the options you supplied, once one of them compiles the other compilers die and the survivor presses on compiling the rest of your program. It's deeply regrettable that this is possible, but it is. Only experts should touch proc macros, and they should obviously not do anything crazy unless (like Mara) they are doing so as a joke.
So, proc macros can do a lot more than C++. Perhaps more than they should do, but more.
> I feel like "interfaces" as in pure abstract virtual classes can achieve the same effect as Rust traits
C++ traits are roughly in the same space as C++ 0x Concepts. You might think aha, we have C++ 20 Concepts. Unfortunately C++ 20 Concepts is a pale shadow.
Traits does a few important things (possible to some extent in C++ 0x Concepts) which C++ 20 Concepts can't do. In particular, it has semantic weight because the concepts must be explicitly implemented. In C++ two concepts with the same syntactic requirement are functionally identical. The ISO document says using a concept which is not correctly "modelled" means your program is ill-defined (might do anything) but in practice the code compiles and everything seems OK. In Rust even though there's no syntactic difference between PartialEq and Eq, the types which don't explicitly implement Eq are not Eq. That is, you can programmatically ask, "Is a == b an equivalence relation?" whereas in C++ all you can actually ask is "Can I write a == b ?"
More directly practically, Rust actually checks Traits whereas in C++ there is no checking.
e.g. if you try to sum() a Vec of u32s that will work, but if you try to do that to a Vec of Strings there is no such function, the Sum Trait isn't implemented for Strings.
You could do this in C++ but it's much easier not to, and so most software doesn't and then your users are fed a lot of SFINAE compiler errors which choke them to death. In Rust it's mandatory to spell out the required Traits.
Most of Rust's traits are safe, which means you can't get them wrong but then on the other hand I can't rely on their meaning beyond syntax. Unsafe traits (which need the unsafe keyword to implement) can make promises and if they break the promise that's Undefined Behaviour.
For example Rust's iterators provide a hint about how big they are. But the TrustedLen unsafe trait on an iterator says its hint isn't just a hint, this actually promises it is true, if it's not true then it's OK if the program crashes. So for example an array's provided iterator is TrustedLen, the array of six things knows it has six things in it, that's what an array of six things is, duh. So code using that iterator can correctly emit code to handle exactly six things, and it will work. Whereas my GoogleSearcher HTTP API might hint it has six things, but then actually Google only returned four, because Google, it isn't TrustedLen, so code might optimise for six based on the hint, but it mustn't crash because I didn't promise six it was only a hint. Emitting code that blows up if there aren't exactly six items is a bug.
- Long build times
- Overly complex language
- Ugly syntax
Rust inherited a lot of problems C++ had. While making some progress in areas such as security is a huge success already, the language has a lot of potential to do more.
Those are not really priorities of Rust or most of its domains anyway.
> The long build times are a product of the type safety mechanisms built in.
The long builds times are a product of the crate culture. Like JavaScript, many people are happy to pull in crates that are tiny to avoid having to write anything themself. So you end up with many packages pulling in over 100 crates.
The Rust compiler team is working on speeding up build times as we speak. Give it some time. If a language was perfect in other respects (e.g. free of memory leaks), I won't mind waiting a little longer for my builds, but I know this bothers many. In any case, it's being looked into.
The complexity argument I agree. Rust won't be the last language for that reason. Already it influences languages like Zig and Jakt, who aspire to be simpler and still suitable for systems programming.
I disagree re: ugly syntax, but that's a matter of taste.
Build times in C++ projects nowadays are mainly the result of naive approaches to project structure and total lack of care towards modularity. On top of that we also have helpers such as ccache and the like to further reduce build times by not having to build at all.
But yes, to the extent that C++ still had some niche before Rust matured, that last niche doesn't exist anymore. I disagree even that Rust was the largest C++ killer, but it seems to really be the last one.
Incidentally this is why there's basically no Rust in the Google monorepo. Rust might be slightly better than C++, but it's not a lot better, and bifurcating the entire ecosystem for something that is marginally better isn't worth it.
Please don't say rust++, I'm curious as to what the next thing will look like. Maybe I have to a bit longer
rust.iter().next();But people will need some time to create that.
I believe much of C++'s complexity does not come from its C heritage, but rather from having defined the primitives to control memory usage, object lifetime, and layout.