Rust – Compile Time Memory Safety

(kkimdev.github.io)

175 points | by kkimdev 1827 days ago

6 comments

  • amalcon 1827 days ago
    The author says that this is "why Rust is interesting". I've done a little bit with Rust -- no production code, but some simulations and things like that. While compile time memory safety was the first thing to interest me in Rust, I don't even think it's the primary selling point anymore. The selling point to me is that nearly everything behaves in predictable ways.

    E.g. since immutable references are the default, you immediately know whether or not a call will modify its argument. E.g. the existence of traits and compiler warnings for style mean that method naming conventions are incentivized, because implementing a trait usually gives you a lot for free. Even performance characteristics are very predictable, a statement which I can only otherwise make about C and assembly (and those are not predictable at all in other ways).

    Yes, the memory safety does play into this. It's much more than that, though: memory safety is just one factor in a kind of pervasive predictability that the language encourages through its very design.

    • estebank 1827 days ago
      The biggest selling point for Rust is a non-obvious one and one that is hard to "sell" with screenshots: refactoring. Despite having few tools for automatic refactoring, manual refactoring of large Rust codebases is a breeze: change the portion of code you care about, follow the compiler's complaints and by the end you're likely to have a codebase in a good state. I wouldn't be able to go back to a dynamic language after using Rust for so long, I'd be in constant panic about changing the smallest thing. I equate it to the same discomfort I feel when getting on a car with no seatbelts. I'm just waiting on the day when I can plug the change I want to do to rustfix and let it figure it out, with the help from the compiler.
      • mzl 1827 days ago
        > refactoring of large Rust codebases is a breeze: change the portion of code you care about, follow the compiler's complaints

        At my $DAYJOB I write a lot of Java, and modern full-featured IDE's for Java like IntelliJ are absolutely awersome for refactoring. Most common refactorings are fully automated, and many more complex things are very easy to do by combinding some of the base refactorings.

        I really like writing Rust in side projects, but after getting used to the phenomenal support refactoring support in Java, having to follow the compilers diagnostics feels very primitive.

        • Sahhaese 1827 days ago
          Jetbrains' refactoring tools have always been top tier. For years resharper was absolutely essential for C# development because visual studio just lacked good refactoring. VS has been catching up and has mostly caught up in recent editions but I doubt that would have happened if not for MS being able to see just how popular resharper is.

          I'd hope that as Rust grows in popularity the tooling will come from that so I'd keep an eye on the intelliJ rust plugin or rider support for rust because as you say, it's really difficult to go from a day job where for example promoting a local variable to a parameter (with replacement at the call sites) just works to one where you actually have to think about manual refactoring.

          • int_19h 1827 days ago
            One reason why I'm optimistic about Rust is because the team not only explicitly recognizes the need for advanced tooling (such as IDEs), but owns critical parts of the underlying infrastructure, such as RLS. If you want a language to have quality tooling, it needs to be designed for that, and there's no better forcing function for that than having to write that tooling yourself, or at least having someone who does on the same team, in constant communication.
          • 0815test 1827 days ago
            > I'd hope that as Rust grows in popularity the tooling will come from that ...

            Patches are welcome! https://areweideyet.com/

          • mzl 1827 days ago
            The Rust plug-in, is already quite nice, and getting better all the time. I use it from CLion, since that has integrated debuging support.
        • capdeck 1827 days ago
          You are mixing "fancy tools for mature language" with core capabilities of another (that will enable even better fancy refactoring tools in future - tools that can make more complex refactoring tasks fully automated). If anything, the fact that what used to be an external tool has migrated to core feature in Rust is a testament to how forward looking the language is. Imagine what that will enable in tools to come!
          • mzl 1827 days ago
            I'm not implying that Rust should have these yet; I am fully aware that the situation for Java is different due to serious and long efforts in developing great refactoring tools.

            The point I was trying to make is that the view that Rust is great for refactoring depends heavily on what one is used to. For example, as compared to any dynamically typed language, a strongly typed language will be better for refactoring.

            Writing refactorings that work reliably is very complex, and I'm not sure if it is practically possible to write the same kinds of refactorings that Java has for Rust. Two of my favourite refactorings in IntelliJ are extract method (with automatic duplication finding) and inline method/expression. I think that such refactorings are a lot more complex to do reliably in Rust due to ownership and borrowing rules, unnameable types, and so on.

          • estebank 1827 days ago
            I fully expect rustfix to gain refactoring capabilities, even if haphazardly bolted on. Things like changing an owned field to become a borrow requires a lot of trivial changes that can be automated away (other than making sure the field been borrowed has an appropriate lifetime, it course) that for the most part rustc already emits as structured suggestions that other tools can use.
        • EugeneOZ 1827 days ago
          IntelliJ plugin for Rust can do some refactoring (renaming functions and variables - it's all I use and remember).
          • mzl 1827 days ago
            For Rust development I use CLion with the Rust plugin, and it is getting better and better all the time.

            It is interesting seeing the difference in peoples expectations on what an IDE can and should be able to do for you depending on what they are used to working with.

      • ajxs 1827 days ago
        I'm really glad to see that there is a huge resurgence in support for statically typed languages in mainstream programming. I always felt that this was somewhat inevitable, but the tide of opinion was against it for a while. I completely agree, and the inverse situation is true too. If you're used to statically typed languages, working with a dynamically typed language can be hellish if your codebase is suitably complex! And by suitably complex I mean more than >500LOC. ( That's not a typo, the omission of the 'K' is intentional. )
        • arcticbull 1827 days ago
          I'd love your thoughts on this, my guess is it's because of ergonomics improvements. Specifically, inferred types. This middle-ground lets you have much of the ergonomics of a dynamic language but with the safety of a static one. You may have to hint it from time to time, but for the most part (99%+) Rust guesses the types of everything I do day to day, and when it doesn't know, it ... asks.
          • ajxs 1827 days ago
            Inferred typing is just a feature of statically typed languages. Static typing just refers to the type of a variable being known at compile-time. Type inference is the ability of the compiler to infer the variable's type from the context of its declaration. It doesn't mean that there's any loss of safety at all, it's just a matter of syntax.
            • arcticbull 1827 days ago
              Yep, exactly; I was suggesting that the move in modern languages towards inferred typing is what makes statically typed languages pleasant to work in, which has lead to their return to popularity. Sorry if I wasn't clear!
              • ajxs 1827 days ago
                Oh, pardon me. I re-read your post and it makes more sense to me now! Maybe that plays a role. I more attribute this, somewhat in jest, to enough time having elapsed since the renaissance of dynamically typed languages began for developers to have gotten sick of maintaining codebases built in them. I also, very cynically, think that there's a very contrarian and competitive streak running through the development community which drives developers to always seek to move against the majority. Cultivating a kind of heroic self-assertion in the process. Think, for example, of the resurgence of interest in functional programming and the aggressive proselytisation on its behalf. In defence of my polemic assertion here, I have somewhat cultivated this idea around myself and my own shortcomings. Also, even when I program in languages featuring type inference I usually mandate that my team use explicit type declarations for the sake of clear declaration of intent and better tooling support, that's just one opinion there though.
          • steveklabnik 1827 days ago
            In some sense. I mean, type inference is not new. But the languages that really brought static typing to the masses didn’t use it, or only did deduction, so it is “new” to a lot of programmers.

            I do think that there’s a relationship between power and usefulness, but more power isn’t always better. I’d rather use Ruby than Java 1.5, but I’d rather use Rust than Ruby. YMMV.

            • typeliftr03 1827 days ago
              It's kind of sad to me that (almost) complete type inference has been around since the [70s](https://en.wikipedia.org/wiki/Hindley–Milner_type_system) and it's still not mainstream. (unless you consider F# or OCaml mainstream languages)
              • pkolaczk 1827 days ago
                Type annotations at the signature level are documentation, so I'm not so sure if global type inference is really better than local type inference. Also HM type inference doesn't play nice with type systems with subtyping or method overloading.
                • int_19h 1827 days ago
                  OCaml does have subtyping (albeit with a clear separation between that and implementation inheritance) and method overloading, but its type inference accommodates those. Or am I missing something?
                  • pkolaczk 1826 days ago
                    Seriously asking: How does it work for undecidable cases? Nominal subtyping may cause ambiguity where both supertype and subtype match. Overloading may cause ambiguity where more than one overload matches, and depending on the overload we can infer different types.

                    BTW I didn't say it was impossible, only that it doesn't play nice and probably is no longer a pure HM system. The search space is definitely much bigger with overloading and subtyping.

                    • int_19h 1824 days ago
                      It still has classes, and implementation inheritance with method overloading, but they're completely decoupled from subtyping (so e.g. subclasses are not necessarily subtypes - a class can use self-type in an argument position). And types themselves are structural, with row variables and polymorphism. If you need to define an equivalent of a named interface, you just define a type alias, like any other. And when you define a class, you get an auto-generated type alias for the structural type of objects that it will instantiate. [1]

                      End result actually looks surprisingly familiar to someone coming from C++ or Java, at the first glance. It's really a shame that it's not something that OCaml is famous for; it deserves to be. I wish I could have an object model that expressive in C#.

                      [1] https://caml.inria.fr/pub/docs/manual-ocaml/objectexamples.h...

      • maxxxxx 1827 days ago
        “change the portion of code you care about, follow the compiler's complaints and by the end you're likely to have a codebase in a good state.”

        That has always been my refactoring algorithm with C++, C# of TypeScript now. I know a lot of smart people using dynamic languages but I always prefer to lean on the compiler to tell me what needs to be changed.

      • burk96 1827 days ago
        > I wouldn't be able to go back to a dynamic language after using Rust for so long, I'd be in constant panic about changing the smallest thing.

        As someone who has been working mainly in Rust for about a year and is now starting to pick up Python, this is huge! I pretty much never write Python that isn't explicitly typed thanks to how Rust changed me as a programmer.

        • arcticbull 1827 days ago
          Same. I think Rust has been the biggest thing for me in terms of growth as an engineer in recent memory, and I've worked across the stack from HDLs to embedded to mobile to desktop to server over the course of my career.
      • mehrdadn 1827 days ago
        I think the question was what the biggest selling point is for those who already do e.g. C++, not for those using dynamic languages like Python.
        • 0815test 1827 days ago
          Both questions are important, though. The Rust community is in a very compelling position, in being able to appeal to both the C/C++/'system programming' and the Python/Ruby/etc/'high-level application programming' dev communities.
          • mehrdadn 1827 days ago
            What I was trying to say was that ease of rafactoring compared to a dynamic language like Python isn't omitted because it's "non-obvious". Rather, it's in fact quite obvious, and simply omitted because it's answering a different question than the one that was asked. Robust refactoring isn't Rust-specific or even new. It's always been the selling point of many statically typed languages, like C++, D, Java, etc.
            • estebank 1827 days ago
              I mentioned Python because it sits in the other extreme, but the combination of pattern matching, strong types, type inference and ergonomic combinators on the standard library makes the experience much nicer than, for example, Scala. Robust refactoring isn't new. Rust isn't novel. It's just well put together because it had the benefit of hindsight and being able to consider how different advanced features fit together without having to abide by backwards compatibility until recently. Refactoring a single threaded process that operates over a vector can be a single line code change, and the compiler is capable of complaining about data races by leveraging the type system and lifetime analysis.
              • kod 1827 days ago
                Much nicer than scala? I don't see how you can claim that if you've touched futures in both languages.

                Rust is a nice language for threaded programming, but the async situation is a trainwreck 4 years in the making.

                • estebank 1827 days ago
                  I have touched both and async in Rust is indeed half baked at the moment. That a factor of immaturity of Rust and maturity of Scala. I also had a Scala codebase that had 4 different Future classes, due to it having been an early adopter.

                  I was thinking if the language itself, where it needs to keep backwards compatibility with Java (the distinction between case classes and classes, for example) and over engineering (implicit action at a distance all over the place). I think Scala is a nice language buried in way too much syntactic sugar. Interestingly enough, Rust's syntax doesn't bother me but quite a few people seem out off by it.

                  Rust is new and it advances slowly but at a steady state. Given its immaturity in some areas I can only go off potential and the async story seems to have a bright future, despite it's somewhat underwhelming present.

                • 0815test 1827 days ago
                  The async situation for Rust is very much in flux, of course - see https://areweasyncyet.rs/ for details and updates. It's definitely a pain point at this time, and stability itself is an important value so I can see this making Rust less desirable for some - but calling it a "trainwreck" that the Rust community is supposedly to blame for just seems pointless, other than as snarky flamebait. That sort of comment is a good fit for `rustjerk`, not for the sort of substantive discussion we seek on HN.
        • leshow 1827 days ago
          I think you'd be surprised, Rust has seen a lot of adoption & interest from the dynamic languages crowd.
          • jchw 1827 days ago
            Go and Rust killed my interest in writing Python code. A paradigm shift worth losing amazing libraries and frameworks like SQLalchemy and Django is rare, but I believe we are here (and have been for a while.)
            • mehrdadn 1827 days ago
              > Go and Rust killed my interest in writing Python code.

              Strange to see Go suddenly brought in to support Rust like this. Was it Go that killed said interest or Rust? Why is Go relevant here?

              • jchw 1827 days ago
                For the same reasons: it offers a strong alternative to C/C++ with memory safety. Often I want performance like C++ but cleaner code and memory safety. Before recently, Python was a go-to when you didn't want to cut yourself on C++s sharp edges, even with it's disadvantages. The overlap isn't clean, but Go is strong for writing simple programs and glue layers, and Rust is strong for writing complex software where correctness and performance are both very important.

                Python still has a fair bit of expressive power that Go and Rust would struggle to provide, but I think both can be compelling alternatives in different cases, and they offer a lot in exchange (such as, full type safety and improved performance.)

                I realize Rust offers much more safety than Go, but both of them are eons ahead of C++ in this regard. When you're starting from nothing, any level of guarantees is a huge shift.

                • blub 1827 days ago
                  You're confusing "correctness" with not crashing because of invalid memory access, which is a low bar to clear.

                  There's really no proof that systems written in Rust have better reliability compared to systems written in Java, Typescript or PHP, is there? I've recently had the same discussion with someone claiming that FP offers stronger correctness guarantees than OO languages: where's the proof?

                  Furthermore, Rust doesn't offer "much more" safety than Go either, in fact Go's GC suggests that it's harder to make memory handling errors in Go compared to Rust. After all Rust's still vulnerable to memory leaks.

                  Finally, comparing C++ and Rust reliability is much more nuanced than your dismissive attempt. Modern C++ written by an experienced team + good tooling can have really good memory safety, not as good as Rust, but good enough so that the other advantages of C++ can tip the balance in its favor. The problem: many projects don't use modern C++, or they don't have experienced devs or they neglect the tooling. But here's the kicker - those same teams are unlikely to use Rust anyway.

                  Here I expect Firefox or Chrome to be given as counter-examples to which I's say that it's likely impossible to write a memory safe browser in C++, due to the project dynamic. Remains to be seen if it's possible to do it in Rust.

                  • capdeck 1827 days ago
                    > But here's the kicker - those same teams are unlikely to use Rust anyway.

                    Absolutely disagree. Those are exactly the teams that will choose Rust over C++. It is well known that many shy away from writing plugins for node or python in C/C++ specifically due to the fact that they don't have enough experience with these unforgiving languages. The gap between Python/JS and C/C++ is wide and deep. But the use case is still there. If you have to improve performance of a component - Rust provides a lower barrier to entry and will be chosen by such teams.

                    • blub 1827 days ago
                      Erm, the teams that are using C++ and having reliability issues due to lack of memory safety are the ones that will choose Rust?

                      In my experience they just fix their bugs and move on with their lives. Probably you were thinking of some other teams, since you're mentioning plugins for Python.

                  • jchw 1827 days ago
                    >There's really no proof that systems written in Rust have better reliability compared to systems written in Java, Typescript or PHP, is there?

                    I am not here to prove mathematically that Rust results in more correct programs than PHP. I am here to claim that I believe that it does.

                    To me one of the biggest obstacles to writing correct code is not just memory safety. It's preventing unintended uses of code. It's handling every case.

                    PHP is a memory safe language, but that does not mean it is equally good for writing complex software. Why? Because:

                    - Exception and error handling in PHP, including the standard library, is inconsistent and tricky to get right. In Go and Rust idiomatic error handling is pretty easy to get actually correct.

                    - Code is not only not type safe, but you might not even notice when you violate type safety. Many things that are runtime errors in Python and compile time errors in Rust or Go are incorrect behavior in PHP.

                    - The TypeScript, Rust, and Go compilers and toolchains are more rigid, because more strict languages make that possible. For example, if you fail to return in every possible code path of a function, that's an error. In TypeScript, any unchecked possibly null access is an error when using strict nulls. Many more examples exist.

                    >Finally, comparing C++ and Rust reliability is much more nuanced than your dismissive attempt. Modern C++ written by an experienced team + good tooling can have really good memory safety, not as good as Rust, but good enough so that the other advantages of C++ can tip the balance in its favor.

                    OK, it feels like you're just being contrarian now. I've made this same argument before as a reason why we shouldn't all just rewrite everything in Rust.

                    I've worked on a fair bit of large C++ projects before. My current employer, Google, does quite a great job with the C++ toolchain, and admittedly while I do not have as much experience with it as I do C++ outside of Google, I'd say it's close to the best I've ever seen.

                    And even then... It's ass ugly, the errors are confusing as hell, I can't understand what the hell is going on in some of the macros, and it's still possible, with metric tons of safeguards, to fuck up and segfault (guilty.)

                    Something C++ fans (including me) love to point out is that if you enforce the use of smart pointers you effectively can prevent whole classes of memory issues. In reality I'm pretty sure everyone knows that's a bit of an exaggeration. It's still pretty easy to misuse a smart pointer. And on top of that, smart pointer syntax can be confusing. I used to be a huge proponent of operator overloading but after not having it for a while I'm pretty sure it has a tendency to turn verbose code into confusing but terse code.

                    C++ just has too much fucking syntax. And it has templates. And on top of that syntax it has text macros. And even if you ban new macros, you still need macros for very basic things like "logging with line numbers". And it's useful for things where none of the other available tools solve the problem. So good C++ code probably ends up using a lot of macros even if most of it is not defining new ones.

                    People have made C++ fault tolerant as much as possible because they have to. There's too much C++ in the world to stop and rewrite it all in some memory safe language. No amount of language debates on Hacker News will ever change that.

                    But, we should not let that cloud the fact that better C++ doesn't come close to providing strong safety guarantees, and it sure doesn't prevent obscene looking code. As much as people will hate Gofmt, I've never seen uglier code than some of the edge cases as handled by clang-format. (If you know of a C++ formatting tool that's any better, please tell me.)

                    So does Rust result in more correct code? Does Go? I believe so yes. For starters, C++ error handling is atrocious. Some people use exceptions, some people don't. Google uses StatusOr[1] and I think the best case would be something like Andrei Alexandrescu's expected<t>. In reality you get a mix of custom status codes, C style error handling, weird OOP style error handling where objects contain error statuses (especially when you can't throw in a constructor.) Even at Google there are third party libraries, with both C and C++ APIs, so there's still some mismatch that has to be dealt with. The standard library is just as guilty here.

                    How about safety? Even with improvements it's really hard to make a subset of C++ where you can't accidentally segfault. Sometimes, you have a smart pointer and you need a regular pointer. So what do you do, dereference? Well sure... But now what are you thinking about? Lifetimes. And thus, the value proposition of Rust. Go solves the lifetime problem with garbage collection, but in C++ you just have to manually, as a human, figure out what lifetimes are expected, and in the meantime someone could submit code that subtly changes this and when you submit your code now head is broken even though neither person made a mistake.

                    The list really could go on for a long time. And I expect every point here to be disputed because there's some way to mitigate the problem. Reflect for a moment, though, that all the energy spent trying to mitigate problems like these could've been spent on writing better code elsewhere if it weren't for the shortcomings of the language.

                    1: http://www.furidamu.org/blog/2017/01/28/error-handling-with-...

                    • blub 1827 days ago
                      I very specifically said reliable systems, not functions, libraries and so on. So you're claiming that Rust systems are more reliable than PHP ones, another person's claiming that Haskell's more reliable than C++ and yet another that Go is more reliable than Python.

                      Once again, where's your proof? You know, studies, experiments, demonstrations comparing real-world (or even dummy) projects and concluding the above? Because gut feelings aren't going to convince me, or any other questioning individual.

                      Almost all these claims, including yours (not surprising at all) revolve around the idea of encoding relationships and allowed operations in the type system. I'm calling this "reliability in the small", or the ability of writing statements and functions which are restricted from performing specific non-desired operations. These do have a positive effect in my opinion, but not enough to result in more reliable systems overall. A pretty well known tenet of safety engineering is that putting together reliable components does not mean one gets a reliable system.

                      Frankly, I think it's other things that really make a difference: thorough code review, formal specifications, design by contract, automated testing, taking time to gather requirements and design things.

                      I'm not going to spend time addressing your points regarding C++, because let's be honest, you wrote way too much :-/ In principle you're correct to say that it's easier to write memory-safe Go or Rust code. C++ requires more effort to get there and C++ teams tend to prefer performance to safety.

                      It is not easier to write correct Go or Rust code compared to C++. Memory access violations aside, there are many more bug classes which aren't prevented by any of those languages. Rust is constantly being oversold, there's nothing that makes it any better than say Java or Swift, on the contrary, the former are much easier to pick up.

                      • jchw 1827 days ago
                        >Once again, where's your proof? You know, studies, experiments, demonstrations comparing real-world (or even dummy) projects and concluding the above?

                        I explicitly said no. I'm not here to fulfill your personal requests. If that's what you want, Hacker News comments are probably the wrong place to ask, and I'm not personally interested in conducting such studies. I'm already convinced, so it wouldn't really benefit me much.

                        >Because gut feelings aren't going to convince me, or any other questioning individual.

                        OK. This isn't gut feelings though. It's a discussion about subjective matters.

                        If that was too much writing, then so is any meaningful discussion about the differences between programming languages. I'm not going to attempt to discuss this further.

                        • blub 1826 days ago
                          The whole point of my initial reply was to challenge an oft repeated belief that using FP results in more reliable systems. This belief doesn't really have solid proof behind it.

                          If you're not providing that proof because it's beneath you, or for some other reasons you've really just wasted everyone's time here by repeating the same appeal to feelings and personal experience for the Nth time.

                          I addressed the core of your arguments against C++. The rest has been discussed before by myself and others. It's nothing new on HN.

              • 0815test 1827 days ago
                Go has garbage collection support, so it's definitely more convenient in some specialized domains than Rust - namely, those which inherently involve spaghetti-like allocation and "ownership" patterns that RC alone can't deal with very well. You wouldn't want to rewrite a typical LISP codebase in Rust, but with Go you could make it work and get good performance out of it.

                (You could of course use a ECS-like pattern in Rust and end up with a half-baked reimplementation of a Go or LISP runtime, but the ergonomics would not be there.)

              • xondono 1827 days ago
                Another point is that using rust is great for products, but not so much for PoCs, since the compiler will complain about most ‘hacks’ to release early, or even refuse to compile.

                I have not much experience with go, but my feeling is that it should be faster to have something working, and then if it’s worth it (e.g. if it needs high performance with concurrency), migrate to rust.

                • capdeck 1827 days ago
                  Yes and no. Yes, Rust requires more "mental cycles" to think upfront about the design, even of a simple tool. But, as they say, nothing is more permanent than temporary. A tool that you write as a one off, before long, ends up running critical infrastructure in production.

                  We all have been in the situation where we quickly put something together. With Rust -- you internalize some of the things you know the compiler will demand -- so tenth time you write something in Rust will be faster. But the beauty is that Rust compiler will force you to _gradually_ fix the design of your tool and the extra time you spend fixing it pays dividends when this code eventually becomes something everyone relies on.

                  Coding in Rust is like an investment: it requires some commitment and slows you down initially (for the right reasons!), but pays huge dividends and return grows exponentially over time.

                  • xondono 1827 days ago
                    > Coding in Rust is like an investment: it requires some commitment and slows you down initially (for the right reasons!), but pays huge dividends and return grows exponentially over time.

                    Kind of my point. Sometimes you don’t want to make an investment, sometimes you just need something to show.

                • dralley 1824 days ago
                  You can cut corners plenty easy. .unwrap(), .expect() and so forth. The great thing about that is, all the places where you cut corners are blatantly obvious and searchable.
      • DCoder 1827 days ago
        This is one of the big reasons I use TypeScript over plain JS as well, especially when combined with TSX for typesafe views.
    • cttet 1827 days ago
      Actually I got into rust because it is the only language that has both ADT/patten matching and a practical mindset/community. There is simply no alternative. I would even code front-end rust for the sake of this.
      • revskill 1827 days ago
        Reasonml also has adt and pattern matching
        • cttet 1826 days ago
          True, and swift. They are nice to work with too.
    • danieldk 1827 days ago
      E.g. since immutable references are the default, you immediately know whether or not a call will modify its argument.

      You do not, since the type of which an object is referenced could use interior mutability (e.g. RefCell). So, you only know if this is the case after inspecting the type.

      Of course, the default in Rust is to use exterior mutability and in general, interior mutability is/should be avoided.

      (I agree with the general thrust of your comment though.)

      • pornel 1827 days ago
        Rust's mutable/immutable is better thought of as exclusive/shared. But in any case, the interior mutability is not causing problems, because the compiler still largely enforces it's used correctly (e.g. no matter how deeply RefCell is hidden, code using it won't be able to share it with another thread, so you don't need to worry about getting a data race this way).
    • kkimdev 1827 days ago
      I fully agree with you, and actually I want to write a summary of those less-discussed features that have high productivity impact including the predictability you mentioned in the future.

      Though, for the people who haven't explored Rust yet, I still think that focusing on the memory safety, the most powerful feature, is a good approach. Personally I tried explaining other smaller benefits first, e.g., immutable by default, move by default, no header files, but didn't work well as I thought. Exploring another language is a significant investment, and people need a significant reason (at least those that appear to be at first glance).

      • estebank 1827 days ago
        And there is a small marketing problem for Rust. The memory management is the big ticket thing we like to show off, but the big benefits of the language are much smaller and boil down to quality of life and composability.
        • ncmncm 1827 days ago
          Yes, banging on about memory safety sells Rust short, and drives away exactly the people who would benefit most from improved memory safety.

          The language is just overwhelmingly better to code in than C, or Java, or C#, or Go. If the compiler were to be made fast -- and there is nothing like a fast compiler coded in your language to advertise its speed (and the reverse!) -- or anyway JITted, with a REPL, it could replace a great deal of scripting.

          The comparison to C++ is much less compelling. Rust fans like to lump C and C++ together, but in modern C++ there are few temptations to memory unsafety. (They might be misled by crufty Mozilla code.) Meanwhile, the greater expressivity of C++ enables more powerful libraries, and each use of a good library eliminates many more than just memory bugs.

    • steveklabnik 1827 days ago
      I agree with you, for sure. We had a huge community discussion about this in 2016: https://brson.github.io/fireflowers/
    • leshow 1827 days ago
      Rust has a great type system that enables the kinds of things you're talking about. It's like c++ and haskell came together and had a baby.
  • millstone 1827 days ago
    > Rust enforces single mutable ownership or multiple readonly aliases at a time. In fact, they are very good idioms to structure large codebase anyways, and normally they do not get in the way for ordinary applications.

    No these limitations routinely get in the way for ordinary applications. The borrow checker is a source of frustration when ramping. Back-references get smuggled in as array indexes. Prohibiting global variables is tough. Any sort of app that can't be structured as a tree is going to have pain.

    This safety is really valuable, but let's not pretend it comes for free.

    • 0815test 1827 days ago
      The author is somewhat mistaken there - what Rust actually enforces is a clear alternative of exclusive ownership/borrowing, or shared access with multiple aliases being active at the same time. While these are normally identified with "mutable" vs. "readonly" access, this is not true in some cases, where special structures with "interior mutability" can be provided with different behavior. For example, if you need to share writable access to a piece of data, you can use the "Cell<>" or "RefCell<>" generic types. For an object which needs to have multiple "owners", each of which can extend its lifetime and prevent the object from being freed, there is the Rc<> type, etc. This stuff may not come for free, but quite often its cost can be made very reasonable while preserving desirable safety properties.
      • millstone 1827 days ago
        The performance cost of interior mutability is often small. IMO the real cost is the undesirable safety properties.

        For example consider an API like JS's getElementById(). In Rust, if a caller frame has a reference to the same element, this would just panic. It's impossible to statically enforce that no caller can have a reference to this element, and it's unreasonable to require it at runtime. So you either give up safety guarantees (viable, e.g. gtk-rs, but it leaves Rust anemic) or you give up the entire programming model (maybe viable, still a research project).

        • Rusky 1827 days ago
          getElementById() is perfectly allowable under Rust's rules, though it may not look like what you expect (and to be fair Rust doesn't make it easy to write today).

          The most straightforward way to get it working would be to return a `&Element` (or `Rc<Element>` if you like), and make all of `Element`'s fields `Cell`s. No panics, no runtime checks, and you can do everything JS can do. The cost is "infecting" the type definitions with `Cell<T>` and the usage with `.get`/`.set`, and the loss of what is normally rich pointer aliasing information for the optimizer (but which other languages don't have to begin with).

          The reason Rust `&mut` feels so restrictive is that it allows you to change the "shape" of the object, thus invalidating (if they existed) any other references into it- replace an enum value with a new variant, reallocate a `Vec`, overwrite a `Box`, etc. But in other memory-safe languages you can't do any of those things. Instead any "shape changing" is done by allocating a new GC'd value and overwriting a pointer- enums are boxed (or don't allow interior pointers), arrays only contain primitives or pointers, etc.

          So I like to think of `&Cell<T>` as a kind of third reference mode, that matches what people expect from other languages. It's not fun to use today, but there are a couple of language additions that could make it much, much nicer:

          * First, field projection- given a type `struct S { x: T }` and a value `r: &Cell<S>`, let `r.x: Cell<T>`. This is safe as you can't invalidate `&r.x` by overwriting `r`- but by extension you can't project a reference through a `Cell` into an enum or `Vec` (just like other languages as described above).

          * Second, some syntactic sugar for reading and writing. Replace `cell.get()` and `cell.set(x)` with `cell` and `cell = x`. Given that `Cell` has zero overhead (other than the loss of optimizations described above) this shouldn't be an issue.

          • devit 1827 days ago
            The more idiomatic way would be to return an Rc<RefCell<Element>>.

            Accessing it will indeed panic if someone else has an active mutable reference to the contents of the refcell, but the idea is that you should only keep that for the section of code that actually modifies the object.

            The reason this feature exists is that it prevents code observing partially modified objects that could be temporarily missing required invariants (in addition to preventing mutating object with references to parts of them which can result in dangling pointers and thus memory unsafety).

            You can also use Cell as the parent argues with the benefit of never failing, but then you don't get protection from recursive calls exposing violated invariants and you need to change the implementation of Element itself.

    • pornel 1827 days ago
      To me it was the opposite: it gave me a vocabulary and taught me how to think about these problems.

      Shared mutable state and ownership exists in C, but I just don't get any compiler support for it. I can't even document it in code, so I (and users of my libraries) rely on RTFM.

      In C I'd just "wing it", and tweak the code until it stops crashing. Maybe add a flag with "obj.free_data_ptr = true" and keep adding mutexes or copies of data where I suspect it's necessary.

      In Rust I get predefined templates for this — owns & borrows, cells/atomics, refcouted and mutex containers, etc. The compiler says "nope, this is wrong!" and I get to conciously decide how to solve it — do I share or copy the data? Is the sharing dynamic, or just in a wrong scope? And my decisions are documented in code, and enforced by the compiler.

  • foota 1827 days ago
    Isn't the example with re-using a moved c++ vector completely safe?

        std::vector<int> x = {1, 2, 3};
        process(std::move(x));
        x.push_back(4);
        // Runtime invalid memory access
    • kkimdev 1827 days ago
      Oh, I didn't know about this one. I did some search, and looks like it should be "valid but unspecified state" after move?

      I can't think of any case that relying on unspecified state is desirable even if it's valid, though I guess it's better if I change that to x.pop_back(); to be clear.

      Please let me know if my understanding is incorrect and thanks for the information!

      • lmitchell 1827 days ago
        The only thing 'unspecified' guarantees you in this context is 'safe to destroy'. It specifically does not guarantee the safety of any other member functions - only the destructor. So either push_back() or pop_back() could potentially cause UB here (specifically, it's quite possible that the move swaps some internal pointers for nullptr, so you end up dereferencing null here - but morally, it's just never okay to continue using a moved-from object).
        • millstone 1827 days ago
          The vector is "valid" and that is what carries all the weight here. The vector is still a vector.

          push_back is absolutely defined. pop_back might be undefined, because pop_back is UB on an empty vector. If you like, call clear, and be assured of an empty, reusable vector. It's not idiomatic, but it's safe.

      • pbsd 1827 days ago
        A moved-from std::vector<int> will always be empty. However, a moved-from std::vector<int, custom_stateful_allocator> may not be.

        Howard Hinnant had a Stack Overflow reply a while back going through the possible corner cases of this precise question: https://stackoverflow.com/a/17735913

    • marcianx 1827 days ago
      Yes, it's completely safe and its rust equivalent is

          let x = vec![1, 2, 3];
          process(std::mem::replace(&mut x, Vec::new()));
          x.push(4);
      
      https://doc.rust-lang.org/std/mem/fn.replace.html
      • khuey 1827 days ago
        There's no guarantee that std::move moves elements out of x. (and thus no guarantee that it's equivalent to a Rust mem::replace with a new Vec) The only requirement the C++ standard imposes is that the vector is left in a "valid but unspecified state".
    • bluejekyll 1827 days ago
      It’s runtime safe (I’m taking your word for it). In Rust this would be a compile time error.

      Although, are any C++ compilers able to at least issue a warning in this case? It wouldn’t surprise me if they could.

    • zelly 1827 days ago
      Move constructors are allowed to call the moved object's destructor.

      Not necessarily like the example given, but

        Object a {std::move(x)};
        x.push_back(4);
      
      could/should segfault.
      • ncmncm 1827 days ago
        No, move constructors are never allowed to call the moved-from argument's destructor. Ever.

        Sometimes the compiler calls that destructor after it has finished the move, if the thing is no longer in scope. That should not be confused with a thing happening in the constructor.

    • millstone 1827 days ago
      Yes it is safe.
  • Const-me 1827 days ago

        int x;
        int y = square(x);
        // Passing a garbage value at runtime.
    
    My C++ compiler doesn’t build that:

    error C4700: uninitialized local variable 'x' used

    • int_19h 1827 days ago
      That's because you're compiling with warnings-as-errors.

      And even then it's still unable to detect anything but the simplest case. E.g. this compiles with no warnings:

         void foo(int& x) {}
      
         int x;
         foo(x);
         int y = square(x);
      • Const-me 1827 days ago
        > you're compiling with warnings-as-errors.

        I'm not. The compiler is VC++ with default project settings. "Treat warnings as errors" setting is set to "No (/WX-)"

        By default, VC++ compiler turns on this for new C++ projects: https://docs.microsoft.com/en-us/cpp/build/reference/sdl-ena... That's why it fails to build.

        • int_19h 1826 days ago
          C4700 is a warning per VC++ docs:

          https://docs.microsoft.com/en-us/cpp/error-messages/compiler...

          So by definition, if it's reported as an error, you're compiling with warnings-as-errors enabled, at least with respect to that one warning.

          The default project settings you refer to are Visual Studio defaults (for new projects created from a template), not VC++ defaults. If you invoke cl.exe directly from command line on your code, you'll see C4700 being reported as a warning, but it still produces the binary.

          /WX is a switch to treat all warnings as errors, but it's not the only switch that controls warnings-as-errors behavior - you have all the other /W... switches, from level-based approach to setting it for specific warnings. And /sdl is (in addition to all the other things it does) an alias for a bunch of those switches. Indeed, on the very page you linked to, it literally says: "/sdl enables these warnings as errors ... C4700". And you can even override that by doing something like /sdl /wd4700.

  • IshKebab 1827 days ago
    This is a really nice way to explain the additional level of safety you get over C++.
  • lincpa 1827 days ago
    Rust implements safe and efficient programming by simulating bank lending practices. This is the first time in the programming language world to reference financial models at the language level, but unfortunately, Rust language designers are not proficient in financial knowledge and have made mistakes in core fundamental issues. The relationship between borrowing and lending is not a buying and selling relationship. It is caused by the transfer of resource use rights, not the transfer of ownership. It causes a series of semantic errors and cannot fully and correctly refer to the financial system knowledge, making its programming too complicated.

    The financial system is the safest, most stable, most rigorous, largest and most tried-and-tested system in human history. From ancient times to today, male, female, old, young, wise, stupid, positive, evil, good, evil, With the participation of the whole people in the game, Rust only learned a little bit of fur and took the wrong knowledge, but he also achieved great success.

    Therefore, I suggest that computer science should classify financial knowledge as a compulsory course, which has great reference significance for building a safe, efficient and stable system.

    from: https://github.com/linpengcheng/PurefunctionPipelineDataflow...

    • int_19h 1827 days ago
      The use of terms like "ownership", "borrowing" etc with respect to object lifetime management long predate Rust. You may not like the way they are defined in this field, but it's a well-established definition. And it is not intended to have much to do with "bank lending practices", other than a very rough analogy that's easier to explain.
    • ncmncm 1827 days ago
      Somebody should tell all the people made homeless by the banking crash of 2008 how uncrashable the banking system is.
      • lincpa 1827 days ago
        In order to gain more benefits, the use of unsafe technologies is a risk cost. There is a presence in both the financial and IT sectors.