1. 8
  1. 6

    Really enjoying this article for its historical analysis of the earlier research languages that inspired Rust’s ownership & borrowing rules. Also there are some great quotes from Rust users, like

    “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.”


    “I can teach the three rules [of Ownership] in a single lecture to a room of undergrads. But the vagaries of the borrow checker still trip me up every time I use Rust!”

    But the really interesting quote, to me, is

    They randomly assigned students to two groups, one having to complete the assignment using the Rust standard library data types, and one using a garbage-collected wrapper type (called “Bronze”) which enabled a number of additional aliasing patterns to pass the borrow-checker, thus removing the needs for more complex aliasing patterns and datatypes.

    They found a significant difference in the rate of completion and the self-reported time to completing the assignment. The students who used Bronze on average took only a third as much time as the control group, and were approximately 2.44 times more likely to complete the assignment.

    This reflects my own opinion that I would prefer some lifetime “violations” to be addressed by using ref-counting or GC, rather than turning them into errors. An example of this is the way Go will allocate structs on the stack when possible but promote them to GC objects when escape analysis shows that would cause use-after-return bugs. Or the way Lobster/Swift/ObjC/Nim use ref-counting but are able to eliminate many of the retain/release operations based on static analysis. Essentially this turns a lifetime-checker into a performance optimization tool, not a gatekeeper from being able to build at all.

    1. 5

      Lifetimes aren’t just about memory management. Iterator invalidation is also a common category of bugs that the borrow checker prevents. Pretty sure the thread safety guarantees also make use of it, to make sure you don’t have any lingering references to data owned by a mutex once you lock it.

      Performance isn’t the only part of Rust’s value proposition, and it’s the easiest one to catch up with.

      1. 1

        I presume that in the context of this experiment they would write some iterator implementations which don’t become invalid. In a single threaded context this should almost always be possible with some slowdown.

        e.g. for a Vec analogue, your iterator keeps an Rc pointer to the Vec, and a current index, and it re checks that the index is still in range on every call to get the next item.

        e.g. for a btree analogue, the iterator stores the last retrieved key and the call to get the next item asks the underlying btree for the first item > the last retrived key. Makes iterating a btree O(n*log(n)) instead of O(n) which is a little bit dramatic.

        1. 6

          This is kind-of what Objective-C fast enumeration does and it isn’t great for usability. Code will now throw an exception on mutation of the underlying collection and you can’t statically verify that it won’t. Checking that the index is in bounds isn’t sufficient, this can lead to TOCTOU bugs if you check a property of the current element, insert something before it, and then operate on a different object by mistake.

      2. 5

        I’ve wondered what an “easy mode” rust would look like.

        I don’t feel like cloning and the borrow rules were my main barriers though. If you don’t care about allocations you can approximate a GC-ish approach by cloning all over the place.

        The problem with making everything mutable and cloning everywhere all the time is that you lose guarantees. Part of the feedback that I like about rust (after having used it for awhile) is that my type signatures for functions and structs help inform me about the impacts of my design decisions.

        1. 1

          Right — I said recently in some other thread that once you start using Rust’s escape hatches like Rc you lose the compile-time checking, which is such an important part of the value proposition.

        2. 2

          But if we want GC or RC everywhere we have that already and don’t need rust? Rust is for when you want the control to decide for yourself when that is ok or if it is not

          1. 1

            You’d still have a choice — the compiler would only use RC if it couldn’t determine the object’s lifetime statically. There could be an optional compiler flag that raises a warning (or error) when this happens, exactly like those verbose borrow-checker warnings, if you don’t want that to occur.

            Or alternatively, the warning/error is on by default but there’s a notation you put in the source code at that spot to say “it’s OK to promote this to RC here if necessary.” (A simple notation that doesn’t require you to change the object’s type or calling convention everywhere!)

            This is hand-wavey and maybe impossible, but it’s Sunday morning and a boy can dream.

            1. 2

              I am pretty sure that it is not merely possible, but already done 25..30 years ago in Jeffery Mark Suskind’s Stalin - IIRC, his fall back was Boehm-Wiser not RC. (EDIT: His early impls were in the very early 90s as a concept fork from the Dylan effort; “Sta-lin” sort of referenced “static language” or “brutally optimizing” or etc.)

              1. 1

                For the annotation version, I guess “just” making Arc<T> more compatible with &mut T basically is this?