Hacker News new | past | comments | ask | show | jobs | submit login
The usability of advanced type systems: Rust as a case study (arxiv.org)
141 points by lykahb on Feb 22, 2023 | hide | past | favorite | 162 comments



> If we accept that Rust is indeed more difficult to learn than comparable systems programming languages

I do accept this, and I try to be honest about it when I'm shilling Rust :) But at the same time, I think it's worth pointing out that what we mean by "learn" can really depend on context. The subjective experience of learning a language looks something like "getting my homework done and turned in for a grade". And in that sense, for sure, learning Rust is harder than learning C. (I won't make a claim about C++.) But what percentage of people who have "learned" C can actually write UB-free C with any reliability? For the folks who really can write UB-free C, how many years did it take you to get to that point?


> If we accept that Rust is indeed more difficult to learn than comparable systems programming languages

My problem is with "comparable systems programming languages". To me the only thing that fits there today is C++ and while a great many programming languages would be easier to learn than Rust, C++ is not one of them by a long shot.

I think the C++ Quiz https://cppquiz.org/ and the Rust Quiz https://dtolnay.github.io/rust-quiz/ illustrate handily. Neither of these languages is a walk in the park, but, notice they both have "Undefined behaviour" as a possible answer? Safe Rust doesn't actually have undefined behaviour, so you get to rule out one of the possibilities any time you don't see the "unsafe" keyword, which is in fact every time on the Rust Quiz. In C++ some of the quiz questions invoke UB, but good luck correctly guessing which ones.


The difference that at same level of let's say "UB bugs in code", C++ code will compile, Rust will not. So person learning C++ will start producing code quicker even if their level of competence will produce more buggier code.


> will start producing code quicker

This will reinforce bad patterns early on which they will have to unlearn through bug hunting or reading about the edge cases and foot guns.


Or maybe it allows them to complete a fun side project quick enough that they keep their interest in programming altogether.


I’ll say that when I started working on finl a couple years ago, I started in C++ (employing 20+-year-old memories of the language). When I switched to Rust, starting from zero, I found myself instantly productive. Yes, there are some things that are harder, but in general, the if it compiles, it works rule holds for Rust. I can still remember some rather disturbing bugs that should never have even compiled in some of my early C code (e.g., treating argv values as if they were variables and could be modified in place).


> keep their interest in programming altogether

Python will do that too and without reinforcing patterns that produce UB.


Although Python is getting long a little in the tooth. It used to be a simple, easy to learn language, but who can honestly claim its async implementation is 'simple and easy to learn'?

I'm curious if there are any newer alternatives that fit in the same niche.


It still is a simple, easy to learn language. But now if you want to do more complex things with it (async), you can.

Of course, that means that it doesn't fit in that niche - it fills it and then some.


They didn't complete it though, if it doesn't work.


There's a wide gulf between "one-off afternoon-long project done for fun" and "serving all traffic for amazon.com during black friday", so I am receptive to the "I can just do this one off thing in Perl". On the other hand I know that once you get familiar with Rust it can be very productive (the "it hurts when I do that" stops when you learn to preemptively "don't do that" and stop fearing "this code is inefficient because it has two extra allocations!!!1") and I've seen too many "weekend projects" become the backbone of large enterprises. Rust's superpower is being able to scale up and down. But the GP might be talking about a project that falls squarely on the former. Anything that starts to move towards the later, I would immediately push for a language/ecosystem more tuned for "maintainability" than "time to first char on screen".


Indeed, I agree.


This doesn't make sense as a metric though.

The goal of software development is not to write code that compiles, it is to have an executing software system that does the right thing.


> The goal of software development is not to write code that compiles, it is to have an executing software system that does the right thing.

But there are many UB-laden, compilable C++ programs that produce the right result, they just are implemented with bad, non-standard, leaky code. Rust and other languages that detect this bad code and force you to remove it are harder to start with, because you need to learn everything to get to a running program.

Interactive development (especially REPLs!) have shown time and time again that it speeds up the learning process. Rust takes the inverse approach imo.


I use debuggers in languages like rust or go very similarly to how I use repls in languages like python. But I do think interpreted languages have a distinct advantage on this front! Which is why rust (or c++, or go) isn't the best tool for every job.


It's worth noting that safe Rust prevents two things. Please be patient with this explanation, as I'm about to butcher it badly (probably not using the right terms):

1) UB - this is when code is invoked. Basically memory mismanagement. We know the signs: segfaults, etc.

2) Unsound code - this is code that has the potential to trigger UB if called incorrectly.

I think this distinction is pretty important. It explains why small projects in c++ can get going so quickly, and they have issues when they grow. You can prevent unsound code from triggering UB, but that of course gets harder to do as a project gets bigger.


In the future as C++ evolves (it had a hiatus of a decade or more where not much happened) it will be interesting to see how it will compare to Rust.

C++ is performance first at any price. Hence UB.

Rust is also after performance, but will specify implementation details where C++ will defer to compiler writers.


> C++ is performance first at any price. Hence UB.

I don't think this is entirely correct, C++ in some cases famously preferred ABI compatibility over maximum performance.


C++ has no ABI defined. Did you mean API comparability? Would help to clarify some examples


The committee won't change something if the change would break the ABI of one of the big implementations. It's not a formalised ABI stability promise, it's just a result of implementers having veto power and wanting to keep their implementation's ABI stable.


While the C++ standard does not define any ABI, some implementations (namely GCC and clang) put a lot of effort into maintaining reasonable ABI compatibility. GCC has only changed ABI twice (afaik) and the second time the only thing that changed was std::string!

Nowadays MSVC tries to maintain ABI compatibility too, so we should stop seeing a proliferation of those msvcrXXX dlls.

Haiku uses C++ for its OS ABI so it relies quite heavily on the fact that ABI compatibility stays very consistent across compiler versions for example.


Same applies to Rust, with the difference that there isn't a story for shipping binary libraries, that is why OSes like Haiku care about keeping the ABI consistent, those BeOS applications aren't going to be recompiled.



Instead of breaking ABI compat for `unordered_map` why not define a new type given that it's so different? It can even be called the same thing and just exposed through a different namespace version (something that's been supported for a while). About the only thing real is intmax_t. The rest feel resolvable within a library without changing ABI / breaking back compat.


As anyone who has looked at disassembled C++ code can attest, "C++ is performance first at any price" aka "zero-cost abstraction" is mostly a pipe dream. Whatever sufficiently smart compiler was meant to make it happen has not materialized.


I don't think thats correct.. C++ has UB because of backwards compatibility, which has origins to design choices of C in the 70s. Its not like it is optimized for performance above all else, it had immediate needs to be close to metal, and that is the best they could do back then, there was not some "strongly typed" versus "performance" debate happening at the time.

The creator of C++ would love for there to be safer abstractions, but has the baggage of the past to carry.


Modern C++ is still a safety-third design.

std::optional is relatively new, and supports dereferencing, which when it’s empty is UB instead of an abort.

There are no plans to add checks to any existing APIs, because “it didn’t have a branch before” is also considered a compatibility issue.


> (it had a hiatus of a decade or more where not much happened)

C++11 is almost as old now as C++98 was when C++11 came out. It's evolved plenty.


Yes, to learn enough system programming to be able to contribute in large scale production project, Rust is massively easier than other options.

An intermediate programmer coming from other high level language can confidently contribute to a big project after ~2-3 months of learning Rust, that number is almost unimaginable for C/C++.


You can learn to use Rust in Go-like settings in under a week.

You can have your first Actix[1]/Sqlx website up in a day, and you can have it doing some non-trivial work -- including sharing state between threads -- by Friday if you start today.

Rust's type system alone is a compelling enough reason to try this. Cargo a close second.

I'm willing to bet a lot of people start using Rust for backend web development. It's quite pleasant.

[1] Or Axum


> I'm willing to bet a lot of people start using Rust for backend web development.

