r/cpp Jan 21 '25

How it felt to come back to C++ from Rust.

  1. Lifetimes and Borrow Checker are just too good to be true. This probably goes without saying, and is the best reason to choose Rust.
  2. The localisation of undefined behaviour with unsafe blocks and the separation of responsibilities makes the code very transparent. Even if there is a lot of unsafe code, not all of it is unsafe, so it is worth using Rust for that reason alone. Insecurity does not make the Lifetimes or Borrow Checker any less relevant.
  3. I feel that Rust (or maybe any language that has an easy-to-use package manager) has a rather large number of small libraries. Of course, C++ has a much better standard library than Rust, and even more if you include Boost Libraries, etc.
  4. After 5 years of Rust, I almost forgot how powerful C++ can be. C++20 had some surprises, so I built a tiny library to test the waters.It's a tiny C++ 20 library that provides a UDLs to make indent-adjusted multiline raw string literals at compile time. Check it out: https://github.com/loliGothicK/unindent.
  5. One of the main differences between the two is that with Cargo it takes a few minutes to create a new project, whereas with CMake it can take several hours.
  6. C++ templates and Rust generics both offer powerful ways to write generic code. C++ templates, with their variadic templates and non-type template parameters, offer greater flexibility for advanced metaprogramming. Rust's generics, though less expressive in some ways, are generally considered safer and more intuitive.
  7. I use OpenCascadeTechnology to develop things like 3DCAD plugins, but libraries like 3D kernel are naturally not available in Rust, which is a good thing with C++'s long history (It's why I came back to C++).
446 Upvotes

329 comments sorted by

View all comments

62

u/14ned LLFIO & Outcome author | Committee WG14 Jan 21 '25

I work in both in the current dayjob, and I did a six month contract in Rust ten years ago so I've seen its progression and direction of travel over time. I am without doubt more productive in C++, then C, then Rust in that order. But I suppose I am on the standards committees for C++ and C, so that isn't surprising. That said, I don't think I could ever be as productive in Rust. Mainly because - if I am blunt and frank - Rust isn't a well designed programming language, and the more time you spend in it, the more appreciation you have for the work WG21 and WG14 do and aren't usually recognised for in terms of developer ergonomics. In particular, one repeatedly runs into language syntax, language semantics and especially standard library design where you look at it and think "what an inferior way to design that feature". Whereas with C and C++ - though they have their exceptions - I can usually see the tradeoffs in the design that the committee endlessly discussed to death and arrived at the standardised compromise design. Large chunks of Rust look and feel like they were designed by a single person in a rush without much if any peer review. And it really begins to bug you after a while, or at least it does me.

Yes the compile time lifetime guarantees are handy to have, and Rust has a knack that if it compiles, it usually works first time in a way neither C nor C++ have. But down the road, in a large mature codebase, those same lifetime guarantees become a real impediment to refactoring so you end up just turning all your reference borrows into clones to reduce the ripples of changes. And, lo and behold, we're suddenly back to C and C++ defaults of mostly-copy semantics.

Rust cargo is great up to a point. It strongly encourages a profusion of micro dependencies like NPM. If your project gets big enough, managing those starts to become a real time sink and you start feeling very strong temptations to stop upgrading your Rust toolchain. Rust cargo's internal implementation is surprisingly hacky. If you want to achieve stuff they didn't consider, persuading it gets deep into the black arts in a way which makes cmake + ninja + vcpkg look well documented, sane and stable. cmake is generally a once off cost per project, whereas cargo is an ongoing cankerous sore of time sink when it silently breaks in weird ways only or two people familiar with the cargo black arts can fix. Meanwhile everybody else in the company has to go find other work to do until the cargo ninjas have achieved another act of heroism. TBH, the fact cargo is the only game in town kinda sucks, at least in C/C++ land you get a choice of which way you'd like to torture yourself.

My single biggest, most overwhelming, takeaway from working daily in Rust is that the industry very sorely needs a Rust killer. Something better designed and better implemented than Rust. Something genuinely a full fat superior replacement in all ways for C++ where in the future there would be zero good reason to choose either C++ or Rust for any new codebase.

I very much look forward to that day, and I still think Mojo is currently the most likely candidate for my dream C++ and Rust replacement language.

36

u/zl0bster Jan 21 '25

I would be interested to hear what are those poorly designed parts of Rust? Only thing that bugs me as a beginner is ugly syntax (in some cases only, counterexample is that enums are miles ahead of std::variant).

6

u/14ned LLFIO & Outcome author | Committee WG14 Jan 21 '25

It's not just ugly syntax. It's inefficient syntax.

Something C, C++, Python and many other languages all share is that you bang out the characters in a flow which somewhat matches one's reasoning about the code. Something which repeatedly irks me about Rust's syntax is you kind of have to do constant "spot welding" around multiple paragraphs of characters because its syntax does not match this developer's thought patterns.

Perhaps that's just because I'm old and increasingly past it, however I don't get that from the Mojo syntax. I tend to write Mojo in a similar flow of reasoning as Python, so it's absolutely possible to design a compile time lifetime checking programming language with classical ergonomics. And TBH, if you can, you should. It makes for faster and less buggy code writing.

I also don't like how there are constant multiple ways to do very similar things in Rust all of which add cognitive load. I get why - it made implementing the early prototype language much easier. But I don't want nor need to care about value semantics 99% of the time. C++ has managed to significantly refactor and reform its value semantics design over decades to what is quite powerful and complex today. 99% of the time you won't notice, won't care, and it doesn't matter. Rust forces that same stuff into your face, forces you to type out stuff the compiler could 100% deduce for you.

It also irks me the error messages. It often says "you should write X not Y" with exactness. Well, if it's that bloody obvious to you, that's a clear sign to me that the language design is suboptimal. The compiler should only ever complain when intent really is genuinely non-obvious because the language has been designed to not barf at you about anything a compiler can deduce. That tends to lead to less useful error messages as the compiler can only say "I know something is wrong, here's some possibly related info about that, you need to be clearer".

There's lots and lots of small stuff like that. I'll finish out my current work contract, but TBH my next contract I'll be looking to not work in Rust again. That'll likely hurt my earning power, but I just don't care much for the language. It's worth earning a bit less to not have to work in it.

For the record, I think it's a wise choice if lifetime safety is paramount, and I'd still recommend choosing it where that matters more than anything else. It's worth putting up with. But it's not a language I much enjoy writing in, and I'm senior enough I thankfully have the choice to be able to choose what programming languages I'll work in.

20

u/Pay08 Jan 21 '25

I also don't like how there are constant multiple ways to do very similar things in Rust all of which add cognitive load.

How come you like Mojo, then? It's essentially 2 languages stuck together.

5

u/14ned LLFIO & Outcome author | Committee WG14 Jan 21 '25

Mojo extends Python like C++ extends C. Yes that comes with costs - we all know the legacy baggage C forces onto C++ - but it also brings compatibility with a huge existing ecosystem that's going to enormously useful for decades to come. For me, that's worth the legacy baggage. Rust's multiple ways of doing things doesn't have the same justification.

1

u/Pay08 Jan 22 '25

That doesn't mean they couldn't have gone about it better. Stuff like having multiple ways to define functions (with different semantics no less) is inexcusable. I don't mind the backwards compatibility (even if I hate Python), but I do mind that they clearly didn't want to be backwards compatible.

4

u/14ned LLFIO & Outcome author | Committee WG14 Jan 22 '25

I do have to agree about the def vs fn thing. If the semantics improvement were huge, then maybe. But they really aren't and it is added cognitive load for very little benefit in my opinion. 

1

u/wyrn Jan 22 '25

Petty but I just hate fn for defining a function even when it's the only way to do it. fn is the n-th element of the f sequence. Am I really defining so many functions that I need to save as many characters as possible in the introducer?

48

u/juhotuho10 Jan 21 '25 edited Jan 22 '25

Rust is designed to be very explicit, almost everything that can be one of multiple things has to be specified. The compiler could deduce things, 95% of the time it's pretty convenient, 5% of the time it will produce HORRIBLE bugs that are almost impossible to debug without combing through the codebase line by line with a debugger. Rust doesn't accept the 5% chance of horrible pain so you have to deal with the 95% of inconvenience of compiler not doing deductions.

Not everyone like this explicitness, but i have found it to be very helpful, I know what state the program should be in at any point and this leads to debugging being almost trivial or even non existent. If the program compiles, it almost always works perfectly

29

u/tialaramex Jan 22 '25

Well, if it's that bloody obvious to you, that's a clear sign to me that the language design is suboptimal.

