Unsafe Rust is harder than C

(chadaustin.me)

59 points | by todsacerdoti 9 hours ago ago

56 comments

  • wongarsu 2 hours ago

    Unsafe Rust is significantly harder than C, with complex rules that are not fully specified. But luckily you don't have to write a lot of it. And the bit you need can either come from battle-tested libraries or be contained to it's own module where you can rigorously test and fuzz it. And the tooling available for that is pretty good. The article shows good examples of that.

    There is some work on making unsafe rust safer, or at least easier to get right. But those are significant undertakings that will take years to pay off.

    On the other hand safe rust is so much easier than correct C that I don't particularly mind the tradeoff.

    • WalterBright an hour ago

      > On the other hand safe rust is so much easier than correct C that I don't particularly mind the tradeoff.

      D isn't Rust, but my personal style has gravitated towards the borrow checker rules anyway. It wouldn't be hard at all to do it in C, either.

      C's biggest security failing, however, is the lack of array bounds checking. It's always the #1 cause of security bugs in the field, by a wide margin. None of the workarounds in Standard C are attractive. I do not understand why the C committee adds all these bits and pieces of new features, and does not fix that problem. It's not like it's hard:

      https://www.digitalmars.com/articles/C-biggest-mistake.html

      The solution has been out there for 15 years now.

      • Gibbon1 28 minutes ago

        The standards people won't even add a standard buffer or slice to the language.

        I kinda cry every time I see a new api with foo(void *src, int sz) signatures.

    • kazinator 2 hours ago

      Safe Rust looks absolutely painful painful compared to other safe languages.

      Correct C is not actually hard to write. It can be somewhat hard to convince oneself that it's right.

      That's an important difference. Not all C has to be correct. If it's experimental or exploratory code that'll be thrown away. If that just works on one machine or on particular inputs that you care about, or it only certain specific environmental conditions, that can be fine.

      Correctness is more than just memory safety and avoiding overflows. They are still bugs in programs and languages that avoid those problems.

      • tptacek 2 hours ago

        When people talk about not needing to write much unsafe Rust, they mean that in any large-scale system, there are probably only a couple hotspots that are complicated enough to need the escape hatch from the Rust safety rules; you write the unsafe code, you give it an interface to the rest of the code, you carefully validate the unsafe code, and you're good to go.

        They don't mean that you write unsafe Rust code for experimental or exploratory code that will be thrown away. You could do that, but it's not common.

        More importantly: this approach of parsimoniously writing unsafe code in a larger-scale safe system does not work in C/C++. Memory safety issues can (and do) crop up all over your system, not just in the small "unsafe" parts you validate carefully.

        • steveklabnik 2 hours ago

          Writing unsafe Rust is enough of a pain in the butt (partially thanks to some syntactic salt that comes from the early days that probably should be removed but isn't being dealt with any time soon as far as I know) that it would be harder to prototype in unsafe Rust than safe Rust, IMHO.

        • kazinator an hour ago

          Write the unsafe code easily in C, and write the rest of it in an easy-to-use language, and you're good to go.

          • tptacek an hour ago

            That's a valid strategy, but it's harder to write "unsafe-safe" code in C than people think it is. We just did a podcast episode about this with results from the Android team:

            https://securitycryptographywhatever.com/2024/10/15/a-little...

            Even with careful rules and oversight and secure coding idioms and library exclusions and fuzzing, the rate of memory safety defects in C/C++ code is still pretty high.

      • steveklabnik 2 hours ago

        Safe Rust takes some time to learn, but once you do, is quite pleasant to write. It is true that it takes some time to get over that hump though. Google found that it was three to six months, but after that, there was no productivity penalty.

        Rust offers tons of tools that are very useful for correctness outside of memory safety. I hope to never program in a language without sum types ever again, if possible.

        • rc00 2 hours ago

          You keep referencing Google's clearly flawed and asymmetric marketing about Rust. It is really unsightly. It has been thoroughly debunked nearly everywhere it was reported but here is a 10 minute recap of some of the clear flaws: https://www.youtube.com/watch?v=vB3ACXSesGo

          The summary is that the research that you reference was not done in good faith.

          • steveklabnik 2 hours ago

            I like prime but just because he says something doesn't mean it's true.

            Repeating his claims in text form would be much more useful than linking to a video. I am not anti-video, but for example, I am out in public right now, and cannot watch it, and therefore can't meaningfully respond.

            • rc00 an hour ago

              The video is worth a watch before responding. I'll try to summarize but it is unfair to the content. (I specifically opted for it over some others due to its brevity.) In all, the statement from Google lacks context and due diligence.

              Some bullets:

              * Experience levels in C++ and Rust are not equivalent. The experience gap between C++ and Rust developers can skew results, as C++ has been around much longer, affecting developer proficiency and code complexity.

              * Comparison of codebases: legacy C++ vs. greenfield Rust. Comparing a mature C++ codebase with a new Rust project is like comparing apples to oranges; age and complexity can impact productivity metrics significantly.

              * Measurement metrics can be misleading. The metrics used to determine productivity often lack depth, failing to account for feature complexity or development duration.

              * Organizations have asymmetric composition of resources. The management style and communication within teams can heavily influence productivity, indicating that not all teams function under the same conditions.

              • Ygg2 37 minutes ago

                Counter bullets:

                * Experience gap between C++ and Rust should give edge to C++ as it has larger pool of candidates to pick from.

                * Even in greenfield Rust vs greenfield C++ like Rust drivers in Android, Rust significantly reduced defect rate.

                * Measurements in this study were done on teams with same size, rewriting existing service. Neither you or Primagen demonstrate how that would be misleading.

                * See first point. While true how can you be sure C++ team isn't overall better than Rust? What if C++ is three senior engineers and Rust is three mediors?

                Actual possible problems:

                * There isn't yet enough data for language comparison. Too early to tell.

                * Observer effect might have influenced the study.

          • OtomotO an hour ago

            Aha, so you're saying that asymmetric marketing is bad (I agree) and as proof you put up some other asymmetric marketing, namely an influencer`s video... Hard pass :)

          • gauge_field an hour ago

            Any other source that goes thoroughly over the video of keynote since it was thoroughly debunked nearly everywhere I could not find it myself. Also, I would not call it thoroughly.

          • Ygg2 an hour ago

            > thoroughly debunked

            Ok. Looks inside. First warning sign Primagen. Ok. What did he talk about:

            > There is lies, damn lies and statistics.

            > Go is goated, let's go.

            Truly dedunked.

  • kelnos 2 hours ago

    It sounds like this boils down to one thing: C and Rust have different aliasing rules, with Rust's being much more restrictive.

    You can't assume programming in unsafe Rust is like programming in C. Unsafe doesn't mean "do whatever you want"; it means you get to do things that the compiler won't check for you, but it's your responsibility to ensure you're still maintaining Rust's invariants.

    So yes, unsafe Rust is harder than C. I think that's fine, though. Most people will be able to get by without writing any unsafe Rust, and those who still need to use it will end up writing very little of it. And tools like MIRI exist to help increase your confidence that your unsafe Rust is correct.

    • jsheard 2 hours ago

      > C and Rust have different aliasing rules, with Rust's being much more restrictive.

      Rust is more restrictive by default, but C's restrict keyword lets you opt-in to strict aliasing semantics in exchange for unlocking compiler optimizations that would otherwise be unsound, so it's not like you're free from having to think about aliasing in high performance C code.

    • steveklabnik 2 hours ago

      It's not so much "more restrictive" as it is "just plain different."

      You have behaviors for all four quadrants: there's stuff that's okay in Rust and not okay in C, stuff that's okay in both, stuff that's not okay in both, and stuff that's not okay in Rust and is okay in C.

  • twoodfin 2 hours ago

    Coming from a position of extreme Rust ignorance & C++ brain poisoning:

    Here’s the issue: waiting_for_elements is a Vec<Waker>. The channel cannot know how many tasks are blocked, so we can’t use a fixed-size array. Using a Vec means we allocate memory every time we queue a waker. And that allocation is taken and released every time we have to wake.

    Why isn't a structure that does amortized allocation an option here? I appreciate the design goal was "no allocations in steady-state", but that's what you'd expect if you were using C++'s std::vector: After a while the reserved space for the vector gets "big enough".

    • chadaustin 2 hours ago

      I'm sorry, I had a whole section on amortized allocation in a draft version of the post but I deleted it, thinking it was tangential. You're not the first person to ask: https://www.reddit.com/r/rust/comments/1gbqy6c/comment/ltonq...

      And my response: https://www.reddit.com/r/rust/comments/1gbqy6c/comment/ltpv0...

      One typical approach is double-buffering the allocation but it doesn't work here because you need to pull out the waker list to call `wake()` outside of the mutex. You could try to put the allocation back, but you have to acquire the lock again.

      I had an implementation that kept a lock-free waker pool around https://docs.rs/wakerpool/latest/wakerpool/ but now you're paying for atomics too, and it felt like this was all a workaround for a deficiency in the language.

      Intrusive lists are the "correct" data structure, so I kept pushing.

    • maxbrunsfeld 2 hours ago

      I had the same question. I would think that with rust’s Vec, no allocation would occur at steady state. Vec does not automatically resize when removing elements.

      • makapuf 2 hours ago

        Yup, you can even pre-allocate a given vec capacity to not start zero-sized.

    • Analemma_ 2 hours ago

      Yeah, I'm also not sure what the author is talking about, std::vec also does amortized allocation and never shrinks. There are a bevy of more complicated vector classes in Rust if you need different behaviors, but off the top of my head I don't know why std::vec wouldn't work here.

  • dgfitz 2 hours ago

    For me, it really is just so much syntax and mental overhead to track. Somehow it seems like the same goals could have been accomplished with less verbosity.

    It almost feels like an engineering tool/language, by devs for devs, instead of by devs for “customers” where customer == “outside” devs, of which I am one.

    Edit: typo

  • maxk42 3 hours ago

    Safe rust is harder than C.

    • Cyph0n 2 hours ago

      Safe C is harder than safe Rust.

      • nanolith 2 hours ago

        That used to be true, but now we have reasonable model checking tools for C. It's possible to write safer C without the cognitive load of Rust.

        https://www.cprover.org/cbmc/

        • Cyph0n 8 minutes ago

          I am sure it is “possible”, but we are talking about practicality here.

          Why doesn’t the Linux kernel embrace model checking instead of experimenting with Rust?

        • OtomotO an hour ago

          Great now give me tooling of this millenium (no, I am not going back to vendor everything manually and I am not reinventing every basic data structure I wrote in University in ever project I work on) and we have a deal!

          Oh, also get rid of header files, they are archaic. And I want fearless concurrency... And sum types!

          • nanolith an hour ago

            If you want those things, you don't want C. Pick a reasonable higher level language you like that makes those decisions for you.

            My comment was not to imply that somehow C is superior to X, Y, or Z, but rather to point out that the safety problem with C does have a practical solution.

            • OtomotO an hour ago

              Fair point, sorry (non native speaker here, for what it's worth) have a wonderful day!

      • Quothling 2 hours ago

        Zig is easier than Rust.

        • jsheard 2 hours ago

          Safe Zig is harder than safe Rust, but easier than safe C.

          In conclusion, programming is a land of contrasts.

          • eikenberry an hour ago

            I've never heard the opinion that safe Zig is harder than safe Rust. Pretty much always the opposite, that Zig is easier than Rust all around.

      • tonetegeatinst 2 hours ago

        Dosnt GCC have flags that enforce memory safety? Also don't fuzzers handle this issue?

        • kstrauser 2 hours ago

          It cannot possibly. You can catch some low hanging fruit, but asking a compiler to evaluate whether a specific chunk of code is memory safe is basically solving the halting problem.

          • jmkr 2 hours ago

            Can you explain what that low hanging fruit is (or refer me to docs), and also explain it being a decision problem a bit more thoroughly. I will accept that if you have to run a program to decide if it's memory safe then that fits the criteria, but from my understanding static analysis doesn't run the program, and a compiler is parsing and lexing anyway so it should be able to catch at least some things (the low hanging fruit)?

            Since I have actually started using C I realized how easy it is to be lazy and not handle memory right so it makes Rust and maybe C++ seem more appealing, but trying to figure out random segfaults it seems like address sanitizer and valgrind catches more than I would have assumed is a low hanging fruit.

            I guess I should look more into how Rust manages that safety or understand what memory safety is trying to accomplish more formally. I've taken GC for granted for years until I needed to care about memory.

            • steveklabnik 2 hours ago

              (Not your parent)

              An example of low hanging fruit is -fwrapv. This flag takes a behavior that is undefined, signed overflow, and converts it to defined behavior, two's compliment wrapping. That improves safety, but it does not prevent all errors. There are many flags like this, but they all tackle individual aspects of the problem, and even if you turn them all on, there are situations which aren't caught.

              • jmkr an hour ago

                Thanks. Yeah that makes sense for low hanging fruit. Going through the gcc flags it does seem like a lot of tradeoffs have to be made so you can't cover everything. A quick look through compiling Rust it seems it does at least some of this checking at MIR. I'll have to read more about it.

            • nanolith an hour ago

              The compiler really doesn't have the opportunity to simulate every possibility of code. It's not just the matter of whether the function is safe, but every possible use of the function, which the compiler may not see when it is focused on a single compilation unit or a library.

              If you want this level of safety, which is possible in C, then you need to use a model checker. Model checking C isn't as trivial as adding a flag to the compiler, but it can be done with about as much overhead as unit testing, if a reasonable idiomatic style is followed, and if the model checker is used well.

              It is still a decision problem, and thus has similar limitations, but you can perform steps to ensure that you have some level of soundness with unwinding assertions and other techniques.

              • jmkr an hour ago

                Thanks that's helpful I'll take a look at model checking.

        • Filligree 2 hours ago

          They’re incomplete, and running msan significantly slows things down, to the point you’d be better off with Java.

    • _bin_ 2 hours ago

      Maybe this depends on the application type. I've written a lot of each, and maintaining anything involving concurrency is worlds easier in Rust, since those tend to be the most painful and time-consuming bugs to fix. It's got a learning curve, isn't a particularly easy language, and a big chunk of the "community" sucks, but most of the places I've applied it have been a net hassle savings.

    • rowanG077 2 hours ago

      Is that really the case? I would say writing bug ridden C is easier then Rust in some cases. Writing working C is much harder then writing safe Rust.

  • agentultra an hour ago

    > Using a Vec means we allocate memory every time we queue a waker. And that allocation is taken and released every time we have to wake.

    Is that so? On every push back? I’d expect it’d only do an allocation when the current array segment is almost full… as a vector you might write by hand or like the ones in the C++ standard libraries do.

  • melodyogonna 2 hours ago

    Rust is harder than C

    • OtomotO an hour ago

      To me C is much, much harder than Rust.

      I've written both, albeit way more Rust.

      • melodyogonna an hour ago

        What makes C much much harder? What concepts exist in C and are not present in Rust?

        • OtomotO 33 minutes ago

          It's not about concepts, it's not about language constructs but the overall work and mental load working with the languages.

          For me it's header files, no package manager, doing pointer magic all the time (void pointers are... Brrr!), concurrency, ...

          I could go on. I don't enjoy it, although I like simple and easy things.

          C is simple, but not easy

  • sgt 2 hours ago

    Can't wait for Swift to gain traction in systems programming.

  • andrewmcwatters 2 hours ago

    I feel a sort of professional obligation to learn the cryptic unnecessary glyphs and runes that Rust and its ilk proliferate all for this important concept that is memory safety.

    However, I would much rather that more languages like Go and Zig simply take off in popularity instead, and that we just reject the eyesore and cognitive mess that is Rust syntax. It’s a language which has no regard for beauty.

    • jcrites 2 hours ago

      For what it's worth, I disagree. I think it would be difficult to design a language more elegant than Rust while accomplishing the same goals. Maybe those goals aren't ones that everyone cares about, and that's OK.

      It's a language that's definitely worth learning; it expands the mind in a way that languages like Go (or Java, or any GC language) will not. There is a great beauty and elegance to its design.

    • OtomotO an hour ago

      I would love a much simpler Rust...a Zig with a borrowchecker, a small stdlib (like Rust), some official package manager.

      I agree that Rust has a lot of syntactic warts but the common stuff I find beautiful

  • handwarmers 2 hours ago

    brace yourself. rust people are coming.

    • whatshisface 2 hours ago

      If you aren't nice to rust people, they'll get RCE after overflowing your buffers and make you a furry.