It will happen, but it's not something you should do today. The async ecosystem in Rust is not yet ready for prime time, there are key language and stdlib features that are just missing. And this in turn holds the web ecosystem back. If you write a web backend in Rust today, you might just do a lot of pointless work that you'll have to rewrite in the future anyway as more advanced frameworks come along and the earlier ones are practically abandoned.


My company runs everything on AWS lambda, I’ve worked on Lambda with Go, Python and Typescript before this, Rust is by far the best experience I’ve had. Async works great, it’s definitely a lot harder to figure out the ecosystem than something like Go, but there’s nothing missing that I know of?


async does not work great, maybe for simple poc but once you start diging it's not well done. The debugging part is even worse.


My best two takeaways for using AWS lambda:

1. Use Rust 2. Use containers

I save a lot of time with those two decisions. My lambdas are significantly less likely to have runtime errors. And with containers I can select which version to run from my registry


What are you talking about? Async is marvelous in its current state. It's not changing much at all.

I had to port async runtime APIs many moons ago and it was an easy migration. Now things are baked in and I haven't had to change anything for years.

> there are key language and stdlib features that are just missing.

Like what?

> If you write a web backend in Rust today, you might just do a lot of pointless work that you'll have to rewrite in the future anyway as more advanced frameworks come along and the earlier ones are practically abandoned.

1. If you write atop Tokio, you're fine. (You wouldn't pick anything else unless you have special case needs for your app.)

2. If you write atop Actix/Axum(Tokio), you're doubly fine -- they're abstracting away everything.


Take a look at https://rust-lang.github.io/wg-async/vision.html for the shiny things that are planned in future versions of Rust. This will most likely require brand-new frameworks and create a whole lot of pointless churn for pre-existing code.


> This will most likely require brand-new frameworks and create a whole lot of pointless churn for pre-existing code.

I doubt it. I suspect existing code will continue to work with minor mechanical changes, and the improvements will just open up new use cases that are either impossible or awkward to do today.


In the list of projects I don't see many that would actually materially change frameworks, beyond changes from -> Box<dyn Future> to -> impl Future or reduced boilerplate due to more concise syntax sugar.

https://rust-lang.github.io/wg-async/vision/roadmap.html


So don't use async. You can get a very, very long way with threads and good old fashioned blocking I/O.


Except that in practice every Rust I/O framework uses async these days. You can't really avoid it.


> You can learn to use Rust in Go-like settings in under a week.

How? I would love to give it a spin.