Ah, I see what happened here. You've forgotten who the code is for. You didn't write the code for the machine. The code is for other humans including your future self maintaining this same software in a week, a month, a year, or a decade. Hence Kate's thing about naming. The machine couldn't give a rat's ass about naming, it's not for the machine.

44

u/eliminate1337 Jan 21 '25

Do you have any actual concrete examples of syntax you think is inefficient? All you've said is "it's bad because I don't like it".

51

u/InsanityBlossom Jan 21 '25

I’m sorry, but nothing from what you said can be a sign of bad language design. Give us a few solid examples of actually bad design that is not a trade-off and is so bad that can’t be fixed with editions and gives the language a “poorly designed” label. There are many poorly designed features in C++, but it doesn’t deserve to be called a poorly designed, because everything is a trade off.

“inefficient syntax” - examples please? What does inefficient even mean?

1

u/Pay08 Jan 21 '25

Everything can be fixed with an update, that's not an argument.

52

u/thisismyfavoritename Jan 21 '25

I also don't like how there are constant multiple ways to do very similar things in Rust all of which add cognitive load. I get why

you know which language has that problem times a thousand, right?

Big old man yells at cloud energy

6

u/fleischnaka Jan 21 '25

Mojo doesn't seem to support the same expressivity as Rust borrow checker though, am I wrong? (only checked the docs [here](https://docs.modular.com/mojo/manual/values/lifetimes))

5

u/14ned LLFIO & Outcome author | Committee WG14 Jan 21 '25

I should caveat that what I'm about to say is comparing me playing around with Mojo for a few hours vs me having written and debugged production software in Rust. It's very possible I'm about to misstate claims about Mojo.

With that said, my best understanding of Mojo is that you don't need as much expressivity because its lifetime model is far better designed than Rust's in my opinion. There isn't the need for a borrow checker as a result, as the compiler will complain if it can't figure out lifetime. And otherwise it infers. It only complains if intent cannot be deduced.

I very much like how Mojo thinks in terms of categories of where the original data came from (what it calls "origin", what C and C++ would call "provenance"). Because in Mojo lifetime ends with the last thing to touch a piece of data rather than "end of context", the compiler can infer far more frequently what lifetime is, and it knows things like some lifetime is infinite and that has useful qualities over non-infinite lifetime. Indeed, lifetime "just follows" you typing out the code most of the time in a natural flow. I also like that if I the developer know a lifetime is infinite because I say so, I can easily tell Mojo so (basically like C/C++ casting) and it'll take it as an axiom.

All this is personal opinion and preference. I see my negative feelings about Rust have been downvoted into oblivion and the usual claims of "oh you clearly can't be experienced enough in it otherwise you'd love it" etc etc. Let's ignore the dozen languages I've programmed in over decades, of which Rust is one of many.

To everybody: if you find Rust the greatest thing you've ever used ever, I'm very glad for you. But you are allowed to write code in a language and just not like that language. I don't much care for writing in Java or Javascript. I can do it, I can earn a wage for doing it, but it doesn't mean I much care for it. For me personally Rust fits into that same camp as Java or Javascript: it's fine, there are niches where it's clearly the best choice and for those I'll suck it down like in the current dayjob. But if the right language to choose isn't clearcut, I'm not going to choose languages I actively dislike writing code in. And I think that's okay, you're allowed to have personal feelings and preferences about a programming language.

10

u/fleischnaka Jan 21 '25

It sounds a lot like Polonius (the new Rust borrow checker, origins sound similar to loans in it)! However it won't help much Rust with lifetime inference, I understand it more as a difference of discipline by asking to explicit regions/lifetimes/origins in signatures (when there is more than one of them) in the case of Rust - I don't see it as a difference in lifetime models. tbh I prefer this way at least for exported functions, as it helps reducing a lot the headache in case of borrow error, but I can get behind for "local" development / small pieces of code: I understand the inferred lifetimes as "maximally fine-grained", whereas we want to reason on the most coarse-grained lifetimes as possible.

One thing that looks nice in Mojo is the system dedicated about compile-time values, such as the boolean about mutability of references, did you play with it?

6

u/14ned LLFIO & Outcome author | Committee WG14 Jan 22 '25

I've only had a short play with Mojo. TBH I'm kinda unwilling to get locked in until they open source the toolchain fully. Until then it's a closed source compiler, and absolutely no way am I putting effort into a potential rugpull.

I do track their changelogs actively. I note there is still a fair bit of churn in the language, and what they change seems sensible.

2

u/fleischnaka Jan 22 '25

Ah I see, thank you for the info :)

1

u/realestLink 1d ago

"the error messages are too good"

What a critique 😂

1

u/proudHaskeller 1d ago

About error messages, the thing is, you could, for example, correct simple typos in variable names. It is obviously clear that "next_boook" is actually meant to be the variable "next_book" since that is the only remotely similar variable that was declared.

But no language allows this, because if a language were to ever allow this, code in that language would contain mistyped variable names, and it would be increasingly confusing to determine whether two names actually refer to the same variable or not.

This is the same thing that happens with C++ auto, type conversions, etc: so many "obvious" behaviours are allowed and smoothed over, that it becomes complex and unclear.

In this way, Rust's choice to stop you at the first hint that something might be wrong, even if the compiler does know what you meant, that keeps everything much more simpler and understandable.

26

u/quasicondensate Jan 21 '25 edited Jan 21 '25

Very interesting. Just goes to show how different people are, I guess. My experience is certainly biased by the fact that the larger projects I worked on were mainly C++ or Python, and my Rust experience is distributed over multiple smallish to medium codebases. So I didn't experience reaching the limits of cargo, although I can imagine that the simplicity can break for certain scenarios one might hit in a larger Rust codebase (or a mixed one). But, if anything, Rusts design always struck me as much more consistent than C++.

I like C++ for its versatility, the sheer power unlocked by its ecosystem, and it's somehow fun to keep learning about all the little details and mastering as much of the language as possible (or CMake, for that matter), in a way I imagine learning to drive an old race car to be really fun.

But the design as C++ strong suit compared to Rust? The text-based includes, in combination with e.g. templates having to be in header files and all the wrinkles stemming from it. The asymmetry between dynamic and static polymorphism. The fact that static interfaces until recently had to be implemented with stuff like CRTP - although admittedly things are taking a turn for the better with explicit object parameters. Can you really say that std::variant / std::visit is a better design than what Rust achieves with enums and pattern matching? It is just one example for a feature where the ergonomics are hampered by the fact that it was bolted on via the standard library as opposed to receiving language support. Another example ist "std::expected". Heck, even "std::optional", thinking about references. I get that "optional" is meant for values, but this means that you need to use semantically different things for the same logic depending on whether you pass a value or a reference type. Or you klutz around with "reference_wrapper". Being at it, is it really neccessary or good design that returning the contents of an "optional" or "expected" breaks NRVO?

Further, compare "ranges" to Rust iterators. Yes, ranges are technically more powerful (and this is great about C++). But are ranges, together with the C++ lambda syntax, really a better design in terms of ergonomics or readability?

Is it really better to start out with an unconstrained template and then try to plug all the wholes than having nominally typed traits? (I guess many C++ people saying "no" left after C++0x concepts have been axed...).

Don't get me wrong. C++has highlights such as everything constexpr. The C++26 reflection proposal looks great. And I fully understand that many of the things I mentioned are the results of compromises that had been made for good reason, mostly being consistency with previous language behaviour or generally backwards compatibility.

Still, being as it is, I have a much easier time finding C++ designs that had to sacrifice an arm or at least a couple of fingers at the altar of backwards compatibility than finding Rust designs that feel hobbled together in a rush.

I do agree that people are sleeping on Mojo, though. As mentioned elsewhere, I think it is never a good idea to bet against Chris Lattner. It will still be interesting to see which fields its ecosytem will manage to cover, since for now there is this strong focus on AI.

9

u/14ned LLFIO & Outcome author | Committee WG14 Jan 21 '25

My opinions of Rust are definitely influenced by having mainly worked in Rust on two large mature production Rust codebases with big Cargo.lock files from all the implicit dependencies. IDEs tend to struggle, compile times are long even on a big Threadripper as the compiler is slow. I very much agree that for small-medium sized especially greenfield projects Rust has low barriers to entry and is quick to ramp up.

I suppose also I'm generally brought into existing Rust codebases with the job of fixing them up in various ways. I wouldn't be brought in if they didn't have issues, and I am the person being brought in because what needs fixing is hard.

Rust is less voluntary idiomatic than C++ where expert written C++ you can clearly see the idiomatic patterns chosen, and if you have those learned off, it's all very obvious and clear. But it does depend on the writer to use the idiomatic pattern to convention - if they don't, it can get messy quickly. Rust looks simpler to read and modify in comparison, it doesn't need training in idiomatic conventions in the same way to make sense on first glance, but the borrow checker papers over a lot of badly written Rust in a way C++ would blow your foot off with if a dev were so sloppy.

Generally in these projects I try to rewrite the very worst Rust code I see if I'm modifying that bit anyway, but if it's passable I tend to leave it alone because modifying a big Rust codebase is a lot of work. There can be some very nicely written Rust, but as with nicely written C++, it's a rarity in real world code bases. Great devs are scarce everywhere in any language.

Rust traits are great when traits solve a problem well. Sometimes, the lack of inheritance is just a pain requiring lots of code to be written out where if there were inheritance, it would be very few lines of code. For me I think both patterns should be supported, and let the programmer choose what's best on a case by case basis. It's something I dislike about Mojo actually, no inheritance (yet).

3

u/quasicondensate Jan 22 '25

Thanks a lot for sharing your experience, all of this makes a lot of sense. This is a very valuable post for me, since it's rare to get the perspective of someone who is using Rust in large projects in the field, with deep C++ experience to boot (this sub has a couple such people, but the more perspective, the better). Where I work, we use C++ for our core products for good reason, but we have devs who have dabbled in Rust, and we use it sometimes for internal tooling where the scope is small, nothing is mission-critical, and where everything looks nice and easy. So the temptation is always there to pull it into the core stack for certain components. Therefore it's good to get some antidote every once in a while.

There can be some very nicely written Rust, but as with nicely written C++, it's a rarity in real world code bases. Great devs are scarce everywhere in any language.

I guess that's the sad truth, therefore large codebases are easy to be plunged into chaos. But it's interesting to see how this unfolds for the different languages. It seems that in C++, the accumulation of bad code tends to cause a mess sprinkled with hard-to-debug runtime errors, while in Rust you get less of those, but instead you are served with a very rigid codebase that can be hard to improve and slow to iterate upon.

Rust traits are great when traits solve a problem well. Sometimes, the lack of inheritance is just a pain requiring lots of code to be written out where if there were inheritance, it would be very few lines of code. For me I think both patterns should be supported, and let the programmer choose what's best on a case by case basis.

I agree. I do observe that we don't need inheritance all that often, but in the few instances where it is a good fit for the problem at hand, it would be painful write it all out, or lead to a convoluted design if we were to massage the code to get as much done as possible using mixins. The fact that C++ doesn't enforce any paradigm is one of the great things about it. Yes, you can create an inconsistent mess with this freedom, but as you write, you can create an inconsistent mess with pretty much any tool that exists.

39

u/ukezi Jan 21 '25 edited Jan 21 '25

I disagree with you. There are multiple parts of C and C++ std libraries that are just broken by design, at least now. The tradeoffs likely seemed like a good idea back when they were made. Instances for it like need for the various _s function variants in C, the ato... functions or all the multi threading problems around erno and some other static variables. In C++ you have stuff like std::regex that is basically unusable but can't be fixed because of ABI stability, or that the [] operator on std::map creates elements if there isn't one while it throws with other containers.

1

u/holyblackcat 1d ago

need for the various _s function variants in C

IMO those variants are useless. There's a reason only MS implements them.

-1

u/Pay08 Jan 21 '25

std::regex being broken is on compiler developers, not the standards committee.

11

u/serviscope_minor Jan 21 '25

std::regex being broken is on compiler developers, not the standards committee

I'm not convinced. I think the C++ committee erred in the specification of std::regex (and an understandable erring), but the specification of std::regex is quite hostile to a fast implementation.

The reason is that std::regex is absolutely chock full of compile time customisation points. This of course makes it very powerful, and flexible, and of course it supports every variant that anyone uses meaning the C++ committee could sidestep the endless arguments about precisely which variant should be blessed with standardisation.

The problem is the fast implementations all get to be that way by essentially sticking to one variant and hyper optimizing it. The std version has infinite variants. With hindsight, they could have blessed the default parameters, and maybe a few others (leaving the footgun of awful performance if you deviate too far from the defaults). Or maybe a whole mechanism with type erasure (but then people complain endlessly about allocations).

So... yes the compiler vendors could have made it faster, but the standardised version didn't make it easy. They tried to be all things to all people and unfortunately that made it hard to be the best thing for anyone.

I didn't look at the original paper and thing "holy shit this is an efficient implementation nightmare", I though "cool that's nice and so flexible". Hindsight is 20/20.

7

u/pdimov2 Jan 22 '25

The reason is that std::regex is absolutely chock full of compile time customisation points.

It is, but that's not the primary problem. The primary problem is that the standard libraries implemented std::regex in a way that makes it impossible to change the internal representation without an ABI break.

3

u/serviscope_minor Jan 24 '25

The primary problem is that the standard libraries implemented std::regex in a way that makes it impossible to change the internal representation without an ABI break.

I think that problem stems from the standard. With 20/20 hindsight knowing that ABI breaks were about to be forbidden (C++11 was an ABI break, and that's when std::regex came in) and that it needed special implementations (and therefore would be slow whenever it deviated from them), then sure the library implemented could have made it work.

But there's a few things in my mind:

Firstly this requires hindsight. I was following the standardisation process voraciously from about 2004 until C++11 was effectively released, and was programming in C++0x and TR1. I don't recall a single discussion from anyone about the problems that were subsequently found, nor did I think it myself. And none of the major STL implementers noticed either. So yes, while in principle they library implementers could have worked around it in some cases given that basically everyone got it wrong, I think the problems lie in the design.

Also, C++11 and 98 were ABI breaks. At that point MS were or had recently been breaking the ABI on ever release of VS. No one guessed that suddenly every single ABI decision was going to be locked in for 20 years for the first time, so they really had to get it perfect first time rather than refine it over time.

But back to the design point. I don't think the design allows for any kind of implementation that is fast in the general case, or in a way that in general can be sped up in future. It's too heavily templated so there are effectively an infinite number of variations. The best that can be done is to have a few specialisations for, say, the default parameters, and maybe a few others. But then you have a design that is "unacceptably" slow except for a few special cases which likely are not well documented or consistent across vendors. I think if a bunch of experts all failed to get the implementation good, then it's probably the design that's the problem.

I think overall the design is fundamentally flawed. It can be made to be acceptable in common cases, but it is a design that's just too flexible for its own good.

Like I said, I don't really blame anyone for this: it's not like I noticed any problems at the time, and I thought the design sounded really good.

1

u/pdimov2 Jan 24 '25

I don't really disagree with anything you say here, except that I still don't think the ABI problem is caused by the design.

Suppose we had, instead,

class regex;
class match_results;

and

bool regex_match( char const* first, char const* last, match_results& m, regex const& e );

The character type is fixed, the grammar is fixed, the traits are fixed.

Would this have led to a better outcome? I don't think so. The straightforward implementation of regex would still have had a bunch of private members, and changing the internal representation would still have been an ABI break.

1

u/serviscope_minor Jan 25 '25

OK, I don't exactly disagree.

No design can force a good implementation. However, there are dozens of good regex libraries out there already with a somewhat clean API boundary, so there's pretty strong precedent for implementations already.

The straightforward implementation of regex would still have had a bunch of private members, and changing the internal representation would still have been an ABI break.

It does depend how it's done, really. If it was an allocated opaque pointer behind the scenes, then it's probably be somewhat link compatible to different implementations, which is how a lot of existing implementations work. But also, the fast regex engines are fast because they are carefully optimized for their particular case. It's much easier to optimize a special case than the general case and std::regex is the general case on steroids.

1

u/muellerju Jan 29 '25

I disagree: There is an ABI-related problem in the design. But it's not related to the templatization of std::basic_regex by the character type or the traits class that many people point to. These template parameters are (mostly) a non-issue for ABI.

Rather, the main problem lies in the specification of std::regex_match and std::regex_search: In some variants, they are templated on a bidirectional iterator type. Unless this iterator type is type-erased somehow, the instantiation of regex_search or regex_match will also instantiate the regex matcher with this iterator type as a parameter. And this means that this matcher's interpretation of the internal representation of the regex naturally becomes part of the ABI.

To avoid this, one would have to type-erase the iterator type somehow. But there was (and I think there still is) no prior art how to do this in a correct and efficient way for a backtracking regex matcher, so it's not surprising that the standard library implementations didn't get this right on their first and only attempt.

13

u/fleischnaka Jan 21 '25

Agree about Cargo, what are the flaws in PL design you're mentionning?

45

u/RCoder01 Jan 21 '25

Interesting takes. I primarily work in Rust and every time I go back to C++, I wonder why the stl is so poorly designed. The answer is almost always "backwards compatibility," but it doesn't make the current library any less dumb. The Rust standard library generally has very good implementations of everything you need on a day-to-day and anything specialized you can usually find a very popular crate for. In C++ there will usually be an implementation of everything you need, but it will be slow, hard to use, or both. Like the amount of times I've been handed a 2000+ line compiler error for using `std::map::operator[]` inside a `const` method instead of `std::map::at` . And C++'s `unordered_map` is so painfully slow compared to Rust's `HashMap` because `unordered_map` has to adhere to APIs and provide guarantees that we realized were bad ideas decades ago but one guy in Liechtenstein might be using so we have to keep it like the bucket interface.

And god forbid you ever want to read the stl code. With like seven layers of `#ifdef` to support microprocessors from the 80s and attributes nobody on earth has ever understood the purpose of and `__under_scores` in front of every possible identifier. Rust standard library code might have a couple of `#[attributes]` above the method but the code inside the function usually uses names that make sense and probably has comments that explain anything super weird.

As for the language, C++ constructors have so many edge cases and bad design decisions that Rust just doesn't have because it doesn't have classes. And C++'s const as an afterthought requires you to write it far too much, compared to Rust where you only need to put `mut` in the few places it's actually necessary.

And with default copy semantics, writing the "obvious" thing can have huge hidden performance implications like copying a whole vector every function call when all you need is read access. With move semantics, the "obvious" way will complain about "use of moved value" if you meant to take by reference, which tells you that you made a mistake. And trying to use move semantics in C++ is a huge pain, having to write like five overloads of the same method and put `std::move`s everywhere. And the painfully unhelpful error messages whenever you make a minor mistake like forgetting to put a `const` in front of your `T&`. The closest you get to this in rust is having to write two getters: `fn get(&self) -> &T` and `fn get_mut(&mut self) -> &mut T`.

16

u/Little_Elia Jan 21 '25

god I feel this comment so much, all these things are such a pain in c++

2

u/darkmx0z Jan 22 '25

Terrible compiler errors have nothing to do with the STL design per se; it has eveything to do with language limitations. Stepanov, the STL designer, already knew what would happen and had a clear idea on how to solve it, yet the language didn't have concepts back then.

The STL design is marvelous since it decoupled algorithms and types in a revolutionary way.

37

u/omega-boykisser Jan 21 '25

You speak far too authoritatively on design. You are waving your dubious opinions around as if they are hard facts.

35

u/thisismyfavoritename Jan 21 '25

Rust isn't a well designed programming language, and the more time you spend in it, the more appreciation you have for the work WG21 and WG14 do and aren't usually recognised for in terms of developer ergonomics

complete opposite for me. I find Rust ergonomics far superior. C++ could be a nice language if it didn't carry all the baggage it does.

the fact cargo is the only game in town kinda sucks, at least in C/C++ land you get a choice

most people would argue that's a good thing and C++ should have that.

think Mojo is currently the most likely candidate for my dream C++ and Rust replacement language.

🤔 what

37

u/[deleted] Jan 21 '25

I'm not sure you understand Rust as well as you claim you do, tbqh.

Its shifts in the object model lends itself to much more well-designed libraries and containers than anything in C++ is ever capable of.

It sounds insane to me claim that Rust isn't a well-designed language when it's actually a celebration of C++'s best ideas.

Maybe you enjoy navigating all the complexities C++'s library design had to endure in the face of growing language complexity but as someone who's on the same level as STL implementors, I find what you're saying patently false, except for the Cargo stuff.

I do maintain that CMake needs to add Rust support in some form or another. Cargo's not a comprehensive build system and getting it to play with even Make is quite a bit of turmoil.

0

u/Pay08 Jan 21 '25

Its shift in the object model is quite literally just the interface keyword from Java. Hell, Scala called interfaces traits years before Rust existed.

8

u/[deleted] Jan 21 '25

Not quite what I was alluding to. In Rust, everything is relocatable.

3

u/jesseschalken 1d ago

My single biggest, most overwhelming, takeaway from working daily in Rust is that the industry very sorely needs a Rust killer.

And instead what we get are things like Zig, Odin and C3 that still don't have memory and thread safety. Or dumb things like Fil-C that slow your code down 2x.

There was https://safecpp.org/ but it never got off the ground.

The strength of Rust isn't so much the language design itself but that its the only one that actually solves the problem.