Hacker News new | ask | show | jobs
by dureuill 722 days ago
This feels very shortsighted to me.

"Easy to learn" and "easy to hire for" are an advantage in the first few weeks. Besides, we now have data indicating that ramp up time in Rust is not longer than in other languages.

On the other hand, serving millions of users with a language that isn't even v1 doesn't seem very reasonable. The advantages of a language that is memory safe in practice and also heavily favors correctness in general boosts productivity tenfold in the long term.

I'm speaking from experience, I switched from C++ to Rust professionally and I'm still not over how more productive and "lovable" the language in general is. A language like zig isn't bringing much to the table in comparison (in particular with the user-hurting decisions around "all warnings are errors, period")

2 comments

> The advantages of a language that is memory safe in practice and also heavily favors correctness in general boosts productivity tenfold in the long term

As someone who has some large projects in C++ and co tribute to OSS C++ projects I find this isn't true. The big "productivity" boost I saw when using rust for some projects was that there were good rust wrappers for common libraries or they were rewritten in rust.

In C++ using the C API directly is "good enough" but because there is no nice wrapper development is slower than it should be and writing the wrapper would be significantly slower unless you expect it to be a decades long project.

When I'm not needing to build my own abstractions above a C library in C++ I find it just as productive as rust and the moment I need to touch a C library in Rust I feel even less productive than C++.

There is definitely an argument to be made about correctness in large teams being beneficial in the long run but clearly very large projects are able to keep some sanity around keeping developers in check but this is the one metric where rust has a leg up on C++ and big backers of C++ agree that it's the place C++ is sorely lacking. Every other metric isn't worth discussing unless this is fixed.

> As someone who has some large projects in C++ and co tribute to OSS C++ projects I find this isn't true.

Well, that goes contrary to my personal experience (professional dev in C++11 and up for a decade), and also to the data recently shared by Google[1] ("Rust teams are twice as productive as C++ teams"). Either your Rust is slower than average, or your C++ is faster than average. Perhaps both.

The reasons for being more productive are easy to understand. Build system and easiness to tap into the ecosystem are good reasons, but tend to diminish as the project matures. However, the comparative lack of boilerplate (compare specializing std to add a hash implementation in C++, and deriving it in Rust, rule of five, header maintenance and so on), proper sum types (let's don't talk about std::variant :(), exhaustive pattern matching, exhaustive destructuring and restructuring makes for much easier maintenance, so much that I think it tends to an order of magnitude more productivity as the project matures. On the ecosystem side, the easy access to an ecosystem wide serialization framework is also very useful. The absence of UB makes for simpler reviews.

[1]: https://www.reddit.com/r/rust/comments/1bpwmud/media_lars_be...

May also be the type of projects I would reach for C++ in as well.

I generally use golang for anything "high" level. I reach for C++ when I actually need that level of control and to be honest most of the ergonomic features in Rust are much higher level than what is needed for proper systems development.

I think my big productivity issue with rust has always been the very weird hoops I need to jump through to make it do stuff I can confirm is correct but the borrow checker prevents me from doing. I can imagine many use cases where Rust would be significantly more productive than C++ but those are places I wouldn't use C++ for in the first place.

Regarding serde, yes it's amazing but also blows up compile times, I know that's rich coming from the C++ camp, but realistically it's not great. I also find rust-analyser painfully slow but that's equally true with clangd except not for speed but more that clangd still doesn't support modules 4 years after they were standardised...

There are many issues with C++, but the reality is there are many issues with any given language and the tradeoffs I need to make with C++ feel better to me than the rust tradeoffs.

And regarding the google report, was that not self reported productivity. Also on a much smaller codebase? I did say for extremely large codebases rust has some very clear advantages and even strong supporters of C++ will agree there ( see any modern Herb Sutter talk, Microsoft, or the reports from Google) but I'm pretty sure we have learnt the lesson that what works for Google or Microsoft or Meta may not work for everyone.

Just make an informed decision is my point, you have tradeoffs for each laguage and for me easy C interop is extremely important for the places I actually need C++. For the rest I use golang.

> The absence of UB makes for simpler reviews

Rust also has UB and you should still be runnig fuzzers and sanitizers on your rust code, that is true for C++. Yes rust reviews are easier, but there are tools available that should be run on CI that can catch those issues, likewise with coding standards. It's not the perfect solution but it's the one we have.

> Rust also has UB and you should still be runnig fuzzers and sanitizers on your rust code, that is true for C++.

Safe Rust doesn't have UB[1], and safe Rust is what I review 99% of the time. For unsafe modules, you should indeed be running sanitizers. Fuzzers are always good, they are also interesting for other properties than UB.

> tools available that should be run on CI that can catch those issues

Available tools have both false positives and false negatives. Careful review is unfortunately the best tool we had in C++ to nip UB in the bud, IME.

> I think my big productivity issue with rust has always been the very weird hoops I need to jump through to make it do stuff I can confirm is correct but the borrow checker prevents me from doing

Interesting, I remember having to adapt some idioms around caching and storing iterators in particular, but very quickly I felt like there wasn't that many hoops and they weren't that weird. There's a sore point for "view types" (think parsed data) that are hard to bundle with the owning data (I have my own crate to do so[2]), but other than that I can't really think of anything. Do you mind sharing some of the patterns you find are difficult in Rust but should work, in your opinion?

> [rust-analyzer and clangd]

I find there's been tons of regressions in usage in rust-analyzer recently, but IME it blows clangd out of the water. The fact that Rust has a much saner compilation model is a large contributing factor, as well as the defacto standard build system with nice properties for analysis.

clangd never properly worked on our project due to our use of ExternalProject for dependencies.

> And regarding the google report, was that not self reported productivity.

No, the recent report (presented by Lars at some Rust conf) is distinct of the blog article and is not self reported productivity. They measured the time taken to perform "similar tasks", which google is uniquely positioned to do because it is such a large organization.

> Just make an informed decision is my point, you have tradeoffs for each laguage and for me easy C interop is extremely important for the places I actually need C++. For the rest I use golang.

That's fair. I would say the tradeoff goes very far in the Rust direction, but I have strong bias against golang (I find it verbose and inexpressive, I don't like that it allows data races[3])

[1]: to be precise, if safe Rust has UB it is a compiler bug or a bug in underlying unsafe code. By safe Rust, I mean modules that don't have `unsafe` in them.

[2]: https://github.com/dureuill/nolife

[3]: https://arxiv.org/abs/2204.00764

> Interesting, I remember having to adapt some idioms around caching and storing iterators in particular, but very quickly I felt like there wasn't that many hoops and they weren't that weird.

I generally didn't feel that way. I didn't need to change much of my use because O barely ran into the borrow checker. The time I did, was recursively accessing different parts of a pretty central struct but the borrow checker concidered the entire struct a borrowed object. Effectively I couldn't access multiple different fields of the struct if I have the larger struct already as a mutable borrow even though I only access one part of it. It was pretty trivial to confirm that the different functions in fact do not touch the same parts of the struct however the borrow checker simply could not do that. Maybe it's changed recently but I required an actual significant change to the code with several abstractions added when it really wasn't necessary.

I did actually agree with you regarding reviewing code to get rid of UB, I was just a little too verbose maybe. Regarding false positives and negatives with santizers and static analysis, I feel it's worth the pain for a language which I find more expressive for my use case, I'm not against usig rust, I'm against saying it's the universal solution to every problem needing to be solved when in my experience I haven't been able to reproduce that view. There are problems it solves well, I don't run into those often.

I'm also happy more data is coming out on productivity, I feel like it can only help light a fire under people on the standards committee who are indifferent to the issues of C++ to actually push to fix some of the issues which are currently solveable.

Ironically I see with your last point regarding golang that we are very different people ans thats fine. For me I would much rather lean back towards C if I can guarantee safety than the more abstract and high level rust. Honestly I am extremely intrigued by zig but until it's stable I'm not going near it.

We want different things from languages and that is fine.

> Ironically I see with your last point regarding golang that we are very different people ans thats fine. For me I would much rather lean back towards C if I can guarantee safety than the more abstract and high level rust. Honestly I am extremely intrigued by zig but until it's stable I'm not going near it. > > We want different things from languages and that is fine.

I just wanted to tell you that I agree. A lot of what makes people like or dislike a language seems to be down to aesthetics in its nobler meaning.

> The time I did, was recursively accessing different parts of a pretty central struct but the borrow checker concidered the entire struct a borrowed object.

Ah OK. It helps to model a borrow of a struct as a capability. If your struct is made of multiple "capabilities" that can be borrowed separately, then you better express that with a function that borrows the struct and return "view objects" representing the capabilities.

For instance, if you can `foo` and `bar` your struct at the same time, you can have a method:

`fn as_cap(&mut self) -> (Foo<'_>, Bar<'_>) { todo!() }`

and have the `Foo` borrow the fields you need to `foo()` from `self`, and `Bar` borrow the fields you need to `bar()` from `self`.

Then you simply can call `Foo.foo()` and `Bar.bar()`.

> The advantages of a language that is memory safe in practice and also heavily favors correctness in general boosts productivity tenfold in the long term.

Does it though? There are many languages that fit this description, you would choose Rust if for some reason you also need good performance. However, if you heavily interop with C/C++ safety goes out the window anyway, and it probably never mattered much in the first place.

> There are many languages that fit this description

I find that in practice not, especially if you further limit that to imperative languages. Note that I mentioned memory safe AND heavily favors correctness. In that regard, Rust is uniquely placed due to its shared XOR mutable paradigm. One has to look at functional languages that completely disable mutation to find comparable tools for correctness. Allegedly, they're more niche.

> However, if you heavily interop with C/C++ safety goes out the window anyway

I find this to be incorrect. The way you would do this is by (re)writing modules of the application in Rust. Firefox did that for its parallel CSS rendering engine. I did it for the software at my previous job. The software at my current job relies on a C database, we didn't have a memory safety issue in years (never had one since I joined, actually). We have abstracted talking to the DB with a mostly safe wrapper (there are some unsafe functions, but most of them are safe), the very big majority of our code is safe Rust.

> it probably never mattered much in the first place

It does matter. First, for security reasons. Second, because debugging memory issues is not fun and a waste of time when alternatives that fix this class of errors exist.

Memory safety isn't a defining feature of rust in any way and with very very simple rules in C++ (no raw loops and not raw pointer access) you can actually just get rid of memory issues in C++.

There are also pretty easy solutions to indexing issues (reading past the end of a array for instance) which you can use at compile time or just by enabling a compiler flag to have that at least hard crash instead of memory corruption. That takes a lot of RCE vulns to simply DOS vulns which is a significant increase in "security".

Memory safety isn't the reason to use Rust. That's already available in well written C++, and many other languages. Doing it easily with a large group of people I would say is an argument for using rust. But then you need to argue why not swift or java or kotlin or golang a or any of the crop of languages coming out that also offers easy to code memory safety.

> no raw loops and not raw pointer access

- Do these rules allow iterators?

- Under the "no raw pointer" rule, how do you express view objects? For instance, is `std::string_view` forbidden under your rules? If no, then you cannot get rid of memory issues in C++. If yes, then that's a fair bit more than "no raw pointer access", and then how do you take a slice of a string? deep copy? shared_ptr? Both of these solutions are bad for performance, they mean a lot of copies or having all objects reference-counted (which invites atomic increment/decrements overhead, cycles, etc). Compare to the `&str` that Rust affords you.

- What about multithreading? Is that forbidden as well? If it is allowed, what are the simple rules to avoid memory issues such as data races?

> That's already available in well written C++

Where are the projects in well-written C++ that don't have memory-safety CVEs?

You are actually just arguing for the sake of arguing here.

Rust bases all their data structures on pointers just like C++ does, just because you cannot look behind the curtian doesn't mean they aren't there with the same issues. Use the abstractions within the rules and you won't get issues, use compiler flags and analyzers on CI and you don't even need to remember the rules.

And of the billions of lines of code are you really going to try to argue you won't find a single project without a memory safety CVE? You will likely find more than there are rust projects in total, or are we going to shift the goalposts and say they have to be popular, then define popular and prove you won't have a memory safety issue in a similarly sized Rust project. Shift the goalposts again and say "in safe rust" but then why can I not say "in safe C++" and define safe C++ in whatever way I want since the "safe" implementation of rust is defined by the Rust compiler and not a standard or specification and can change from version to version.

I've agreed already that Rust has decent use cases and if you fall into them then and want to use Rust then use Rust. That doesn't mean rust is the only option or even the best one by some measure of best.

> You are actually just arguing for the sake of arguing here.

I'm very much not doing that.

I'm just really tired of reading claims that "C++ is actually safe if you follow these very very simple rules", and then the "simple rules" are either terrible for performance, not actually leading to memory safe code (often by ignoring facts of life like the practices of the standard library, iterator and reference invalidation, or the existence of multithreaded programming), or impossible to reliably follow in an actual codebase. Often all three of these, too.

I mean the most complete version of "just follow rules" is embodied by the C++ core guidelines[1], a 20k lines document of about 116k words, so I think we can drop the "very very simple" qualifier at this point. Many of these rules among the more important are not currently machine-enforceable, like for instance the rules around thread safety.

Meanwhile, the rules for Rust are:

1. Don't use `unsafe`

2. there is no rule #2

*That* is a very very simple rule. If you don't use unsafe, any memory safety issue you would have is not your responsibility, it is the compiler's or your dependency's. It is a radical departure from C++'s "blame the users" stance.

That stance is imposed by the fact that C++ simply doesn't have the tools, at the language level, to provide memory safety. It lacks:

- a borrow checker

- lifetime annotations

- Mutable XOR shared semantics

- the `Send`/`Sync` markers for thread-safety.

Barring the addition of each one of these ingredients, we're not going to see zero-overhead, parallel, memory-safe C++. Adding these is pretty much as big of a change to existing code as switching to Rust, at this point.

> Use the abstractions within the rules and you won't get issues, use compiler flags and analyzers on CI and you don't even need to remember the rules.

I want to see the abstractions, compiler flags and analyzers that will *reliably* find:

- use-after-free issues

- rarely occurring data races in multithreaded code

Use C++ if you want to, but please don't pretend that it is memory safe if following a set of simple rules. That is plain incorrect.

[1]: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines