As someone who tried learning C++ a few years ago this is (personally) my biggest gripe with the language and this gif[0] perfectly sums it up. Gave up and started learning Rust instead, which I'm very happy about in hindsight.
Fortunately, those can almost all be ignored almost all the time.
Rust will seem fine until you are writing a library and find no way to express what would make the library nicer to use; or you find using some library awkward and error-prone. Almost every C++ feature is designed to enable delivering more powerful libraries, and they compound.
So, in C++ you as library writer are empowered to make the use of your library correct by construction: what compiles is safe and correct. Rust makes the compiler responsible for memory safety, but offers much less to make using your library pleasant and foolproof.
I think Rust's combination of data-bearing enums and destructive move semantics goes a really long way towards letting library authors express their invariants through the type system. And that's in addition to the borrow checker and mutable aliasing rules. The mutable aliasing rules in particular are often kind of annoying for callers, but they're a godsend for API authors, because they allow for methods like Mutex::get_mut that don't even need to lock the Mutex, in safe code!
> but offers much less to make using your library pleasant and foolproof
Not my experience so far, I might go as far and say it makes it more foolproof because when writing your library you have the option to return an `Option` or `Result` enum.
Option and Result types are as easily coded in C++.
The former is in the Standard C++ library. The latter will be in the next, but anyway the one in Boost has worked fine forever.
Throwing, instead, is a choice unavailable to Rust coders, so you often have no alternative but to return these more complicated things that users are then obliged to unpack. A standard macro makes that easier, and is semantically equivalent to throwing, but imposes substantial overhead on successful calls.
The fact that `std::optional<T&>` is verbotten makes them quite a bit more useless. The Boost one is better because it doesn't have this misguided limitation. It's really quite a sad story and a loss to all of C++ that it is this way (and not for lack of trying on ThePhD's part).
> no alternative but to return these more complicated things that users are then obliged to unpack
Yes, thank you. It is much better this way. I think "substantial overhead" needs to be backed up with some numbers here because setting up exception landing pads is certainly not free.
According to this link (posted below and includes benchmark results and a benchmark you can run), they compared result style error handling with conventional exceptions in C++, and the result style error handling (using an impl of std::expected) was "more than four times slower". According to the link and my understanding, exceptions are essentially "free" at runtime in the non-exceptional case on certain platforms such as Linux, but slower than result style error handling in the exceptional case.
I prefer result style error handling personally and I am not suggesting that it's worse or shouldn't be used, just thought the findings were interesting and worth considering.
So I did some testing with Rust and I don't think that the C++ "four times slower" applies to it at first glance. Maybe Rust can just do better optimizations or something because Rust knows that it is a discriminated union, but these kinds of conclusions can't just be ported across languages blindly.
Of course, porting the benchmark for `sqrt` wasn't trivial because (of course) C++ just modifies the span passed in making it easy to abstract out the allocations whereas Rust says "you cannot pass a mutable slice across a `catch_unwind` barrier", so all of my tests end up returning a new `Vec` inside the benchmarked function. I also wonder how much using an inner function for `fib` would allow some TCO to kick in and change results again.
In any case, I don't think that C++'s has anything that satisfies the use cases that Rust's `Option` or `Result` cover in its standard library, so even if `std::optional` and `std::expected` were magically faster, the fact that `std::optional<inner_field_type const&>` is not supported means I can't use it for what I want anyways and I'm back to Boost or hand-coding classes and dealing with corner cases myself. Which is, unfortunately, a C++-shaped problem that has existed for a long time anyways, so…nothing new.
I agree that "four times slower" may not apply in Rust specifically.
I do think the idea that exceptions are faster at runtime (at least in micro benchmarks) can be ported across languages though just due to the way exceptions are implemented. In any language, a caller of a function that can return a Result or Option always need to branch to unwrap the returned value, but callers of functions that can throw don't have these branches at all (on platforms with "zero cost" exceptions). Whether or not it actually matters in practice is certainly debatable. I would guess that it will never be a real problem for most people.
Wrt to std::optional and std::expected, I also have found them to be pretty clunky and limited, especially compared to the Rust counterparts! The lack of pattern matching is one issue, the lack of optional references is another, and they just don't integrate with the language as well since most libraries and even std functions don't use them!
C++'s option and result types are much larger, uglier, and harder to use correctly than Rust's. The shift from throwing to returning a Result is not really an increase in complexity- closer to a reshuffling of syntax.
But that's not really all that relevant either, because comparing languages at this level of detail misses the forest for the trees. An interesting comparison takes a step back and compares which problems the languages (try to) solve. Often something that appears to be a clear win for one or the other turns out to be a non-issue.
> C++'s option and result types are much larger, uglier, and harder to use correctly than Rust's.
What's your rationale for claiming in a swiping generalization that implementing a data type, regardless of all options or approaches and design, has no other option than doing everything wrong?
C++'s standard library optional type is a tagged union implemented from scratch, with a dizzying array of template metaprogramming to meet the standard's requirements- see e.g. Microsoft's here: https://github.com/microsoft/STL/blob/main/stl/inc/optional#.... This isn't a criticism of the team behind this code, it's just what it takes to do this in C++.
Rust gets most of that functionality from the language instead, in a much cleaner way. Sum types are built in, value categories and move semantics are handled automatically, there is little-to-no metaprogramming, and most of the API is just simple convenience combinators with obvious implementations: https://github.com/rust-lang/rust/blob/master/library/core/s...
If nothing else, this is a clear counterexample to the claim that "Option and Result types are as easily coded in C++."
Last time I looked at Rust's std::Result, it's implemented as a tagged union.
Most of C++'s implementations of a Result data type are implemented as tagged unions as well.
What's your point?
> This isn't a criticism of the team behind this code, it's just what it takes to do this in C++.
The only conceivable argument you can possibly make is argue that Rust might support tagged unions as language primitives, but that would be totally pointless as Result types are relevant for the interfaces and higher level abstraction they provide, not your personal opinion of how hard someone else had to work to implement them.
I've developed Result and Either types in a few languages, including C++, and the only hard thing about C++ is doing the usual homework to handle lvalur/revalue/revalue well.
The std::optional in MSVC was implemented in a language considerably behind current C++, for obvious reasons. The need to use template metaprogramming in implementations is much less, today.
How so? Some of the "enable_if"s can probably be replaced with "requires" syntax, as well as the "conjunction_v"s and "disjunction_v"s, but the majority of the complexity seems to be in expressing the various requirements imposed by the Standard. Are there recently-added concepts (or other features) that would express them more naturally?
> So, in C++ you as library writer are empowered to make the use of your library correct by construction: what compiles is safe and correct
Empowered? Sure. But it seems no one likes using that power for long stretches of time. Rust gives the same power and it gets used far more usefully IME.
> Almost every C++ feature is designed to enable delivering more powerful libraries, and they compound.
Well, we can take `std::variant`, `std::optional`, and `std::expected` out of that pool because the committee hamstrung them from their better alternatives that already existed in Boost for silly, misguided reasons.
> Rust makes the compiler responsible for memory safety, but offers much less to make using your library pleasant and foolproof.
C++ libraries have, historically IME, been far more likely to ask you to juggle live grenades while dancing in a minefield than Rust libraries and APIs have.
It’s all fun and games until you find the need to unsafe Rust, which opens up a bunch of eldritch monstrosities that are sometimes even harder to tame than C++.
The happiness people get from Rust was achieved by the incredibly hard work of library developers who had to deal with all sorts of complex semantics under the hood to make safe Rust safe. The knowledge gap between library user and library writer is even worse than C++, where most people do not feel comfortable enough to actually write low-level libraries (custom data structures, bindings from C, optimized routines) in Rust.
(At least PL people are inventing things like stacked borrows to make writing unsafe Rust easier… but it’s still not easy.)
on the other hand, that makes Rust's learning curve way way more gradual than C++.
An intermediate programmer with 3 months of Rust experience can reliably and confidently cobble up the libraries together to contribute to the project. Whereas such programmer will be a heavy burden (for other seasoned devs to guide/review/feedback) in a C++ project.
Also, building low-level libraries that work correctly, efficiently, relatively-safely in C++ is NOT a simple feat, and requires years of experience (on API design). With unsafe Rust you can just fuzz/brute force out the unsoundness with the safe API.
> It’s all fun and games until you find the need to unsafe Rust, which opens up a bunch of eldritch monstrosities that are sometimes even harder to tame than C++
Lol what? Unsafe is literally the only "mode" in C++. I'm not trying to shill Rust, but this is not true, and I don't know where you've read that opinion. Unsafe Rust is just Rust with the ability to use "C++ style" pointers.
> The happiness people get from Rust was achieved by the incredibly hard work of library developers
I'll give you that writing Rust libraries is harder, but that's true for any language. Even C++. Do Rust developers go the extra mile to make libraries ergonomic and have nice APIs? Yes! But that's not really a "problem" with Rust, it's something Rust enables, and now the community expects. C++ libraries in my experience tend to be a very bare minimum, and to use some of them it requires hundreds of lines of set up code. In cases where Rust libs require that much set up code, the authors go the extra mile to create macros or additional succinct APIs. So, yeah, their job is harder because they are held to a higher standard than C++ libraries, if anything.
Also, most of the effort with authoring Rust libraries is writing documentation, which is practically a requirement, whereas with most C++ libs (even some sections of boost!) you practically weep tears of joy when you see five lines written above a one class in a sea of classes.
> who had to deal with all sorts of complex semantics under the hood to make safe Rust safe. The knowledge gap between library user and library writer is even worse than C++, where most people do not feel comfortable enough to actually write low-level libraries (custom data structures, bindings from C, optimized routines) in Rust.
Sorry to be so blunt and call you out, but this is just so devoid of any sensible thought.
Most people don't feel comfortable writing low level libraries. Period. Regardless of language. For every library author, there's dozens of not hundreds of non-libray developers. Hell, most developers don't even contribute anything to open source in general, but just consume it.
So pretending C++ is this magical fairytale land where everyone produces libraries with the flick of a wrist is just bullshit.
Which is why, those of use that want some Rust like confort, when C++ is part of the diet, turn on bounds checking (#define _ITERATOR_DEBUG_LEVEL 1 on VC++), and make use of static analysis during the whole development (/analyze on VC++).
It is bullet proof? No, but it does help surving a couple of shots.
The C++ Language book. I enjoyed it up to the first half, but then was deer in headlights for the rest. Maybe it felt like all the rules were too big to fit in my head, but it personally felt like an overwhelming language.
they are doing OK, but much worse than the private Snowflake, in terms of multiples and growth rate. FOSS is too easy to replicate, so Mongo is most likely earning a lot less than AWS and other cloud vendors from their forks of Mongo.
I have been using the snap versions of Chromium, Discord, CLion and Spotify for a while now and I haven't noticed any slowness whatsoever. Sure the applications need some seconds (at most 5) to start up on a freshly booted machine but other than that it's pretty snappy (heh). The only issue I have encountered so far is that some applications don't respect the environments cursor theme (looking at you Spotify and Postman) but that's easily fixable by the package maintainers. Other than that I really don't understand the hate-train for snap. Coming from Arch Linux, PPAs seem like a PITA to me and an elegant solution such as the AUR doesn't exist in the Ubuntu ecosystem so snap / flatpak are the next closest thing to it.
Most applications on Macs start up instantaneously these days. 5 seconds is 50 times the limit for "instantaneous" feel. [1]
All of the raging discussions in this thread would be totally absent if Canonical had taken the time to make apps installed with snaps fast. Unfortunately, these days, the "make it work, make it right, make it fast" mantra seems to stop at the "make it work". At least the 20.04 release seems to be at that stage.
Congratulations, you just got a bunch of users who are going to avoid updates even more because you are going to make everything slower with your shiny new release.
No, most apps are slow the first time they are run on Mac (not as bad as first run of snap though, usually). After that, pretty fast. AppStore apps seem to always be fast.
I got sick of it after six years or so and moved to Linux Mint. (This was before Manjaro was widely visible.) Been on Mint ever since: it's a better Ubuntu than Ubuntu, and a better Windows than Windows (for ordinary uses).
Note that Mint 20, although based on Ubuntu 2004, has removed snap from the base install. `apt install chromium-browser' takes you to a web page explaining why.