Hacker News new | past | comments | ask | show | jobs | submit login

For a really unpopular opinion:

I prefer modern C++20/23 to Rust.

For 98% of tasks Rust is incredible. But modeling those 2% of problems that don't fit neatly into Rusts domain, the level of "unsafe", "PhantomData", and advanced type-system hacks you need feels obscene when I can use some pointers in C++.

Rust also does not have a compelling platform/story baked into the std for low-level asynchronous programming.

High level Mutexes and locks are there, but you're in for a world of hurt if you want to do something like sharded/striped locking, where the actual data being protected isn't directly owned by the lock (see Tokio's "OwnedMutexGuard" for this, thanks to Alex Chi and Victor Yang for telling me about this)




For an even more unpopular opinion:

I find the syntactic verbosity of Rust absolutely appalling:

fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {}

Surely this monstrous code has advantages over C(++) but in no world would I voluntarily write code in this language if other options were viable. I don't get how people prefer this language outside of a very narrow application context where the lasts millisecond of optimization matters.


It's easier to read if you split it into tokens and qualifiers. The & is a reference. The '_ is a lifetime parameter. The things in the <>are types. So this is a function which doesn't mutate self and takes a mutable reference to a formatter with a sensible lifetime and returns a result which will contain either nothing (or a unit) or an error if things fail.

Thank you for posting this. I had to google and read up some things.


I think most of the gratuitous verbosity comes from qualifying smt::fmt::Error. If you import it, it becomes

    fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), FmtError> {}
But the common practice is to have an alias Result<T> to mean Result<T, whateverErrorWeAreDealingWith>. This doesn't really maps well to doing impls in the Debug trait, since this trait uses an error that is generally different from the error defined by your own application, but with some care, it can be used. Then it becomes:

    fn fmt(&self, _: &mut Formatter<'_>) -> Result<()> {}
Okay, but what about the whole rest? it's syntactically heavy, but each part of it is very significant.

Take the <'_>. It means that the Formatter struct receives a lifetime parameter but you opted to explictly omit it. In previous Rust editions, you would keep it implicit

    fn fmt(&self, _: &mut Formatter) -> Result<()> {}
But this was confusing and ambiguous! People looked at it and sometimes didn't know there is a lifetime involved - until it showed up in compiler error messages!

Now, there's two elided lifetimes in this snippet: one in the &self and one in the &mut. Rust has clear rules for this case: your snippet means

    fn fmt<'a, b>(&'a self, _: &'b mut Formatter<'_>) -> ...
Once you learn the rules (and here are the rules https://doc.rust-lang.org/nomicon/lifetime-elision.html), this makes the syntax slightly lighter. It's not an issue like the other case because if you know the rule, it's not ambiguous.

The _ in place of the variable name is there just to not give a name to the formatter. It's actually a catch-all pattern (like one in the end of a match). If you wanted to give a name but disable the unused lint, you would write _formatter instead.

The rest, I can't really remove much sacrificing the semantics. The only superfluous thing I love is the ->, which is much better than the C convention of putting the return type before the function name (IMO).

The <> syntax itself for generic parameters sucks, and it's about the worst thing that Rust inherited from the C++ syntax. The :: also sucks, and it's also from C++ (the turbofish ::<> combines both, and it was Rust's brainchild. https://turbo.fish/ [0]) Actually most C++ influences on the Rust syntax is a disaster. I would much prefer to have a Haskell-inspired syntax for types, so that the type Result<A, Box<C>> would be written Result A (Box C)

[0] there were multiple attempts to remove the turbofish from the syntax. this thread has a discussion of it https://news.ycombinator.com/item?id=26019053 - also see https://www.reddit.com/r/ProgrammingLanguages/comments/fe3yg...


> The only superfluous thing I love is the ->, which is much better than the C convention of putting the return type before the function name (IMO).

For what it's worth, C++11 supports that syntax:

    auto thing(int a, bool b) -> string;


If the auto isn't the return value (supposedly string is).. then what does it means?


I've found that a lot of folks writing Rust don't seem to grok lifetimes, especially the rules around elided lifetimes.

I see a lot of manually-written elided lifetimes.


Except there is one thing C++ might never get, security culture.

It is already better than C, as WG21 at least acknowledges there are some security issues and has taken multiple approaches to try to fix some of the underlying issues.

However the way defaults are managed, versus the compiler provided frameworks from pre-C++98, are completely the opposite that security minded developers would wish for.

The NSA cybersecurity bill seems to finally have hit home that this needs to change, however it remains to be seen if those at ISO that care about it will be able ot steer the ship around with their paper proposals like P2657R1, P2723R0,P2687R0.

Bjarne keeps adocating the adoption of Core Guidelines and static analyzers improvements, which in a world where clang struggles to catch up, seems to have quite low priority in tooling improvements.

In a way I guess it depends how relevant many key C++ frameworks and workloads (HPC/HFT/games/LLVM/GCC/FPGA/GUI) will evolve and what Rust can manage to take over.


Yes.

Any system like Rust will have corner cases where it is awful.

The simplest example is doubly linked lists, specifically, and any circular data structure in general.

They are hard in any language to get right, but for all its sweat and pain Rust is no help here. C++ is a better choice.


I will openly say I think Rust is an all-around better language. If you are building general-purpose systems software, or just want to write fast software, use Rust.

If you are fiddling with bits, doing low-level concurrency, writing a GC/Memory Manager etc, or have self-referential datastructures like graphs or trees, you're going to have an order of magnitude easier time writing it in C++. (Rust-experts excepted)

Also, the developer experience writing C++ is worse than Rust by miles. Though the tooling (fuzzing, LSP, debugging, sanitizers, etc) is stellar, likewise with Rust.


I agree with this, but also sadly note structures like trees are sadly quite common, in the type of code I write anyway. It is a bit of a niggle I have with Rust.


> sadly note structures like trees are sadly quite common, in the type of code I write anyway. It is a bit of a niggle I have with Rust.

Trees are no problem with Rust

It is circular, rather than recursive, data structures that are troublesome


I had to use recursion to build a tree in AoC '22 day 7. I needed to have both a mutable stack of current tree nodes (`cwd` if you like) and the tree structure. Or equivalently a doubly-linked tree, where nodes point at their parent. It was a bit ugly.

In C++ you just write it.

I get why it's hard in Rust, just saying, it really does crop up.


Yes.

It is hard on garbage collection too, circular structures.


Try the Rust development experience in Visual Studio or Clion versus C++, specially for stuff like GUI, DirectX or parallel debugging.


CLion is quite good you're right. I think it's the best of any Jetbrains IDE

Visual Studio is also impressive, though I have a bit of bias against it from when it was so slow.

(I know it's not the case now. It has a great debugging experience, that I don't think can be rivalled anywhere else.)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: