1. 61
  1.  

  2. 39

    Rust is hard because it draws heavily from two of the hardest other languages to learn: C++ and Haskell. If that’s not enough, it dumps the borrow checker (not too bad if you’re from C++) and then lifetimes on you.

    C++

    • const/non-const versions of functions (&self, &mut self)
    • compile-time directives (#[cfg, etc.)
    • references vs values
    • moving and copying
    • operator overloading
    • RAII (c’tors, d’tors)
    • monomorphism

    Haskell:

    • pattern matching
    • derive as in “typeclass”, not as in OOP
    • traits are like typeclasses, not interfaces in OOP
    • enum as sum types
    • inferred typing
    • using types to indicate usage

    In isolation this is a lot of strange things. I’m an experience C++ dev, so those points weren’t difficult and I was able to get productive in Rust rather quickly. However, it wasn’t until I spent the last month or so learning Haskell that a whole bunch of the parts I thought were quirky started to make considerably more amounts of sense.

    1. 15

      I think this is spot on, and is very similar to my experience (except I knew Haskell before Rust). The reality is Rust is simply not a beginners language, and I don’t think it will ever be one.

      In terms of complexity, I do think Rust is up there with Haskell and C++, but I can’t help but think C++ and Haskell have quite a bit of accidental complexity due to legacy reasons. Rust certainly has a bit of that, but overall the language is extremely well designed and I almost think that makes it more daunting. You can’t just point to some complexity and say “oh thats just for some silly X, Y and Z,” its always “Oh, that has some real and important purpose X, Y and Z.”

      Rust will certainly get there in terms of accidental complexity (its a very young language, that cruft is built over time), and I can only imagine how complicated it might become one day. The async/await has some of this – because even though I think it is well designed (for now) it is exceedingly complicated, and I have a hard time believing that nothing better will come about (leaving cruft).

      1. 4

        async/await .. think it is well designed

        I already know people calling it poorly designed today, with statements like “optimized for ~no CPU work on the critical path”.

        1. 3

          At a minimum, it’s fairly awkward to use when you have a mixed I/O and CPU bound workload. The project I’m working on like that splits work between Tokio and Rayon for that reason. All of the client libraries we’re using are based on async, so it would be challenging to choose a different style of concurrency.

          1. 2

            Wouldn’t that be the executors that are poorly designed? The actual built-in async/await support doesn’t seem specific to any given workload.

            1. 2

              I think the issue with async/await is not so much the implementation in Rust, but all the different (incompatible) libraries built on top of it. This means that importing a few dependencies can lead to a few different async/await runtimes being used, with all the trouble that may bring.

          2. 7

            I think it the listed features are more from ML than Haskell. Even the first compiler of Rust written in OCaml. https://github.com/rust-lang/rust/commit/6997adf76342b7a6fe03c4bc370ce5fc5082a869

            1. 6

              I feel like some of these things took no time at all to learn, such as pattern matching and enums as sum types. It was more of a “well fucking finally” than a “how does this work?”

              1. 4

                I think the sort of challenge comes at identifying usage of these when you spend your life not using sum types (and instead relying on stuff like OOP to get the effects). Also, pattern matching does affect thigns like the flow of ownership, so you can land on some non-intuitive stuff. I “get” this stuff but ended up losing a good amount of time in a refactor because of pattern matching + implicit reference stuff.

                Rust isn’t Haskell, but it does have the “if you want to avoid problems, write code in a very specific way” quality to it that makes “porting this C code to Rust” non-obvious at times.

                1. 1

                  I first encountered some of this when I poked at OCaml in college, after really only knowing Javascript, Java, and a bit of Scheme. It was immediately obvious to me how much better pattern matching was than the equivalent if-statements.

                  On the other hand, I’m also kind of a paranoid coder overall, and was already dissatisfied with how verbose and risky if-statements were, so I suppose I already had a use-case in mind before I saw the feature.

              2. 2

                Coming from a C++ background, it’s generics and especially lifetimes that give me a hard time.

                Generics are superficially like templates, but their type-checking is much stronger. Templates kind of let you sneak away from strong typing, kind of like structured macros. Generics don’t. I’m sort of used to this because I’ve used Swift (and a bit of Scala) but sometimes I find it hard to convey to the compiler exactly what the type constraints on T need to be.

                Lifetimes are something I understand and manage all the time, both manually and by rigging up C++ stuff to enforce them for me with RAII. What I’m not used to is having to explain my logic to the compiler, especially when there are only limited facilities for doing so and the syntax is kind of arcane. This is where I tend to rage-quit and why I haven’t gotten farther.

                This may also explain why I’m liking Nim more: it’s kind of Rust-like but comfier. Its generics hew closer to Rust than C++, but it also has things closer to C++ templates (cleverly called “templates”). And its GC/refcounting means I don’t have to sweat lifetimes so much; that comes at a cost, of course, but as I’m not writing device drivers or lightbulb firmware I don’t mind.

              3. 9

                The thing that helped me most in learning Rust was getting on IRC or Discord and talking to people who could give context to the problems I was having.

                1. 8

                  One thing that does help is experience with a language with a strong type system like F#, Scala, or O’Caml. The borrow model is novel, but the rest of Rust seems pretty similar to any of these with a bit of C++ inspiration.

                  1. 10

                    Borrow checker isn’t exactly new either. Not even “C++ with algebraic types and a borrow checker” idea is new: Cyclone did it before it was cool, and faded into obscurity (I suppose not being backed by a big name is one of the reasons).

                    However, I can see how it can be hard for people who were stuck with 70’s language designs and now have to catch up with three decades worth of programming language research at once.

                    1. 1

                      it’s new in a mainstream-ish language :-)

                  2. 8

                    I didn’t find rust particularly hard.

                    I had a lot of experience with c, and had just come off of f# (rewrote my f# lisp interpreter in rust, mutation was much easier). So I was already familiar with all its base concepts, which certainly helps. Of course, I didn’t already know how everything worked, but nothing (traits, borrow checker/affine types/ref&copy semantics) was novel enough to comprise a whole new way of thinking (as you get in e.g. array languages, lisps, functional languages).

                    A lot of the excitement and frustration surrounding rust comes from its exposure to systems programmers (as a c++ replacement) and web developers (as it’s from a browser company)—for many of these people, it’s their first exposure to a language with strong, expressive types.

                    Although I’m somewhat glad that expressive types are entering the mainstream, I also think it’s somewhat of a shame it had to be rust. The borrow checker is more a hinderance than a help to most projects. I’ve written at length about why I don’t think it’s the right approach in any situation; and as dr richard jones notes, leaving ownership out of APIs makes them more flexible. But for something like a cli tool or web backend application, f# (especially if you’re into into asp), ocaml, or haskell is definitely more suitable than rust.

                    1. 5

                      Although I’m somewhat glad that expressive types are entering the mainstream, I also think it’s somewhat of a shame it had to be rust.

                      I wonder if that’s actually happening, or if we’re just in some sort of a bubble due to Rust team’s extensive and skillful outreach that makes it look so.

                      Around 2015, Rust rose to #14 (1.147%) in Github repos. 5 years later, it’s currently at #13 (1.018%). The growth seems to have plateaued.

                      From my point of view, the biggest success of Rust so far has been a few high quality unix CLI tools. I don’t know if there’s anything about Rust that made those tools possible or even significantly easier to create. Momentum/hype generates motivation though, and that’s always significant.

                      1. 2

                        I got the same conclusion. F# & Elm cover 99% of the problems we encounter at work with my co-workers without needing to have a significant investment trying to figure out what is idiomatic Rust. F# has very few gotchas and great out of the box performance, a library for anything you might want to use. For me there is no real reason to switch to Rust and be put up with the borrow checker and co. I have tried many times, it is not worth it for me.

                      2. 4

                        What made me a rust fan was the idea of getting C like semantics and control out of something as high level as ruby or Haskell. Getting GC like memory mangement without paying the price.

                        Rust has, for me, mostly lived up to that.

                        1. 6

                          Well, there’s “rust” in “frustated”.

                          1. 3

                            To my understanding, Rust is meant to be extremely safe and performant, so you can build something that needs to be extremely stable and efficient like a device driver or an encryption library. I don’t think it’s meant for, say, saving programmer time while building a user application. But it seems like it’s become very popular with those who want to move away from C or C++ to something safer.

                            C and C++ are used for everything, from low to high level, and Rust probably looks like an upgrade path for those languages in general. I’m suggesting that many new Rust devs might be using it for something high level, which may make it the wrong tool for the job. (How accurate is that?)

                            For what it’s worth, I think Rust’s type system is amazing and cool, and I am interested to study ownership and lifetimes and really understand them, and when I do that I’ll be thankful for the error messages I’ve heard so much about.

                            Anyway, if you’ve been frustrated with Rust, look at a language inspired by it, which is Swift. It has a very similar syntax and type system, including enums as sum types, nil as an optional enum case and not a pointer sentinel value, pattern matching switches, and protocols (traits) with associated types. It does ARC, not garbage collection, and there are both reference and value types. Async/await is just now on its way into the language, as server applications are their current target for growth. Ownership features are targeted for the future in order to support Rust-like low-level code, but would then be optional.

                            The major upside for devs frustrated with Rust is, Swift started at the user application space and is working toward being great for low-level code too, rather than in the other direction. You will probably trip on generics a bit, but not the way this article suggests.

                            Current downsides of Swift include slow build times for large projects, a common default assumption that you’re on macOS rather than Linux or Windows, and so far, only a handful of supported Linux distros.

                            1. 1

                              I’ve heard anecdotes that people who are new to programming don’t have as much trouble learning Rust. I suspect it’s because Rust has different approaches to many things. Neither C-style pointer juggling, nor Java-style OO hierarchies are a good fit for Rust. Rust has its own programming patterns and style, so you first have to unlearn the other style.