14 ms·
The usability of advanced type systems: Rust as a case study
- jjtheblunt 4y agoIs 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) ?
- skavi 4y agoNot exactly a class, but there are runtimes of concrete programs written in SBCL and C++ here: https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/sbcl-gpp.html 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_does_the_gc_in_sbcl_impact_fps/ https://old.reddit.com/r/Common_Lisp/comments/j8z1j7/how_doe...
- jjtheblunt 4y agothank you!
- dgb23 4y agoIt’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.
- tmtvl 4y ago> 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 https://github.com/marcoheisig/sb-simd>, see the texinfo file for documentation.
- saurik 4y agoI 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).
- TheDong 4y agoThe 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.
- howinteresting 4y agoI 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!
- jjtheblunt 4y agoalias 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.
- jamincan 4y agoIt'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.
- nequo 4y agoMaybe 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 https://www.youtube.com/watch?v=yGRn5ZIbEW8
- ww520 4y agoNot 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)
- dgb23 4y agoI 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.
- howinteresting 4y ago"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.
- eitland 4y ago> 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.
- eslaught 4y agoAnecdotal, 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.
- briantkelley 4y agoI’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.
- adgjlsfhk1 4y agoI 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.
- optymizer 4y agoBecause 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.
- oconnor663 4y ago> 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?
- IshKebab 4y agoYes, 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".
- thayne 4y agoAnd 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.
- oconnor663 4y agoAlso 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.
- unrealhoang 4y agoYes, 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++.
- gary17the 4y ago> [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.
- cornholio 4y agoIf 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.
- gary17the 4y ago> 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).
- redbar0n 4y agoThe 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.
- singularity2001 4y agoWhat is the role of ARC (automatic reference counting such as in swift) compared to the explicit approaches rust takes?
- singularity2001 4y agoI 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.
- zozbot234 4y agoIt'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.
- deleted 4y ago[deleted]
- singularity2001 4y agoQuote 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]
- sanderjd 4y agoYeah 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".
- ambicapter 4y agoSounds a bit melodramatic, honestly.
- asimpletune 4y agoSaying 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’”
- resource0x 4y agoThe 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.
- up2isomorphism 4y agoI 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.