Start with the official Rust Book (https://doc.rust-lang.org/book/)

Then move onto Zero 2 Production In Rust (https://www.zero2prod.com)


I wish there were equivalent books to Zero 2 Production for other ecosystems. I'm working on a project with Sveltekit project right now, and a major frustration for me is the proliferation of choices that come from working in the TS/JS ecosystem. While those choices exist in Rust too, the Zero 2 Prod book at least presents a pathway where reasonable defaults are presented for everything from tracing to ORM and I don't have to review a million and one different options and figure out how to make them work. JS build systems are nightmarish to me and Python is only slightly less bad.

Edit to add: if there are recommendations for TS/JS front-end development, I'd love to see them.


I started with exercism for rust, and then Zero2Prod.

Through Zero2Prod, it's very nice to see how easy the rust ecosystem makes it to do "high level" web programming with minimal friction. The type system makes Domain modeling fantastic. Options and Results feels like the right way to do error handling, and the `?` operator cleans things up nicely.

I only wish that rust was something I could use at work (we're mainly a Go shop; not my favorite), but I suppose I'll have to wait for the right opportunity.


> You can learn to use Rust in Go-like settings in under a week.

Depends if one feels like trying their hand at writing asynchronous code.


Not so much. You cannot learn Rust to any useful degree in under a week, period. It's worth learning, but it's not that easy.

Async makes it even harder, yeah.

Though a lot of what I'd use async for in Python, I'll use Rayon for in Rust, and it's... fine, really. Better than fine even; it's easy to avoid concurrency issues that way.


> Not so much. You cannot learn Rust to any useful degree in under a week, period. It's worth learning, but it's not that easy.

Let's pick a competent engineer with zero Rust experience and teach them. I wager you $1,000 that they can write a TODO app in Rust/Actix without looking at a direct example.

I'll even up the wager and concede that they must have zero C/C++ experience. Only garbage collected languages under their belt.

I'm being a bit facetious here as I don't have the time to babysit this wager given my startup, but I really want to make this bet and I'm very comfortable in thinking that I would win it. (Maybe if I offer $1000 to the engineer too as incentive to try - that could work.)


I won’t take that bet; I agree that they could.

I just don’t think that counts as “learning rust”. Which means that now we’re arguing over semantics.


And even if you compare to languages that use garbage collection to prevent a lot of memory safety errors, rust's type system protects you from problems that occur there, like forgetting to close a file, or using a file after you close it, and many (but not all) race conditions with multiple threads.


Also lots of "spooky mutation at a distance" bugs. Anywhere you see languages like Java or Python using immutable data structures, that's a place where I think Rust's shared/mut distinction solves the same problem more cleanly. And to be fair to C and C++, they also do this pretty well, if you go through the trouble of making your code const-correct.


Not that many developers even try writing UB-free C (or C++), or want to. Almost always the UB works (might not be portable, might break with a future compiler...) but im those cases they just debug the issues (or blame the compiler or don't update after all). I guess for moat projects that approach is actually economical.


That's fine as long as you don't need to use the project for anything important.


I've never tested my hand with Rust, but I've come to appreciate reading its ecosystem of champions who seem to be, like this is hard. that's a good thing.


'hard' is not the goal, so much as "don't skip inherent complexity".

It turns out things were already hard, Rust just has the tools to make it tractable to deal with all of it instead of ignoring it.


> "don't skip inherent complexity"

...except when it comes to build and packaging problems.

The thing missing from this conversation is that we're talking about Rust the language, C the language, and C++ the language. In practical terms, the ways you go from "this is source code" to a running program matters a lot for end users.

Point being, so far the Rust community mostly camps out in cargo land and ignores the hard problems of how to get systems code into the various ecosystems that benefit from close-to-the-metal programming. Maybe some day it will be figured out, but it's essential complexity to the broad problem space.


That is, admittedly, something Rust has not prioritized. I think it was probably the right decision given how successful the cargo approach has been in terms of bootstrapping a language community.

There are some very talented people thinking about the interoperability story, but it is also hard so they haven't committed to a solution prematurely.


Can't you integrate rustc into any C or C++ build system?


In theory. In practice, there are a lot of complications to sort out, especially if you want to have Rust call C that calls Rust that calls C that calls C++. Build requirements of transitive dependencies need to be communicated and propagated, keeping in mind that C and C++ rarely uses cargo and at least public Rust crates rarely use anything else.


I wish that were true. If you dare mention finding anything hard in Rust circles, you'll get swamped with "no, you're wrong, it's dead easy!" responses (with the obvious implication about anyone who finds it hard).

More specific questions will often get extremely helpful and detailed responses, so I'm not remotely claiming the community is horrible. You just have to project a fair bit of "Shiny Happy Rust Person" to get on with it if you don't want to get bogged down with pimply arguments.


I suspect there are two factors at play: first, there are a lot of drive-by rust critics who spout of stuff they've read that plainly haven't really tried using rust themselves; second, I suspect that people want others to understand that while it might be challenging, it's not as hard as most people make it out to be and the payoff can be worth it. While you might find forward progress hard initially, you won't accidentally delete the universe in the process, and the compiler and rust community is quite helpful in getting you on course.

For me as someone who came from primarily working in Python, I really appreciate the strict type system that comes with Rust and have been quite productive in it, even if it feels like I've hit a wall at times. I think it'd be a shame for other people in my shoes to not even explore the possibility because they think it's only meant for a certain class of low-level C++-style programmer.


> there are a lot of drive-by rust critics who spout of stuff they've read that plainly haven't really tried using rust themselves;

I haven't come across this so much. More the kind of YA male philosophy undergrad types (of all ages & genders) which tech in general and Rust in particular seems to attract. These people just like to argue. A lot. In interminable screeds, often on the Rust user forum.

The attitude is certainly more loud than universal. Many of the most thoughtful (and helpful) Rust insiders recognise the language's difficulty. The Rust Foundation certainly does, to the point that making Rust more tractable to learners is one of its current stated goals.

> I think it'd be a shame for other people in my shoes to not even explore the possibility

Rust's current cultural kudos in dev circles is such that I don't believe this is really a factor. Many want to try it. It's more that Rust is just difficult for many to actually work with. I don't mean 'learn' in the absolute beginner sense. There's no shortage of excellent and generously accessible materials (the Rust Book amongst others). They're not that difficult to get through. But many find that once they've got a minor grasp on the whole language, it's just a real slog to get anything substantial done with it. Getting through all the Rustlings doesn't mean you can get anywhere near writing real software at a motivating pace. This is certainly my experience, and that of all the pro devs I personally know who have started to learn Rust, all of whom (other than me) have dropped out in frustration. All of whom (including me) have by the way learned and used many other languages without the same troubles.

Pretending (or passively-aggressively asserting in response, as so many do) that Rust is easy does nothing other than (truthfully or not) trumpet the abilities of the arguer.

My guess is there are many factors at play. Some of it may be attachment on the part of those who have clearly exerted great efforts to learn Rust, and perceive assertions that Rust is difficult to be negative criticism (often wrongly). Partly it's the argumentative immature adults that tech fields so often attract. We all know them. It's my guess that Rust, being more towards the techy end of that spectrum, does so particularly (I don't know if this is equally true for C/C++). Partly it might be Rust's self-conscious inclusiveness: a positive in itself, like many similar ideals it can lead to aggressive denial when more clung to as an identity than pursued on as an aspiration.

There you go. My interminable screed. Maybe I would fit into the Rust 'community' after all.


> It's more that Rust is just difficult for many to actually work with. I don't mean 'learn' in the absolute beginner sense. There's no shortage of excellent and generously accessible materials (the Rust Book amongst others). They're not that difficult to get through.

This is something that I noted when I first picked up the language: The rationale and explanations made sense, I could "learn" the rules, but in practice it was much harder, because I hadn't internalized the rules, nor fully grasped the consequences of those rules.

Having said that, I wish someone had told me ".clone() early, .clone() often" and "only return borrows if they are borrowing from an argument, otherwise use owned values" for the first 3 to 6 months. Having to learn the intricacies of borrowed values and a whole FP-inspired systems language at the same time is a lot.


> Having said that, I wish someone had told me...

Another rule of thumb I always try to emphasize for beginners:

Don't add lifetime parameters to your structs. Sometimes the compiler will suggest that you do it, but ignore those suggestions. Concretely, always make your structs hold String or Vec instead of slices. In 3-6 months you can ignore this rule.


> ".clone() early, .clone() often"

Make your types Copy. Don't even worry about cloning.

That said, there are few things you want to have lifetimes, and in that case you want to de-elide lifetimes. As practice.


I hear this a lot. But don't see that much evidence. Is it hard? Yes. Is it impossible? I came to Rust from Java and despite hurdles I learned it during it's most difficult part - before lifetime elision.


This is so close to a demonstration of my point that I'm tempted to believe it's satire.


It's not satire. It's not happy shiny Rust is perfect nor demonstration of it being easy. Despite what you want it to be.

I'm around average developer of 40ish years, that learned Rust when it was at its hardest.

And yes, going from Java to Rust was hard compared to C#. I needed months to get proficient, compared to mere hours in C#.

Honestly, learning Git was harder than Rust. That took 4-5 months to re-learn it proper and get rid of training wheels.

And it was as hard as learning Haskell/Prolog. Hell, I'm still horrible in Prolog. And people don't complain as much about those two.

People seem to forget that saying "A new programming language should completely change your mental model", otherwise you're learning a new dialect not a language.


> And people don't complain as much about those two

Why do you opt to label plain descriptions of Rust as being difficult as 'complaint'? There's nothing inherently wrong with something being difficult to learn. Au contraire, I would have thought, unless the difficulty is unnecessary, which isn't something I've seen levelled much against Rust. The Rust Foundation has 'Flattening the learning curve' [sic] on its 2024 roadmap: https://blog.rust-lang.org/inside-rust/2022/04/04/lang-roadm.... Is that a 'complaint', or just a reasonable response to an empirical reality?

The complaint, inasmuch as there is one, is against the loud subsection of the Rust community which swamps any reference to Rust's difficulty with "no it's dead easy! You're just wrong!" (they leave the 'and probably thick' as an obvious inference, stiffly maintaining the rictus).


> Why do you opt to label plain descriptions of Rust as being difficult as 'complaint'?

See article

> [Rust's Ownership and Lifetime rules] come with a steep learning curve and have been repeatedly cited as a barrier to adopting Rust

Complaint, in the sense as this is too hard (aka too foreign) for me to learn. Because it's subjective.

Is Norwegian language difficult to learn? For native speaker - no. For European, probably not that much. For a Chinese, probably yes.

For example Prolog is nigh impossible for me to learn and non-ironicaly harder than Rust.


It’s not that hard in the end, just up front. It has a learning cliff with a plateau at the top. When you get it you are like “wow so this is how you make all those threading and memory bugs go away forever with near zero overhead” and you are good to go.

You never go back. It’s nice writing code that just will not fail in certain ways.


> But what percentage of people who have "learned" C can actually write UB-free C with any reliability?

If your alternative is C and you really need the power of C and a guarantee of no UB, then by all means, (struggle to) learn Rust. But I would argue that limits Rust to very few niches of system/expert programming. The number of people who write these is very, very limited, hence the use of the language, tooling, libraries, etc.

The promise Rust makes nowadays is of an all around safe and productive language that can reward any effort spent fighting the type system and compiler with an error free execution.

For most programming tasks people use languages in the real world, I would say Rust fails to deliver on this, in the sense that the effort spent to master it and use it day to day only affords limited benefits. If you need to move fast and don't absolutely need the strongest reliability guarantees, then Rust is a tar-pit.


> If you need to move fast and don't absolutely need the strongest reliability guarantees, then Rust is a tar-pit.

That's true, but other type systems will likely fit your needs. For instance, if you're doing web, Elm's type system is head and shoulders above the more traditional options.


"Tar-pit" is overstating it significantly. It takes a paradigm shift, sure, but there are lots of options who feel Rust makes them more productive than other mainstream languages. A good type system can speed you up in some ways, too.


Can someone explain to me why this comment thread is so obsessed with UB? There is plenty of way to shoot yourself in the foot with well defined code in C and some undefined behaviours can be perfectly safe provided you know what the compiler you are targeting is doing.

Is it a new fad in the Rust crowd to confound UB with memory unsafe? Some of the use here seems completely random to me.


Yes, I would say even compared to something like Python it can be easier to "learn" depending on what exactly you count as "learnt" (e.g. the ability to write bug free code).

That said I think it's fair to assume your "getting my homework done" definition, in which case it is definitely harder.

So I would describe it as "harder to learn but faster to finish projects".


> For the folks who really can write UB-free C, how many years did it take you to get to that point?

There is an easier path: you can test your C or C++ stuff with valgrind and get reasonable certainty that it work.

However nowadays we are putting our trust in the borrow checker, and quietly ignore the fact that half of the rust stuff needs to use the 'unsafe' keyword.


>> half of the rust stuff needs to use the 'unsafe' keyword.

If this is true for the Rust code you are writing, either you are doing it wrong or perhaps you are writing lots of low-level embedded / drivers code?

Having written C and C++ for years and now learning Rust, I much prefer the safety and nice features of Rust. Coding in C and C++ now feels like driving in a car without a seatbelt: you can do it if you don't have other options, but if you do have the choice, why wouldn't you?


Even for low/level embedded drivers code, you don't use 50% unsafe.


In that case, it is being done wrong.

I haven't had to use unsafe Rust yet, but have had to do some squirrely C tricks in the past for really low-level stuff.


Both Rust and C/C++ have some important limitations when it comes to being categorically UB-free. Rust's limitations first:

- If there's `unsafe` code in your dependencies, that's potentially just as bad as `unsafe` code in your own project. So you do have to be careful with what you pull in.

- There are known "soundness holes" in the compiler, i.e. compiler bugs that let you corrupt memory without writing `unsafe`. However, the ones that remain are pretty obscure and very hard to hit accidentally.

Now C and C++'s limitations:

- Valgrind and all the sanitizers depend on test coverage. So 100% line coverage is table stakes for getting certainty out of these. And you probably also need to think about coverage of your dependencies.

- None of these tools catch the UB that results from violating the "strict aliasing" rules. I believe there's something called "TypeSan" that promises to improve this situation, but it's experimental?


For 'actual undefined behaviour' Clang's UBSAN is the better choice though (and for completeness: -Wall, -Wextra, --analyze or better clang-tidy and finally ASAN and TSAN).


I’m curious why we see so many papers and discussions about the difficulty in writing Rust relative to other languages. I would like to see more analyses of total cost of ownership.

Yes, Rust requires approaching writing code somewhat differently than most popular languages (different isn’t inherently bad), and it’s true borrow checker limitations cause it to reject code that is sound. But that increase in cost at the front end, in my experience, is significantly offset by the decrease in maintenance costs.

I’ve spent a non-trivial portion of my career hunting down and fixing memory corruption issues, race conditions, and other undefined behavior in extremely large codebases. Yes, we’ve seen great advances in tooling in Clang+LLVM, but writing that buggy C/C++ code is still quite easy (a point I didn’t see in the paper). Using validation tools to their full extent is costly from maintenance, configuration, and compute perspectives, and their correctness assessments are less strong than what an advanced type system provides.

Preventing such classes of bugs through an advanced type system seems like a better approach to reducing total cost of ownership because:

1. Some classes of (expensive to fix) bugs are eliminated by codifying system behavior into the type system. 2. The need for a variety of CI runs with Address Sanitizer, Thread Sanitizer, Undefined Behavior sanitizer, etc. and their associated corpus of coverage tests is greatly reduced because such issues are defined away by the language. This saves on CI infra costs and requires engineers to spend less time writing/maintaining tests to exercise the tools. 3. More engineers will spend more time moving the product forward and less time hunting down hard-to-reproduce bugs.


Because for most software 'good enough' is the best outcome.

Users don't care if you wrote it in Rust (unless the target audience is developers). All they care about is if it solves their actual problem: a real world, pain-in-the-neck problem.

If you take 20% longer to write it in Rust, that's short term cost for long term gain, but users don't think that way. Everyone from the users, sales, marketing, product and even engineering departments want the product out the door yesterday, so the pressure is always on.

Users don't care if you need 30% fewer CI servers. The CEO doesn't care if one additional dev has to spend his time making sure asan, ubsan, etc are running properly. This is cost factored into pricing. Coverage metrics are used as proxy metrics for quality.

Meanwhile the devs are wrangling the complexity of the software itself, which requires mental resources. Any additional complexity introduced by Rust slows down development and frustrates the team and stakeholders.

And the bugs are still there. So many bugs from devs who were sloppy or couldn't tame the complexity that +-20% due to Rust doesn't matter. The QA team still needs to exist and test the product thoroughly. The issues still come back and require fixing, regardless of language used or frequency of bad bugs.

In the end, this software process produces acceptable, good enough software that gets fixed, patched, updated, etc Users see improvements and progress.

Operating efficiency becomes important only after the product is out and stable, with paying customers and product-market fit, but by then, why boil the ocean with a rewrite? Sometimes leadership can be sold on a new and improved v2 (now in Rust), and sometimes they see it as a distraction from the current target (if it ain't broke, don't fix it, add more features instead).

That's why I think we need languages that make it less mentally taxing for developers to write product software (because the logistics are already complicated), while features like the borrow checker, though cool, increase the mental overhead.


> That's why I think we need languages that make it less mentally taxing for developers to write product software (because the logistics are already complicated), while features like the borrow checker, though cool, increase the mental overhead.

But all Rust do is expose those issues? For example, do you consider a type checker a burden or a help? Is being able to check at compile time that “hey, why are you trying to pass a nullable to a string? This don’t make sense” helpful? I cant count on how many times have saved my time debugging issues.

I agree that Rust is not for everyone. Not everyone will like types even if it good for them. I would argue that a language that can accept dev/random as source to be very popular especially because you can get started very easily even if that means you will spend the rest of your life debugging it.


It's not whether it's a burden or help, those words have connotations associated with them.

The type system is objectively overhead, because it forces you to think about the types in your program, so it consumes mental bandwidth.

You and I might decide the cost is well worth it, but you cannot ignore that there is a cost. In the real world, many companies didn't always want to incur that cost in the beginning, so they built their products using scripting languages, Python/PHP/Ruby, etc and only much later added types via annotations or features of the language. Even C++ recognizes that there is a sizable overhead and introduced auto types.

Your /dev/random example makes no sense because it would not generate a working program, so it would not be popular.

There is no 'good for you'. Is the type system good for you if it slows down your velocity? Are dynamic types good for you if you get an exception deep in the code one hour before launch because someone was adding a string to a number?

There are only trade-offs. You either make the right trade-off (even by luck) given some set of goals and limitations, or you don't and pay the price one way or the other.


>The type system is objectively overhead, because it forces you to think about the types in your program, so it consumes mental bandwidth.

I don't understand this point. Types is an overhead yes, but its an overhead that you _have_ to deal with. Would typing without knowing what letters would come up be a lesser burden? Seeing the code that I just wrote seems to be an overhead too. Types is simply there to tell you what you wrote. Much like when I realized I mistyped `j` instead of `k`, I would also realize that I am trying to append an `Object` to a `number`.

> Your /dev/random example makes no sense because it would not generate a working program, so it would not be popular.

My point is that a language that will literally, and I mean literally, accept any and all source code and execute them is probably going to be popular. Just imagine a language designed with this goal in mind; to make the set of possible program in the language be the entire space of possible sequences of texts. For example,

```

00000what????;

function hello() {

world

}

```

This seems meaningless. But with that mantra in mind we can simply define that any line that is undecipherable be a declaration to an unused string. Why not? So the first line is is simply something like `let _ = "00000what????";` And the second one is simply a function that returns the string `world`. Why? Oh because its so inconvenient to put quotations to declare a string isn't it?

I think you'd agree that this language is horrible. It might be faster to get to a prototype but I personally can't imagine maintaining a codebase written in this language.

Have you considered that this is exactly what JS is like (in spirit)? It defines so many things that would otherwise not be defined in other languages. For example, what does this even mean? Without looking at the spec, tell me what it does?

```

function hello() {

    1.0 + "hello"
}

console.log(hello())

```

Does this program even makes sense? Does this program makes any more sense than something like

```

"helloworld"();

```

Not even JS would compile this, but why not? Why not simply throw an exception here? Something like "function not found"? Since this clearly works

```

const a = {

    "hello": ()=>{console.log("world")}
}

a["hello"]()

```

The set of possible program is JS is much, much larger than any other languages. There's so many little tricks that the language provide that makes it so powerful and so much worse.


Rust can be argued to "expose" the issues compared to manual memory management, but not compared to automatic memory management.

(In the first case there's of course also the caveat of false positives from the borrow checker)


You hit the nail on the head with long term vs short term gain, but I think there is an additional factor to consider: software generally isn’t frozen at v1. You should also consider if using Rust reduces the toil of incremental changes to your software after its gold release.


I'm mentioning v2 towards the end (I did make a few edits so apologies if it was missing for you)


It’s always hard to convince management of the business value of a v2 rewrite, especially if v1 works “well enough” from the outside :( Sometimes it’s easier to just front load the cost to avoid significant churn later.


I agree with your point completely.

I also use a lot of these points when I explain why I use NodeJS and avoid typescript for most of my projects.

The thing is though, it really depends on the project.

Most of my projects are web UI heavy with simple commodity CRUD RESTful APIs sprinkled with business logic.

Time to market tends to be the biggest constraint (I live in startup world), so "good enough" performance and a loose type system are perfect for that.

If I was writing SCADA controllers for a nuclear power plant, my choice of technology would be much different.

The sorts of "rah rah" debates we have on HN about languages frequently don't take the specific project characteristics into account.

Like with everything, it boils down to intelligently selecting the right tool for the job.


> Users don't care if you wrote it in Rust

Totally agree! But they do care if Word corrupts a document that’s been open for 10 days (a bug that a principal engineer spent many, many weeks hunting down).

> If you take 20% longer to write it in Rust, that's short term cost for long term gain, but users don't think that way.

There are many steps between writing code and delivering a product to users. Sure, I can commit some C++ in less wall time than Rust (though I’m not actually sure that’s true!). But when we have to delay a major release by 2 weeks because there’s a heap corruption bug in the flagship feature, everyone loses.

> the pressure is always on

Sigh, yeah. It’s difficult for organizations to act in their own best interests. If a codebase primarily has architecture debt and little implementation quality debt, it’s much easier to keep taking the shortcuts to ship now. But with implementation quality debt, UB is lurking behind every corner and can cause the project to go off the rails at any time. This is why I would like to see more research about the value of Rust with respect to the predominant cost of engineering in extremely large systems: maintenance.

> Users don't care if you need 30% fewer CI servers

Sure, but DevOps does. When we have to drop jobs due to capacity constraints but mark the commit as stable anyway, it’s just a matter of time until a major regression sneaks in.

> The CEO doesn't care if one additional dev has to spend his time making sure asan, ubsan, etc are running properly

Haha, yeah, in practice it‘s always one dev maintaining the asan, ubsan, etc. loops! But, it’s up to engineers writing code to make sure it’s exercised by those systems, so in reality the loops are highly under utilized and eventually hard-to-find UB bug slips in.

> Meanwhile the devs are wrangling the complexity of the software itself, which requires mental resources.

Yes. And encoding ownership and lifetimes into the type system is a huge reduction in the mental tax when working through code that hasn’t been touched in years, or wiring up some feature to an interface another team just landed that’s not documented at all. Not having to reverse engineer the code to discern ownership and concurrency patterns is a huge time saver.

> Any additional complexity introduced by Rust slows down development and frustrates the team

I agree artificial complexity due to limitations of Rust’s soundness checks are frustrating, but a cost I’m willing to pay given the long term maintenance benefits. But the cost of system design complexity has to be paid at some point, and I’d rather pay as much as possible at compile time vs. discovering problems at run time.

> And the bugs are still there.

Certainly. But essentially eliminating classes of UB bugs that are hard to repro saves all the engineering teams time.

> Operating efficiency becomes important only after the product is out and stable, with paying customers and product-market fit

Yes. Total cost of ownership is what I want to see more research in.

And I think this is the source of our deferring perspectives. My original comment wanted more research on TCO, but I don’t think that’s particularly applicable before product-market fit.

> but by then, why boil the ocean with a rewrite?

Who said anything about a rewrite? Use Rust for new development. And, as the system grows, components will be rewritten (whether due to new business needs or to improve maintainability). Don’t keep playing with fire when it’s no longer necessary!

> Sometimes leadership can be sold on a new and improved v2

Perhaps I’m jaded, but my motto is “Rewrites always fail.”

> we need languages that make it less mentally taxing for developers to write product software

For extremely large systems whose codebases have been written over decades, we need languages that make it less mentally taxing for developers to maintain product software. The complexity of maintaining these systems is why FAANG employs tens of thousands of engineers who seem to move slower than an aircraft carrier. The difficulty in reasoning about system behavior greatly impedes progress.


> Totally agree! But they do care if Word corrupts a document that’s been open for 10 days (a bug that a principal engineer spent many, many weeks hunting down).

Caused by memory corruption, or the remaining 30% of logical bugs present in any safe language?


IIRC, that specific bug was due to a few issues:

1. Mismatched type definitions in different translation units caused an implicit 32 to 16 bit conversion. 2. An addition to the 16 bit value overflowed, causing it to become negative. (The “run for 10 days” thing helped the value get to the point of overflowing.) 3. The negative value led to an out of bounds write, corrupting a key bookkeeping data structure.

Rust would have failed to compile at step 1, and would have panicked at 3 (and potentially 2, depending on compilation settings). I’m not sure what other safe languages are candidates for this domain, but I would suspect these issues would likely be similarly identified.

That bug, unfortunately was identified after release. The release blocking heap corruption bug I mentioned was due to a C++ object being deleted when it called back to an event handler and wrote to its member variables after the handler returned. Ownership and lifetimes would have prevented this design error (which is surprisingly not that uncommon).


> Mismatched type definitions in different translation units caused an implicit 32 to 16 bit conversion.

Rust would not have failed to compile at step one necessarily. Or were you assuming a full revamping of the build and dependency management systems in addition to the rewrite of the program itself?

If we need to clean up the build and dependency management system to start using Rust correctly, isn't that two major migrations?


> Rust would not have failed to compile at step one necessarily.

True. In this case there was old code and new code. I assumed in a hypothetical hybrid Rust codebase, bindgen would be used to bring the types in old code to Rust, so the compiler would identify the type mismatch in perhaps the impl From.

> If we need to clean up the build and dependency management system to start using Rust correctly, isn't that two major migrations?

At the scale of Office, managing the build system is an evergreen project staffed by dozens of engineers. Adding support for Rust integration is a typical deliverable for such an org.


My point was that data can be corrupted due to logic bugs as well, safe languages no matter which one not even formal proofs prevent this.

If suffixes that the data written for an entry metadata is not the correct one for the actual content.


> My point was that data can be corrupted due to logic bugs as well

For sure. But in the 10+ years I worked on Office, I spent far more time per UB bug than logic bugs. 1 UB bug could take weeks to fix. I don’t recall any logic bug taking more than a few days.

Developer productivity would be much improved if logic bugs were the only class of bugs.


Yet so far other than AddIns, Office has been very unwelcoming of anything but C++.

I bet if Sinofsky would have had an option those AddIns wouldn't even exist, given his opinion on .NET.


I think this comes from people trying to use rust to replace fast gc languages (e.g. C#/java). rust is much lower cognitive overhead than writing correct C/C++, but it's a lot harder than writing code where memory is automagically dealt with for you.


I am still in awe that I could write an entire emulator in Rust without encountering heap corruption, use-after-frees or segfaults in host code (as opposed to guest code, the code under emulation). It's a truly amazing language.


> I am still in awe that I could write an entire emulator in Rust without encountering heap corruption, use-after-frees or segfaults in host code

Or you could just write it in Java, which has been around since the 1990s. Even Go is memory safe if you don't touch concurrency.


Java makes it a lot more painful to interact with native code (which I have to do often), while offering much worse performance characteristics and no memory safety while doing so. I also wouldn't enjoy dealing with NullPointerExceptions all the time — not a memory safety issue, but also something Rust deals with better.


Not to mention Ada.


For an emulator, just go with SPARK :-) Ada didn't bring lots of memory safety until the recent work on ownership, but yes it's all getting there. If you have to manage registers, bitmasks, complex bit-precise data structures, and weird non power of 2 bit sizes or even specific bounds constraints, you'll be in paradise.


TCO is not that important in most cases compared to finding out what to build and estabilishing the user base and not failing due to slow iteration speeds at the beginning. If it takes off, the value derived from the software is tends to be more than the cost, so discovering more things to build is better than optimizing sw expenses at second half of the lifecycle.

Of course there are exceptions, sometimes the potential upside delivered by the software is small and capped and the requirements fixed in advance, and implementation path is well trodden...


Because the language's resource allocation style is not so much a "cross-cutting concern" as something which is all-pervasive across every single non-trivial program written in that language. For a long time, we had four styles: Static allocation, purely manual management of dynamic resources, garbage collection (usually with manual management of some resources), and "don't worry about it" for truly high-level declarative languages. RAII adds something to this, but C++ didn't give up new and delete (or malloc and free... or anything whatsoever) so it isn't really a wholly new paradigm. Rust, however, adds something that's genuinely new to most programmers, and it requires a re-working of the thought-styles, so it's worth thinking about whether it's worth it.


> it's worth thinking about whether it's worth it.

Totally agree we need to evaluate the trade offs of various paradigms. But, I think the most important aspect to understand is the impact on maintenance as opposed to writing.

I’m not sure what my ratios of time spent on reading vs. writing vs. debugging code are, but I am certain writing is what I do least, which is why I’d like to see more research on total cost of ownership.


> [Rust's Ownership and Lifetime rules] come with a steep learning curve and have been repeatedly cited as a barrier to adopting Rust

Rust Ownership and Lifetime rules aren't really that hard to learn. The only trick is that a programmer cannot learn Rust exclusively by trial-and-error (which programmers love to do), trying dozens of syntax combinations to see what works. A programmer is forced to learn Rust by RTFM (which programmers hate to do), in order to understand how Rust works. That's all there's to it. It's like trying to learn to play the guitar without reading a book on guitar cords, pulling guitar strings individually at random - the music just won't happen, even if you're Mozart.


> Rust Ownership and Lifetime rules aren't really that hard to learn.

What’s hard is not to learn the rules themselves, but to build things under the constraints of these rules. That turns out to be very different, because even though the rules are simple, they have knock-on cumulative downstream effects on the process of making software. My guess is it doesn’t fit well with how most people think, and especially the order in which a problem is broken down (such as front-loading ownership decisions).


> What’s hard is (...) [building] [code] under the constraints of these rules

Good point - I have to admit that, for example, the single-mutable-reference rule in Rust did force me to keep restructuring my code differently than I used to in other PLs and I did struggle with it to an extent.

It doesn't automatically mean, however, that Rust is (literally) "harder to learn than other PLs". It is possibly the question of whether "a programming concept is hard to learn since it cannot be painlessly brought over from previous experience" or "a programming concept is hard to learn since it is hard to understand in itself".


Yeah. It also depends on what we mean by learning a language. For most people, I assume we feel comfortable when we can achieve day to day tasks. You could have other metrics though, such as learning how to write code that is free from certain problems, like safety and data races.

The amount of problems that Rust moves from the “runtime and unit test” domain to compile time errors is unprecedented in mainstream languages, and also that those errors are binary pass/fail showstoppers. Empirically, I’ve found that programmers coming from C/C++ are much more happy about that than other backgrounds, presumably because they are aware of the pains of having those issues appear at runtime. However, it’s also important to understand that there are a huge amount of use cases where those problems can be solved “good enough” with GCs and other runtime safeguards, which then can give other benefits that outweighs the downsides.


But this might be the main difference. At least for me.

I learn by imitating, like a child. I got my first guitar when I was a kid as a present (no internet, no money to study or to buy books). I learnt by imitating the music I liked. Later as an adult I got technical and I closed the circle with guitar and music theory and advanced techniques.

This is the way I got into C and later C++ more than 25 years ago. Again, without internet, or with limited access. I understand ownership and lifetimes, but when I open a Rust project I cannot understand what the hell is going on.

For new languages I want to learn, I read other people's code and try to do some modifications to check what changed, or add a small new feature. And Rust is so hard on the syntax and the way it wants things to be done. The worst part (for what learning regards) is that Rust doesn't forgive.

So I started a new project. I had no problem with ownership and lifetimes, but later I realize that returning "Result<Self, Error>" and the "?" operator are no so magical after all. And "Error" sometimes is a struct defined in "io", sometimes it thinks is a trait, sometimes is defined in some other error-handling crate. Then you have to learn to map errors, boxing them, dyn dispatching, thinking about what is the overhead of all that, etc. And all that is just for returning something meaningful from a function.

So it's not all "ownership and lifetimes".


> when I open a Rust project I cannot understand what the hell is going on (...) > Then you have to learn to map errors, boxing them, dyn dispatching (...) So it's not all "ownership and lifetimes".

I definitely see where you are coming from when it comes to PL syntax density and PL surface area in general, Rust or no Rust. I will probably surprise you when I admit that even though I have been coding C++ for decades and I learned Rust without any major problems, I lost my small personal war to become comfortable with the modern, newcomer-friendly, memory-managed... Swift. Yes, that great-for-your-first-PL Swift. I started out as a Swift aficionado, but with time I started hating the language exactly due to the fact that "I [opened] a [Swift] project [and] I [could not] understand what the hell [was] going on". The language was just too bloated and idiom-based. I gave Rust a shot and I learned Rust much more effortlessly, perhaps due to Rust's "consistency" and "predictability". My point: it might be that any modern PL can get pretty dense and broad due to the necessity of handling imperative, functional, generics, async, multi-threaded, a capable standard library, etc., while it might be just a myth that Rust is "harder to learn" than other modern PLs. For me personally, Rust "ownership and lifetimes" turned out to be easier to learn than Swift "feature set", but it might be just me.

> The worst part (for what learning regards) is that Rust doesn't forgive.

Yes, but this is the whole point of Rust! Rust tries very hard to shift bug detection from runtime to compile-time and the only way it can do that is by strictly enforcing a set of sometimes complex rules at compile time. However, it is common occurrence that it takes tenfold more time to find a bug in a piece of code (especially bugs related to unexpected memory mutation or multi-threaded memory access) than to write that piece of code in the first place. So, wrestling the Rust compiler is "a calculated cost" designed to be worth it in the end.


After a year and a half doing hobby projects in rust, I must say this Error dispatch is the biggest pain. You may get used to ownership by just writing less ambitiously. But those errors everywhere, are quite tangled. Even Box<dyn Error> that seems universal, stops working with thread-related functions.


I agree, I think the Rust Gods should think of some ingenious way (which actually seems to be the norm when it comes to the "thoughtfulness" of the Rust ecosystem) to do both library-style error handling (where specific error conditions have to be handled differently by a library consumer) as well as app-style error handling (where multiple error conditions can be handled pretty much the same way by an event loop), add it to the standard library and `clippy` out (lint) the livin' lights out of every other approach out there :).


The difficulty with ownership and lifetime is not the basic principle which indeed is easy to understand, it's understanding how those rules affect the code you write in all kinds of detailed ways. That simply takes time.


If most people won't RTFM then most people won't get to become productive in Rust, so your pool of job applicants is diminished, the real-world use is lower, the tooling and library support is reduced.

Being a member of a super-elitist cabal that knows how to use Rust "properly" is not some point of pride, it's in fact a mortal danger for the language, just look at Haskell.

And this is why the world has descended into Javascript hell, everybody and their mother are coding "apps" and "microservices" where each click is accompanied by a stack dump because the code is written by a student doing Elance in their spare time, but hey, it's good and fast enough to ship so you get to be first on the market and 3 years later your startup is bought by my bank and I can't login on my internet banking account without seeing that same revolting stack dump that couldn't be produced by a modern and flexible strong typed language. Real world doesn't care about abstract code purity metrics.


> Being a member of a super-elitist cabal that knows how to use Rust "properly" is not some point of pride

I'm not sure I follow you, since I am saying something exactly opposite: anyone, even a mediocre, 1-year-of-experience programmer can successfully learn Rust, if only he or she tries to read a book on Rust, instead of just randomly hacking at Rust code. No "super-elitist" brain required, just a switch of learning methodology.

> just look at Haskell

The problem with Haskell is that it requires a monumental, conceptual switch from imperative programming to functional programming, which has not happened anywhere yet outside of academia (well, perhaps with the exception of functional reactive UI programming, e.g., React Native, Swift Combine, etc.), so Haskell is a tough sell in "real world", commercial organizations. Rust does not have this problem, since Rust does not rely on any "code [structure] metrics" (did I understand this right?), but instead moves as much bug detection as possible from runtime to compile-time (especially for multi-threaded applications).


> Being a member of a super-elitist cabal that knows how to use Rust "properly" is not some point of pride

Rust's ownership rules are simply an explicit form of what people already have to (and sometimes fail to) look out for in C/C++. If learning a language for a couple months makes one feel elitist that's not the language's fault. Though granted, some Rust people are...very eager at talking about its virtues.


I find it hard to accept that being first to market, capturing a lot of value and dumping it on a greater fool are things to aspire for.

I see this argument in many forms all the time, in a nutshell: monetary gain should be the sole metric of success for engineering. Considering we are killing the planet and perma-exploiting entire classes of society following that logic, maybe we should change approach?

But yeah ship it, pocket it. Why be idealistic if I just need to make enough money to retire and say "got mine, f you"?


If you won't ship it, then someone else with much worse ethics will, and they will also embed a secret GPS tracker that sells data to the worst abusers.

Competing with unethical actors is already hard, playing the game on "super-hard" just to feel good about your ethics is actually setting yourself up for failure and betraying those ethics. Been there, done that for a decade or so, have nothing to point to as the net positive effect of my impeccable ethics, but I most certainly do lack the financial and political capital that would afford me any real impact.

So by all means, ship it, make a trillion bucks, then educate and feed the poor and invest all your cash into eco technologies.


You are absolutely right. It is much, much easier to stoop to the average level of morality. And yes, attempting to have standards will certainly limit your earning potential. In the same way that preying on weaker communities around you via war, enslavement and pillaging has been a winning strategy for most of civilization. I truly think capitalism in its current form it immoral. And the fiction of effective altruism as a justification just makes it worse.


Our current society, at least the western version of it, is by far the best human society that ever existed on this planet. Our levels of wealth were inconceivable even 300 years ago. Not even kings could have things we take for granted, like medical care, round the world air travel, indoor plumbing, lasting peace and physical security, access to information and learning.

In a very extremist fashion, of course you can define a moral standard high enough that we will always fail. But the opportunity for you to think about such absolute moral issues - as opposed to literally slaving in a coalmine or birthing you 14th child and dying before the age of 45 - was created by moral pragmatists who were "effective altruists"[1] of their times , even if the only thing they did was to seek profits by inventing some type of better machine.

We're at a cross road now where the existing mode of wealth production, focused on growth and individual material satisfaction, has reached the point of diminishing returns and even negative returns if we consider our fate as a species in the natural environment. But denying our ability for incremental improvement, forgoing our world building tools and returning to some elusive harmonious state of nature can't be the answer.

[1] though I dislike that term due to its abuse by virtue signaling sociopaths


There's no need to strawman a "return to nature" argument. We've always exploited each other, and the current shape of society has an unprecedented proportion of people living in prosperity, yes. But at the cost of catastrophic destruction of ecosystems and disruption of a climate that made that possible in the first place.

I get it, modern life for the average western citizen is quite amazing. Using that as an argument for 'thus the western model of exploitation is superior' is not warranted at all.


> But at the cost of catastrophic destruction of ecosystems and disruption of a climate that made that possible in the first place.

I disagree. We could have wrecked the natural environment even if we had a much inequitable and oppressive society, think Nazi Germany winning the second world war and entering a nuclear race with the US. There is no single "western model of exploitation", there is a whole family of them (in an alternate history sense), all capable of destroying the planet, but with massive moral differences, and your framing loses that nuance.

This nuance matters because your implication is that the environmental costs are a tradeoff for this prosperity, where in reality they are just a direct result of a runaway species with technology. And our proven ability to control this process towards moral goals, such as material welfare for the masses, suggest to me that we can, in principle, limit and reverse the environmental damage, as long as we recognize it as a worthy moral goal.


> Being a member of a super-elitist cabal that knows how to use Rust "properly" is not some point of pride, it's in fact a mortal danger for the language, just look at Haskell.

Just a reminder of the Haskell motto: "Avoid success at all costs."

So in their case, it's working as intended.


> the music just won't happen, even if you're Mozart.

Thousands of self-taught musicians, many of them famous, beg to differ. Not that this is relevant to the discussion but the example seems flawed.


Ok, let me rephrase: "[without using any instructional materials] the music will take an unusually long time before it happens, even if you're Mozart". Which seems to correspond to the problem that if you keep hacking at a Rust compiler without using any instructional materials, it will take an unusually long time before you eventually get the code to compile by trial and error.


Most of them had mentors in one form or another.


"The last question I will inspect here is that, if Ownership is difficult to learn and use for so many reasons, why do developers choose to use Rust anyway?"

Ownership is not that hard. Borrowing is a bit harder but not that much more so.

I use Rust because of the ownership and borrowing systems, not despite them. They help me design systems and write code which has very few bugs once it compiles. (The last code I had with bugs in it I wrote maybe 2 months ago.)

I hope some useful pedagogical work comes out of the whole project, but I'm really doubtful of the premise.


Anecdotal, but I recently had the opportunity to write some relatively greenfield code in each of Rust and C++. Nothing too massive, a few thousand lines. But fairly intricate code with a need for high performance. I consider myself to be fairly advanced in both languages (but not can-write-the-spec level advanced).

I was shocked at how many memory corruption / uninitialized data errors I was hitting in C++, for what (I thought) was fairly nice, modern-ish code. This is for code that builds cleanly with -Wall -Werror, with exclusive use of .at() instead of unchecked vector/map access, etc. There are just far too many ways to end up with uninitialized data, and the ecosystem (think serialization libraries) don't really help you there.

I didn't realize how much of a cognitive burden Rust was relieving me of. Sure, the borrow checker is a pain. But I have confidence that when my code builds, it will be entirely free of certain classes of bugs. And other classes of bugs are well-managed by Rust's compiler warnings and Clippy lints.

There seems to be a sort of sleight of hand in programming communities where when Rust is hard, we blame it on the language, but when C++ is hard, we blame it on the programmer. I didn't see the article do a good job of teasing this out.


> There seems to be a sort of sleight of hand in programming communities where...

I don't think there's anything sinister here, just the classic difference between old and new. Old things are familiar, and their downsides are facts of life. New things are threatening, and their downsides get a lot of scrutiny. I'm not even complaining; it's a pretty reasonable heuristic most of the time.


I agree it's not sinister, but I think it goes deeper than this.

Strong (static) type systems fundamentally make you do more work up front. They force you to be more explicit. They restrict what you can do. They're in your face, because you can't get your program to compile without them. No one gets through any significant amount of Rust programming without knowing that the borrow checker is there. It's so fundamental that it's just part of the experience.

C++ arguably has just as many rules for writing "correct" programs, but they're not enforced by a compiler. So much stuff happens by convention. If you follow the convention, great, your code is safe. If you mess up, no one catches you. Because it's your responsibility, it's easy to attribute this failure to your programming skill rather than to the language that made that mistake possible in the first place.

I'm not saying stronger type systems are always better, but I do think there is a fundamental human tendency to attribute the up-front as hard and the latent as easy, even if the up-front work ultimately helps you avoid the latent work down the line.


> Ownership is not that hard. Borrowing is a bit harder but not that much more so.

I guess this is a bit like Typescript then. Especially when it comes to generics, sometimes I need to sit down and think carefully about what datatype exactly is it that this method requires compared to what I have and how do I express it in a generic way.

Sometimes it ends up taking some time but the result is invariably that the code ends up easier to read, understand and modify.


It would be interesting to see alternate languages with the ownership model but without the burden of the alien syntax, complex type system, and added complexity of macros. All of which make learning rust a burden. I constantly go like "Wtf does this piece of code even do?" when looking at rust code. And it doesn't help that the libraries and documentation is riddled with type system jargon. I'm sure it's all necessary. But it is not simple.

It might get easier without all that but is that even possible? The concept of borrowing at it's core isn't that hard to grasp: you taking ownership of something means that somebody else doesn't have it until you release it back to them. Easy. But we seem to have a sample of 1 when it comes to rust style ownership. At least, I'm not aware of other languages doing this. It seems to have lots of advantages. Which raises the question why this obviously good idea is not being replicated by others.


It's been a while since I started learning Rust, and have the advantage of learning new constructs as they are being implemented before they even hit stable, so I find it hard to put myself in your shoes. Could you give some examples of alien syntax whose functionality you couldn't extrapolate from other constructs? rust-analyzer does provide on-hover documentation for keywords and (some) sigils, but it isn't as discoverable as it could be and I can believe that there are documentation "bugs" of missing explanations for specific use-cases, even in The Book!


The wisdom for programming language adoption is that you need to be able to do nothing in 3 minutes (exitCode 0 = success), do anything in 10 minutes (pick a softball problem for your tutorials that avoids the difficult parts of your language), and do something in an hour.

With that in mind, you don't have to remove the model, you just need defaults that can get you past that 1 hour mark, and people will stick around long enough to put in the time and effort to figuring it out.

Most simple problems don't need shared state in order to work, so a language that makes assumptions, such as no sharing by default, could kick that can down the road for a bit. A lot of recursive and some iterative examples only ever accumulate state on the stack. You don't need shared state until you interact with an external system, such as another computer or a human.


Is there a simpler model out there, somewhere between Rust and Haskell, where -nobody- owns the objects and all arguments are pass-by-value unless otherwise stated or inferred?

Pure functions should be able to share objects. The problem I suspect is working out the calculus of transitioning from stateful into stateless code, but the plus side is that you end up with a programming language that enforces functional core, imperative shell.


I never understand why people think rust is harder to learn than C. To be able to actually shell out a 100K non trivial C code base, you have to either reinvent or learn all the “patterns” laid out in languages like rust plus more.

You can not say you can write a bubble sort in C and you know C, and that’s indeed relatively easy. But you can not actually use it anywhere.

Again a good C programmer will be a better rust programmer if he wants to. But sometime the luxury of just work with people with the same technical calibers is a sufficient reason to stay.


Saying other programming languages are easier to learn is practically the same as saying they’re easier to start writing bugs sooner.

I forgot who said this but something like “if reading code is ‘debugging’ then writing code is ‘bugging’”


The difficulty of learning translates to the difficulty of understanding and "mental fog".

Mental fog translates to logical bugs that either the compiler or human reviewer never catches.

As a result, the program becomes buggy in more subtle ways than before.

To claim that overall, rust reduces the number of bugs during the lifetime of the program, we need experimental data, which is still missing.


The summary is buried on page 11 in this sentence:

> There are almost certainly many other large and small, obvious and subtle reasons for the difficulty of learning and using Rust’s Ownership type system, but these three (Rust’s different paradigm, unhelpful error messages, and the incompleteness of the borrow-checker) are the most apparent from the works surveyed here.


Quote from the paper:

Is Ownership difficult to use? “Learning Rust Ownership is like navigating a maze where the walls are made of asbestos and frustration, and the maze has no exit, and every time you hit a dead end you get an aneurysm and die.” — Student participant from [Coblenz et al. 2021]


Yeah but this is a much better description of when I was learning C++ in high school and trying to figure out why I got a seg fault. The rust compiler is much friendlier than just the text "segmentation fault".


Sounds a bit melodramatic, honestly.


Is there a well defined class of code, whose expression in Rust, will perform better than what would be emitted by a sufficiently advanced optimizing compiler (say SBCL) ?


Not exactly a class, but there are runtimes of concrete programs written in SBCL and C++ here:

https://benchmarksgame-team.pages.debian.net/benchmarksgame/...

No direct Rust SBCL comparisons, but generally Rust and C++ perform similarly.

There's some discussion on the limitations of SBCL's GC in this thread:

https://old.reddit.com/r/Common_Lisp/comments/j8z1j7/how_doe...


It’s interesting to see that a fully dynamic, GC language manages to stay within 1x, 5x, 10x range of C++ in these tests.

I haven’t looked at the tests themselves so I can’t speak to their application.

But the range of performance for dynamic languages seems to be enormous. Much bigger than the one of static languages.


> fully dynamic

Well, no, it's SBCL. Common Lisp has support for types, but most compilers only use them for optimization, SBCL goes one step further and emits warnings when you mismatch types. And looking at the code, I can see lots of type declarations.

It's also interesting to note that the code does not seem to be using SBCL's new SIMD library*, so it could be sped up even more.

* <https://github.com/marcoheisig/sb-simd>, see the texinfo file for documentation.


thank you!


The most common example is aliasing. Since the rust type system promises that you won't have two mutable aliases to the same data, even data coming from an unknown caller, the compiler can in theory make optimizations that aren't possible in other languages that permit multiple mutable aliases.

Especially for values that cross the boundaries of compilation units, the compiler in another language cannot possible have enough information to know if aliasing could occur.


I think we haven't even come close to harnessing the full benefits of

(1) mutable references can't be aliased, and

(2) referential transparency: data behind an immutable pointer never changing no matter how many aliases to it there are.

(The exception being UnsafeCell, of course, which can easily be detected.)

Exciting times ahead for Rust performance!


alias analysis in Rust is great, but friends worked on it in optimizing compilers in Urbana-Champaign in the early 1990s, so it's possible Rust is its most widespread rollout.


It's possible to specify no alias in C with the `restrict` keyword, but it isn't a widely used feature. With Rust, a whole bunch of bugs related to it were found and fixed in LLVM, so I think you're correct that it is the most widespread rollout.


I mean... if the compiler isn't smart enough to see what you were trying to do and generate some ridiculously fast implementation, is it truly worthy of being called "sufficiently advanced"? ;P You just know someone is going to try to add in an LLM-based (note the lack of a V there) compiler pass that says "give me a faster algorithm that does the same thing as this function" (and then all hell will break loose when it starts injecting random bullshit into the function it sort of grokked, or decides your code is for some evil ad company and deletes it on purpose... oh, wait: maybe this is a great idea).


Maybe CPU bound code where either your data fits on the stack or you can avoid a lot of copying chunks of the heap by expressing your algorithm in terms of in-place mutation?

That is where garbage collected languages seem to be incurring penalties. Not sure what SBCL does specifically. There was this talk last October about efforts at Jane Street to make explicit stack allocations possible in OCaml:

https://www.youtube.com/watch?v=yGRn5ZIbEW8


Not sure how C++ compiler or SBCL compiler utilizes auto-vectorization. The Rust compiler is able to auto-vectorize tight loops to use SIMD without much prompting. E.g. the following uses SIMD automatically.

   byte_array1.iter().position(|&x| x == digit)


I was shocked when I first heard about SIMD in a online lecture.

Not by what it does. But by the fact that apparently every operation uses it, but most just throw away the result.


What is the role of ARC (automatic reference counting such as in swift) compared to the explicit approaches rust takes?


I found a paper with survey about the reference counting overhead of the Swift language. It says on popular client side benchmarks, the atomic counter update itself takes 42% of the total execution time on average.

Keep in mind that in swift virtually everything is Arc-ed, so it puts heavy optimization on it like merging several counter updates within a scope into single atomic operation.

In my opinion such a performance cost is absolutely worth it in most applications given the much reduced complexity when writing code.


It's really not. ARCing everything gives you less efficient code than just using tracing garbage collection. The approach that makes RC and ARC worthwhile is the one seen in Rust, where RC is only used for any object that might have more than a single "owner" controlling its lifecycle, and ARC when such "ownership" might be shared across threads.




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

Search: