1. 43
  1. 15

    I have this weird problem where I’m simultaneously excited and compelled by rust (open! memory safe! community! cute crab!), and disgusted by it (magic operators? borrow checker? error boilerplate?)

    can anyone recommend a solid, simple place to start? every time I read Rust I feel like I’m looking at a Rails codebase.

    maybe I have rust fomo or something

    1. 18

      I just went through the Programming Rust book (O’Reilly) and thought it was really well done. I would recommend reading it before the “official” Book.

      I was familiar with the type system features (generics, traits, etc.) — I’m one of those people who says “oh, it’s sort of a monad” — so it was mostly the lifetime stuff I had to contend with.

      I would describe the barrier by saying that in C++ you can make any data structure you want and it’s up to you to ensure you follow your own rules on things like ownership, immutability, passing between threads, etc. Whereas in Rust, you tell the compiler what you’re going to do, it checks whether that makes sense, and then insists that you actually do it. But compilers are dumber than people, so you can come up with a data structure that you “know” is fine, yet you cannot explain to the compiler so it can prove that it’s fine. You have to work within the model of “move by default” and unique/shared references that is quite unlike the C++ assumptions.

      Your choice at that point is (1) just relax, clone it or use a reference count or whatever, those wasted cycles probably don’t matter; (2) use a different data structure, and remember there are lots of good ones waiting for you in the library; (3) go ahead and do what you want using a minimal amount of unsafe code and a thorough comment about the safety guarantees you put in but couldn’t express to the compiler. (Number 3 is what the library code does that you should be using.)

      Once this clicked, I started to really enjoy the language while working through Advent of Code and various toy problems.

      1. 11

        Admittedly, beginner and intermediate resources are a bit of an ongoing problem that the Rust community is working to solve, which this article is meant to be a small part of. Apart from the book, which is great but can be a bit overwhelming at first, a good place to start is Rust By Example, which is a tad more practically oriented.

        1. 7

          Seconding, thirding, and fourthing the Programming Rust recommendation, notably the 2nd edition. I struggled with other attempts at learning Rust before (incl. The Rust Book), and the O’Reilly one was a breakthrough for me, suddenly I seemed to not get blocked by the language anymore afterward. (I still get slowed down from time to time, but I seem able to find a more or less pretty/ugly workaround every time, and thus I now feel I am in the intermediate phase of honing and perfecting my skills.)

          As for your “excited and compelled” note: my areas were a bit different, but what made me finally accept Rust’s downsides, was when I eventually realized that Rust manages to be very good at marrying two things, that IMO were previously commonly seen as opposites: performance + safety. It’s not always 100% perfect at it, but really close to that. And where it is not, it gives you a choice in unsafe, and notably that’s where you can see some sparks flowing in the community. (That’s because two camps of people previously quite far away from each other, are now able to successfully live together as a Happy Family™ — just having occasional Healthy Family Quarrels™.)

          As for error handling, as discussed in the OP article, personally I’m quite confused by two things:

          1. Why not use the thiserror & anyhow libs? I believe reaching for them should be the initial instinct, only possibly hand-unrolling the error types if and only if run-/compile-time performance is found to be impacted by the libs too much (iff proven through profiling). Notably, thiserror & anyhow are described in the Programming Rust, 2nd ed. book, which was one of the reasons I evaluated the book as worth reading. And the creation of those two libraries was a notable argument for me to try learning Rust again, precisely due to how they kill error boilerplate.
          2. Why does the article recommend and_then over the ? operator? Wasn’t ? introduced specifically to combat and streamline callback-style code required by and_then?
          1. 1
            1. I briefly pointed out the existence of crates that can reduce the boilerplate in the article, though I didn’t name any, because I decided it was beyond my ideal scope. I didn’t want to write an entire expose on Rust error handling, because many others had done that better than I ever could, but rather a short and accessible guide on combinators, which are often neglected by people just getting comfortable with Rust.

            2. I never recommended and_then over ?, they’re not mutually exclusive. Sometimes, coming up with names for every individual step’s outputs can be a bit difficult, or a bunch of steps are very tightly logically related, or you simply want to avoid cluttering the code with too many ? operators that can be a bit hard to notice sometimes, especially within as opposed to at the end of a line. I perhaps should have worded myself a bit better, but overall I think and_then simply serves as a good motivator for a reader to go look at the rest of the more specific combinators in the Result type, and I never implied it would at all replace ? in most Rust code.

          2. 4

            If you’re used to TDD, rustlings can be a great resource for getting into the swing of things with Rust: https://github.com/rust-lang/rustlings

            It walks you through the same things as the The Rust Programming Language, but programmatically. I tend to learn better by doing, so it works for me. But the “puzzles” can be cryptic if you don’t use the hints.

            1. 3

              can anyone recommend a solid, simple place to start?

              I started on Rust doing last year’s Advent of Code. It was tremendously fun, and TBH a much softer learning curve than i imagined. It helps that the AoC challenges can usually be solved by very straightforward algorithms of parse input -> process data -> output, so i didn’t really have to “fight the borrow checker” or even think about lifetimes. And also Rust standard library and docs are excellent.

              I think only on one of the problems i got a bit stuck by Rust’s semantics and i had to learn a little bit about lifetimes. I tried modelling that problem in a more OO way, in the usual “graph of objects” way. Turns out, that’s usually not a very happy path on Rust, as you suddenly have to consider lifetimes seriously and the “who owns who” problem. Still, nothing that a coat of Rc (reference-counted pointers) on all those objects could not fix. And the problem could also be solved using a built-in data structure and some indexing, instead of having objects that point to each other.

              Anyways, i digress, my point being that i found Advent of Code an entertaining and effective way of approaching the language for the first time :)

              1. 1

                Rust has good PR, but when it comes down to brass tacks, most of the truly innovative stuff is still written in C. Maybe Rust will eventually dominate over C. But it’s been 8 years already. It’s also possible Rust will end up like C++ or Java. Only time will tell. I’m in no rush to learn the language.

                1. 3

                  Replacing C in our current infrastructure is a totally impossible task, especially in 8 years. I don’t think Rust needs to replace C to be meaningful and useful apart from any hype.

                  1. 1

                    I think the problem with anything replacing C is that the people left writing C are those that have convinced themselves it’s a good idea.

                    1. 3

                      I am employed to write C. I would prefer to be writing Rust, but BIND is critical infrastructure that needs to be maintained. I think it’s more true that C and C++ will continue to be used because of inertia, in terms of programmers’ skills and existing code. But you are right that this inertia will often get rationalised as a good idea, rather than due to the more realistic expense of change.

                      1. 1

                        Oh yeah, you’re totally right about inertia especially when it comes to large existing projects.

                2. 8

                  I used to be enamoured with the idea of result/option types, but you either need syntax sugar, which means only the builtin result/option types are nice, or an unwrap in your function somewhere. And you can’t weaken arguments or strengthen result types without making a breaking change to your API.

                  All of these issues go away with first-class unions, you can strengthen your outputs from String | Error to just String without breaking (as much) client code, and you can do the reverse with arguments: doSomething(String) can be updated to doSomething(String|null) without breaking any client code.

                  1. 13

                    Just as a note, Rust does allow any type to implement the Try trait, and get the same syntactic sugar that Result and Option have. I still see your point; there’s no opportunity to design new sugary features without modifying the language, only to implement things that work the same way as the existing ones.

                    1. 3

                      The Try trait isn’t stable yet, but it it will be hopefully soon.

                      1. 1

                        Ah! Yes. Thanks for that. (Sorry about the lateness of this response.)

                    2. 8

                      You could expose the try operator to polymorphism so that everyone can use the syntax if they want to. That’s basically what Haskell does.

                      1. 2

                        Rust too.

                        1. 3

                          That feature isn’t stabilized yet so I didn’t want to mention it :)

                      2. 6

                        I agree. It’s awesome to be able to go from “this is a collection of functions with the same signature; they are all of type () => int” to “oh, now i need some of these functions to return a string too, well, let’s just change int to int | string” without having to change any of the previously existing functions of type () => int to conform to the signature of () => int | string.

                        Doing something like that with Rust enums requires introducing a new enum FooResult { Numeric(i32), Alphanumeric(String) } and then changing all the existing functions to return one of those variants, which can be quite cumbersome.

                        But given Rust’s low-level “friendliness”, i think this seems like a good design decision. On a lower level of abstraction, you can’t just have an int | string the same way you have an int. You need some extra runtime information besides the int itself to tell the two cases apart; some sort of tag field. And that’s just what Rust’s enums are: tagged unions.

                        Maybe Rust enums could have better ergonomics for these little ad-hoc cases though.

                        1. 3

                          OCaml 4.08+ solves that problem by providing a way to bind the sugary let*/let+ operators to any functions you want, like let (let*) = Option.bind or let (let*) = Result.bind, or any other function of your choice.

                        2. 6

                          You can even use ? on Option values in a function that returns Option, which is a fact I really wish was more highly advertised.

                          Oh hey, TIL!

                          1. 4

                            Nice, clear write-up of an important topic. Error handling is incredibly different in modern languages, to the point where experience in several might not be transferable to a new one. A quick overview of some popular options with low barrier to entry and little boilerplate is welcome.

                            1. 2

                              Thanks! This was exactly my intention, glad to hear I did okay.

                              1. 4

                                It was really great. I started thinking that it was a fairly boring option type and then you gradually showed me how to build APIs with great affordances. That’s something I miss in a lot of language / framework / library documentation: how you should build things with these primitives that make life easier for consumers of your API.

                            2. 4

                              All the syntactic helpers for Result has really helped in handling a lack of exceptions or the like in Rust. I’ve found Results way less encumbering, especially after having the right reflex of checking if a builtin Result method does some transformation I need. Just scrolling through the docks you might find is_ok_and fits exactly what you need and gives you an idiomatic one-liner! Maybe.

                              1. 4

                                The special syntax for Result and Option is just a crutch for the lack of monads.

                                1. 4

                                  If that’s the case then i’m glad they went for the practical crutch of Result/Option instead of polluting the standard library with overly abstract concepts as the m-word and the related category theory ivory tower jargon that usually comes with that.

                                  1. 1

                                    I mean, they started out as macros and only became syntax because… It’s shorter?

                                    You can implement most bind semantics with a macro

                                    1. 1

                                      You can implement most bind semantics with a macro

                                      I am afraid this is not possible as macro expansion runs before running the program.

                                      1. 3

                                        What does that have to do with anything? Unless you’re looking for some kind of HKT trait based generic monad interface, which sure would be cool but isn’t related to the syntax question at all. (Almost?) any given monad can have nice syntax implemented as a macro.

                                        1. 3

                                          Syntax has nothing to do with monads at all. Macros or special operators are a syntactic solution to a semantic problem. The lack of ability to express monads, which is missing in Rust because of lack of HKT, means you can’t express the general, semantic solution.

                                          1. 2

                                            Sure, but if rust had HKT that wouldn’t solve the problem that try! Or ? Solves at all. It would let you build a syntax or macro that was generic to all monads, but then we’re back to syntax again. The problem ? solves is just making the common pattern more syntactically pleasing, which HKT won’t do on its own.

                                            1. 1

                                              But with monads you don’t have to put ? in your code at all. You write code in the happy path and you get early return for free due to the way function composition works.

                                              If anything, macros could be useful to implement something akin to do notation in Haskell, although for Rust it would probably look more like OCaml’s binding operators.

                                              1. 4

                                                But that’s not because of monads, that’s because of having special syntax to go with monads, nothing more.

                                                1. 2

                                                  Syntax helps with clarity, but it’s not required at all for semantics.

                                                  1. 2

                                                    There’s nothing stopping you writing a type with monad semantics, though. You just can’t write the Monad type itself. That’s a problem when you want lots of monadic things to work nicely together, but it’s not really a problem if you just want to replace a bunch of special Result syntax with a mess of bind calls.

                                                    1. 1

                                                      In fact, Result is already a monad and has bind as and_then

                                                    2. 1

                                                      Well yes, but that’s tautological, the haskell function semantics aren’t the same as other languages. Having a syntax the produces wildly different semantics would not be useful.

                                                      For example, to get the haskell behaviour of one value being None resulting in a None result - which again does not make sense in a language that is imperatively structured - you could have the ? or whatever be an early return rather than an early None result in an expression. But you could also very easily write the Haskell-ish thing

                                                      Apologies for any errors as it’s been quite a while since I wrote any haskell

                                                      someFunction arg = do
                                                        first <- doThing1 arg
                                                        second <- doThing2 first
                                                        doThing3 second

                                                      The modern imperative-ish language with the syntax change above where ? produces early return would be

                                                      func someFunction(arg) {
                                                        return doThing3(doThing2(doThing1(arg)?)?)

                                                      I’m not sure if this is better - coding architecture/design style is very different between FL and IL.

                                  2. -4

                                    No it’s not. It’s just a Result type. But I understand that it may seem that way if Rust is the first post-1995 language you’ve ever used.

                                    1. 37

                                      While I understand this isn’t the most technically advanced article out there, all I’m trying to do is provide the same learning moments I had to other people in similar positions to me.

                                      1. 22

                                        The best teachers are often those who just learned something, and remember what it was like not to understand it.

                                        Keep at it.

                                        1. 6

                                          Just to add to what @Loup-Vaillant said above, one of the things Rust is sorely lacking is alternative learning paths, driven by the experience of people from various backgrounds. Rust is so large and intimidating that very few people choose to write about it in a manner that’s not just rehashing official documentation with some advocacy sprinkled on top. Your post involves a bunch of cool idioms related to a very practical problem. It’s a very good post that fills a very relevant niche, not just with PLT in general, but also with Rust in particular!

                                        2. 59

                                          Don’t be a dick.

                                          1. 23

                                            All Result types are cool, including Rust’s.

                                            1. 13

                                              If Rust makes it possible or attractive for people to use 21st century lang features, I’d call that a success for Rust and for programming in general.

                                              1. 19

                                                This particular feature is squarely XX century though. A quote from first rust presentation is apt:

                                                • Many older languages better than newer ones: – eg. Mesa (1977), BETA (1975), CLU (1974) …
                                                • We keep forgetting already-learned lessons.
                                                • Rust picks from 80s / early 90s languages: – Nil (1981), Hermes (1990), – Erlang (1987), – Sather (1990), – Newsqueak (1988), Alef (1995), Limbo (1996), – Napier (1985, 1988).

                                                That’s quite surprising and important to tactfully highlight !

                                                1. 5

                                                  I object-level agree that the Result type is cool - I thought precisely the same thing a decade ago when I learned about Algebraic Data Types and specifically the Maybe and Either types in Haskell. That isn’t a joke, I really was blown away by these programming constructs in Haskell when I first learned about them there, and I was surprised that no programming language I had ever previously used had support for what were obviously such good ideas. I still think that one of the most exciting things about Rust is that it popularized ADTs in a mainstream general-purpose programming language (not to say that Haskell is obscure, exactly, but Rust is probably more widely used than Haskell at this point).

                                                  So I’m inclined to agree that Rust’s Result type shouldn’t be conceptualized or taught to beginners as some special Rust thing, but rather as a (genuinely quite generally useful) application of a well-established programming language construct that Rust happens to make use of. And it’s definitely worth questioning why it took so long for the ability to create an Maybe-like ADT to become a mainstream programming tool, and to wonder about what other broadly useful programming conceptual tools exist, but just aren’t available in the mainstream programming languages that a lot of the world’s software gets written in.

                                                  1. 2

                                                    Either (and Haskell data types in general) are just tagged unions, which plenty of languages have. You could argue that inheritance based polymorphism is the OO language equivalent.

                                                    Obviously every older imperative language has Maybe as well in the form of pointers - the problem of course is that all pointers are always maybe and there’s no mechanism to ensure you check before using :)

                                                2. 4

                                                  I’ve recently started using a library for C# called OneOf, which makes it easy to implement Result and Option types, and it’s really cleaned up my error handling, especially solving the “never return null when the caller expects an object” trap that C# and Java dump you in by default. There’s little/no syntactic sugar in the language to help with it, but as a lisper, it doesn’t really bother me that match is a method rather than a keyword.

                                                  1. 0

                                                    Sure, but calling basic features “cool” is juvenile.

                                                    1. 2

                                                      Why? Do you never go “huh, that’s cool” to anything anymore?

                                                      1. 1

                                                        To be honest, I never understood what “cool” means, except as something that kids say, so no, I never used the term, as a kid of otherwise. But when something is described as cool, it’s a very good litmus test for me signalling it’s something I don’t need to pay any attention to.

                                                        1. 3

                                                          That’s disappointing as it’s not the signal you seem to think it is, so you might well be missing things you would have otherwise found interesting.

                                                          1. -2

                                                            You are so wise and smart and above it all.

                                                    2. 11

                                                      Think twice before posting again. Is your contribution useful and kind? If not, refrain from expressing yourself. No one is getting paid here. You get what you give,

                                                      1. 5

                                                        True, necessary, and kind, yeah? Worth trying to hit at least two of those three.