Settings

Theme

Rust for Professionals

overexact.com

126 points by keewee7 3 years ago · 102 comments

Reader

wrs 3 years ago

An important “unblocker” for me when learning Rust after decades of other languages was internalizing that assignment is destructive move by default. Like many Rust intros, this sort of glides past that in the “ownership” section, but I felt like it should be a big red headline.

If you’re coming from C++ especially, where the move/copy situation is ridiculously confusing (IMO), but also from a simpler “reference by default” language like Java, this has profound impact on what’s intuitive in the language.

For the C++ comparison, this is a pretty good article: https://radekvit.medium.com/move-semantics-in-c-and-rust-the...

  • scotty79 3 years ago

    Also very important realization is that things that are moved around (assigned to variable, moved into or returned from a function, kept as a part of the tuple or a field of a struct) must have fixed and known size. And the variable itself is not a handle. It's a fixed sized area in the memory that you named and moved something into it.

    This makes completely logical why some things must be Box<>'ed and borrowed. Why you cannot treat Trait or even impl Trait like any other type. Why sometimes it's ok to have impl Trait as your return type while in other cases it's impossible and you must Box<> it.

    Third important realization is that things borrowed out of containers might be moved 'behind the scenes' by the container while you hold the borrow, so you are not allowed to mutate container while you are holding borrows to any of its contents. So it's ok, to instead hold indexes or copies or clones of keys if you need.

    Another observation is that any struct that contains a borrow is a borrow itself. Despite using completely different syntax for how it's declared, created, it behaves exactly like a borrow and is just as restrictive.

    Last thing are lifetimes, which don't have consistent (let alone intuitive) syntax of what should outlive what so are kinda hard to wrap your head around so you should probably start with deep understanding what should outlive what in your code and then look up how to express it in Rust syntax.

    Rust is syntactically very similar to other languages, but semantically is fundamentally different beast. And while familar syntax is very good for adoption I'd also prefer tutorials that while showing the syntax explain why it means something completely different than what you think.

    • alltheworlds 3 years ago

      As someone who hasn't looked at Rust, reading all this makes me never want to. Am I missing something? Is it actually easier to reason about the language than it feels like from reading this text?

      • zozbot234 3 years ago

        This is just stuff that's flagged automatically by the compiler. You don't need to "reason" about it, the compiler will tell you how to fix it if it does come up.

  • coderenegade 3 years ago

    I found the copy/move situation in Rust to be far less intuitive than in C++. In C++, move semantics are obvious because they rely on std::move and the && operator, whereas in Rust, similar behavior seemed to depend on the object type. Even more confusingly, Rust has its own move operator as well, despite destructive move being the default behavior for assignment.

    I found it frustrating enough that I put the language down and just went back to using C++.

    • scotty79 3 years ago

      > In C++, move semantics are obvious because ...

      In Rust it's also obvious because every = is a move. Confusion comes from tutorials pretending for too long that it's not.

      > whereas in Rust, similar behavior seemed to depend on the object type.

      It's best to think of that as an exception to the rule created specifically for numbers and other similar small, cheaply copied things.

      If you need to move `a` but `a` has a trait Copy then you copy instead.

      > Rust has its own move operator as well, despite destructive move being the default behavior for assignment.

      I don't think that's true? Rust has a `move` keyword but it's a part of closure definition that makes it take all the variables from its environment by move, even if it doesn't need it. Unless you are talking about something else...

      • legerdemain 3 years ago

          > In Rust it's also obvious because every = is a move.
        
        False, depending on the presence of an explicit type annotation on the left-hand side, an = triggers either a move or a reborrow.
        • scotty79 3 years ago

          How does it look like? Do you mean this? https://haibane-tenshi.github.io/rust-reborrowing/

          If you assume = always moves you can just never use automatic reborrowing (do &mut * instead) and you'll loose nothing.

          I don't think that's a very common pattern, fully optional and it can be just treated as another exception to the rule, just like Copy types. You don't have to manually write .copy() in some cases, and you don't have to manually write &mut* in some others. But that's what happens. Move is still done.

          So that's total of two exceptions. Way easier to be tackled individually when the time comes than assuming = means something else, or something complex from the start.

          • puffoflogic 3 years ago

            There's a way of thinking of `Copy` which makes it not an exception: `Copy` variables/places are simply not rendered invalid/uninitialized when they are the source of a move operation, unlike non-`Copy` sources. They're still moved from bitwise like all other rust values!

          • legerdemain 3 years ago

            I've posted similar minimal examples before. The first one triggers a move (and then fails to compile), while the second one triggers a reborrow. Sure, you can explicitly write out every instance of reborrowing with `&mut *`. That would require you to understand every instance that triggers it, and would also be unbelievably noisy, since automatic dereferencing and automatic reborrowing are actually incredibly common.

              > pub fn main() {
              >     let mut x = String::from("moo");
              >     let y = &mut x;
              >     let z = y;
              >     println!("{:?}", y)
              > }
              > 
              > pub fn main() {
              >     let mut x = String::from("moo");
              >     let y = &mut x;
              >     let z: &mut _ = y;
              >     println!("{:?}", y)
              > }
            • scotty79 3 years ago

              Ok. So that's just triggering auto-insertion of &mut *

              It's completely optional fearure. You don't have to rely on it. Like auto-insertion of semicolons in JS maybe it's better if you don't. Although likelihood of this biting you is way lower than in case of relying on semicolons autoinsertion.

              • legerdemain 3 years ago

                It's "completely optional" in the sense that in several years of reading other people's Rust code, I have never seen anyone explicitly use it throughout a code base. It is utterly ubiquitous, rarely explained, and poorly (if at all) understood by a large majority of Rust users I've mentioned it to.

                My claim is that for most Rust users, understanding the borrow checker is limited to a few heuristics that seem to work for them 80% of the time, and throwing up their hands and declaring an approach unworkable the remaining 20% of the time. To a first approximation, no one can mentally model the borrow checker beyond extremely simple examples of 4-5 lines of code.

    • saghm 3 years ago

      I'd be pretty surprised if the C++ move semantics were considered more intuitive by someone not familiar with either and then learning both for the same time. Rust's semantics do depend on the type in that anything that implements Copy will be implicitly copied rather than moved, but I'm sure I understand why that's unintuitive. I'm also not sure what you mean by Rust having it's own move operator; the only thing I can think of is the `move` keyword used for indicating that closures should capture by move rather than by reference, but it's not used outside of closures as far as I'm aware, so I suspect that the confusion here is more due to expecting things to behave like C++ rather than the Rust semantics being "unintuitive" in a vacuum.

      At a high level, I think the most unintuitive part of moving in C++ compared to Rust is that it can silently degrade into a copy without anything indicating it. In Rust, trying to use a value after it's been moved will give you a compiler error, at which point you can reconsider whether you do in fact want to explicitly copy or if you made a mistake or need to refactor. In C++, the only way I'm aware of to verify whether a value is actually moved or not is to use a debugger. The benefit for requiring explicit copies is similar to having bindings be immutable by default and requiring an explicit `mut` annotation; if you start out enforcing the constraint that things should be moved or immutable, fixing it later if you find out it won't work that way only requires adding `.clone()` or `mut` in one place. On the other hand, if you start out with implicit copies or mutability by default and then want to change it later, it can be a lot more work to refactor the places where the variable is used to not violate this, and it may not even be possible in some cases.

attractivechaos 3 years ago

Nice blog post. Nonetheless, to a new learner like me, the hardest part of rust is not its syntax; it is the ownership management. Sometimes I easily know how to implement a task efficiently in other languages but I have to fight the compiler in rust. I either need to reorganize data structures, which takes effort, or to make a compromise by cloning objects, which affects performance. Note that I do agree with rust designers that it is better to make data dependencies explicit but learning to deal with them properly is nontrivial for me.

  • csomar 3 years ago

    This is exactly why I prefer Rust, eh, for everything. It forces you to think harder about your data structures and to better organize/understand your program and how data flows, gets consumed and get output.

    You can ignore that in other languages, but this comes at a cost later.

    • legerdemain 3 years ago

      My experience with Rust data structures is the opposite. To satisfy lifetime analysis inherent in pointer-based data structures, users tend to introduce additional indirection. For example, mapping keys to indexes, and then indexes to array elements, instead of simply mapping keys to values.

    • verdagon 3 years ago

      I've used Rust quite a bit, but I would love to hear more about this. Is this comparing to lower level languages like C/C++/Zig, or higher level GC'd languages? What problems does it make us think more about up-front, and do you think it's due to single ownership (in the C++ and Rust sense) or the borrow checker?

      • csomar 3 years ago

        If you think about programming as information transformation (ie: you are getting information in, applying a certain transformation and then giving some output), programming becomes about how this information flows. Data structure becomes the most important thing.

        Here is an example that now frustrates me about JavaScript. I don't know if some variables "MyVal" declared somewhere is global, local or how transfer it around. Global variables no longer makes sense to me. In Rust, global variables are possible but they are obvious and their scope is completely within your control. Now, all the data is there and it's up to you how to structure it, move it around, store it, etc.. You have to think these through before doing any code. If you don't think well enough, the Rust compiler (borrow/ownership thing) will start to give you headaches.

  • insanitybit 3 years ago

    The short answer is... yeah, just clone the objects. Whatever other languages are doing is going to have the same tradeoff - performance (or safety, if the other languages aren't memory safe). Iff it becomes a problem, come back later and remove the '.clone()'.

    • zozbot234 3 years ago

      You can also use Cow<> to choose between cloning and and immutable borrowing at runtime.

      • insanitybit 3 years ago

        Unless you're using `'static` you will still run into lifetimes with Cow, which I suspect a lot of people will find difficult. I'd suggest just using clone, learning the broader language, and learning about lifetime stuff later.

        Lifetimes aren't hard or complicated, what's complicated is understand their interaction with other features (closures) when you don't even understand wtf closures in rust are, or traits, etc. I'd just focus on the other stuff for as long as you can.

        • aussiesnack 3 years ago

          > Lifetimes aren't hard or complicated,

          Untrue - read https://users.rust-lang.org/t/what-is-a-good-mental-model-of.... Even Rust aficionados can't describe how they work without vast screeds. Or if you think the posters there are just wrong, try the RFC on which the current implementation is based: https://rust-lang.github.io/rfcs/2094-nll.html. You truly don't find that hard or complicated?

          It may be the case that people can use simplified subsets of the language to ease their way. But when a Rust advocate says that in effect to understand lifetimes you have to read the standard library source (as is written in the forum thread linked above), because no current documentation is comprehensive enough to cover it, then you know the whole thing itself is pretty complex.

          • insanitybit 3 years ago

            > You truly don't find that hard or complicated?

            This is like saying: "You think a 64bit integer is simple? OK, let's dig into the C memory model, twos complement, overflow CPU flags, how CPU caches are implemented, architectural nuances that can lead to unsynced writes from registers to RAM, etc". In reality most people can just learn that it's a number that holds 2^64 values, not complicated. If you want to really understand integers though go read the intel manual and the C memory model specification.

            Are lifetimes complicated? If you're a compiler developer who needs to consider the implications across all features and edge cases, yes. If you're learning the language and you just want to write "find me a &str in another &str but don't clone it", no, it's not complicated at all.

            The vast majority of beginners can just learn "& means any number of readers and no writers, &mut means one writer and no readers".

            • aussiesnack 3 years ago

              > The vast majority of beginners can just learn "& means any number of readers and no writers, &mut means one writer and no readers".

              But 'beginning' is never the hard part of learning any programming language, at least for a programmer. The hard part is going from having learned the basics to getting stuff done. Rust is harder than any other mainstream language to do that in. The writers in that thread can't even describe their mental model (NOT the underlying tech as you claim) of lifetimes without vast elliptical descriptions. And they can't point to any straightforward documentation.

              I have learned multiple programming languages, of many different paradigms, and never had the trouble getting to the stage of writing useful software that I have had with Rust. I've witnessed the same again and again with all the people I know - all professional programmers. In fact I'm the only one I know who's stayed with it.

              I find the denial of Rust's difficulty (and not even centred on the borrow checker - it's the use of just about every commmon library) just very very strange. Odd enough (and distant enough from the obvious, adn the experience of every person I've known) that I find it completely incomprehensible.

              • nicoburns 3 years ago

                > Rust is harder than any other mainstream language to do that in.

                I’d argue C and C++ are both harder. It’s hard to even get these to build once you move past trivia examples (including libraries etc).

                > I find the denial of Rust's difficulty (and not even centred on the borrow checker - it's the use of just about every commmon library) just very very strange

                Some of us just didn’t find it that hard. Like, I get that its hard for people in theory, because I’ve seen enough people express this that I believe it must be a common experience. But my personal experience was a couple of weeks of slow progress and lots of puzzling, and then everything became much easier, coupled with a joy that most of the language is so much better designed than the previous generation of languages I was used to using (e.g. it actually has sum types, and everything is an expression - why on earth doesn’t every language work like this)

                • jandrewrogers 3 years ago

                  C is a simple language. Many people have quickly become proficient at it with only K&R to work from, and that is a thin book. The C language has many deficiencies but I don’t think many people would characterize it as difficult to learn among its peer programming languages. It has very few concepts that you need to learn.

                  C++, on the other hand, is exceedingly complex and a difficult language to become proficient in, though modern C++ is an improvement in that regard. Some of this is due to decades of legacy cruft, some is due to it being an atypically expressive systems language, all of which has been stirred together.

                  • nicoburns 3 years ago

                    C is a simple language if you only want to do simple things with it (like say, numerical calculations). As soon as you want to do anything more sophisticated you find all the complexity in other languages and more comes back just as library APIs rather than language-level features.

                • aussiesnack 3 years ago

                  > > Rust is harder than any other mainstream language to do that in.

                  > I’d argue C and C++ are both harder. It’s hard to even get these to build once you move past trivia examples (including libraries etc).

                  Agree I overstated that. What I mean is that, out of the difficult to learn languages, Rust is the only one whose community for opaque reasons denies the difficulty.

                  > Some of us just didn’t find it that hard.

                  Why the difference I don't know. So far everyone I know who has learned it has dropped out because they just couldn't get practical traction with it. My guess is that most people who say they don't find it hard are either longstanding C/C++ programmers, and/or are learning at work where they have immediate help & scaffolding.

                  What is odd is that rather than responding to difficulties with a curious "Oh, I didn't find it hard, I wonder where the difference lay", Rust advocates tend to come back with an aggressive 'proof' that Rust is factually not hard to learn (with implications we can all guess at). No other programming community frequently does this in my experience (and I have brushed against many). That community attitude itself must have causes that are worth thinking about.

                  • pjmlp 3 years ago

                    I know C and C++ since the mid-90's, and have delivered several products into production with them, still have issues today when dealing with some cases where the borrow checker should be fine (from human borrow checker point of view), but it doesn't get it.

                    There are also these patterns one has to learn like applying references to numbers in some corner cases, e.g. func(&123), and the fact polominus is being developed shows there is still room for improvement on the borrow checker logic.

                  • nicoburns 3 years ago

                    > My guess is that most people who say they don't find it hard are either longstanding C/C++ programmers, and/or are learning at work where they have immediate help & scaffolding.

                    Neither of those were the case for me. In fact, I'd previously tried and failed to learn C (to a level where I could achieve something useful). I did learn at work, which gave me time as I was able to work on it full time, but I had no help available as nobody else at the company knew Rust at the time.

                    > Rust advocates tend to come back with an aggressive 'proof' that Rust is factually not hard to learn (with implications we can all guess at). No other programming community frequently does this in my experience (and I have brushed against many). That community attitude itself must have causes that are worth thinking about.

                    I somewhat agree this behaviour isn't great. I suspect the cause is a whole bunch of people saying that Rust is too hard, and that the industry therefore shouldn't bother with it.

                    Factors I'd guess at:

                    - Previous experience with a language that uses functional patterns is a huge plus. Even if that's very lightweight functional patterns like you might find in JavaScript or Kotlin (past exposure to something like Haskell or Scala would be even better). - Previous experience with C++ helps. Especially if you write "modern" C++ with smart pointers, etc as this is rather close to how Rust works. - C experience can be a positive or a negative. If you are the sort of C programmer who is hyper aware of object lifetimes and programs carefully and defensively it'll likely help. If you're the kind of C programmer who likes to play fast and loose with safety so long as it works in practice then it'll likely hinder as you'll expect to be able to do things that you can't. - Java/C# and other strongly OOP languages can make learning Rust harder. OOP code tends to be full of graphs of objects that all point to each other, and you just can't do this in Rust. So if that's how you're used to programming your going have to completely relearn how to structure code.

                    And I guess some of it's just going to be how comfortable people are with abstraction. Rust is definitely heavier on the mathier side of programming (with a sophisticated type system, etc). Personally I find that makes it easier not harder than languages like Go and C which are much simpler in this respect, as when reading code in those languages I often find it hard to pick out those forest from the trees. But from what I've seen, programmers are pretty split on their preferences on this issue, and I imagine for some people this would make it harder.

                    • aussiesnack 3 years ago

                      > I suspect the cause is a whole bunch of people saying that Rust is too hard,

                      Perhaps, though I haven't myself witnessed anyone saying it's 'too hard'. Just that people (like me and everyone I know who's tried to learn) find it 'hard'. Then we're informed it's not.

                      My thought about the underlying reasons was more along the lines of the Rust community's inclusion ethic. I find that entirely laudable in general. But like all ideologies it can lead to blind spots when insisted on against evidence. For example, it's even built into the (plainly false) official tagline: "A language empowering everyone to build reliable and efficient software." If someone finds Rust difficult, this creates a strong cognitive dissonance in fierce Rust advocates (for some reason so many are), which evokes vigorous denial.

                      > Previous experience with a language that uses functional patterns is a huge plus

                      My previous two 'learn for interest' languages were Clojure and Elixir. I could do more in both after a week than after some months of Rust.

                      In my case it's not unfamiliarity nor abstraction, but the sheer complexity that bleeds right through the ecosystem. For example: something I experience every time I try to do anything 'real': I search for the most common/recommended relevant library, go to its docs and find them utterly incomprehensible. It then takes days to glean enough to use the new library fluently. Even reading commandline args - detailed but conceptually simple - leads to a vastly complex horror of a library (I won't name names) that takes days to decipher. It's never taken me more than an hour or two in any other language. My Rust projects slow to a stupefying crawl.

                      For all that, I'm persisting (or rather re-starting, as my attempt earlier this year left me thoroughly demoralised) out of a combination of some real practical uses I have for it ("hard" doesn't to me in any way mean "bad"), and stubbornnes. But I'll do so without the 'help' of the community, which I find insufferable.

                      • alltheworlds 3 years ago

                        I write low latency Java for a living (usual finance type stuff). Our fund has started to use some Rust but I've not yet delved. I'm still trying to resolve whether it will be worth my time to do so. I'm one of those unusual people who really enjoys Java programming for the most part because it both allows you to be very productive but also offers enough sophistication to achieve really high performance.

                        • aussiesnack 3 years ago

                          I wouldn't let descriptions[1] of Rust as difficult to learn put you off. I'm long out of date with it so can't make confident comparisons with the Java of 2022 (2023!), but you might enjoy exploring what Rust brings to the table. It's certainly very capable, and those who become fluent with it claim the early productivity hit is eventually overweighted by the correctness/longevity of what you produce. I haven't reached that stage but have no reason to doubt the claims.

                          If you do interact with the Rust community, I suggest doing so in Shiny Happy Person guise.

                          [1] only the Rust community considers these 'complaints'

              • insanitybit 3 years ago

                The rust borrow checker has been described in trivial terms many times. The first time I had it explained, as I recall, was as a book.

                You own a book.

                `&` - You can lend others the book, they can't fuck with it. `&mut` - You can lend the book to one person, they can fuck with it `move` - You give someone else the book, it's theirs now

                Or `many reader NAND one writer`

                Is this a complete explanation? No. But it's quite simple and you can be plenty productive with just this amount of understanding.

                Your experience is not my experience. I used Rust for the first project I wrote as an intern after dropping out, having never used it before, and I even used it to interact with mysql, which I had never used before.

                At my last company I had multiple people pick up rust in a matter of days.

                I'm not denying rust as being difficult, I'm saying it's easy for some and hard for some. I found it easy, I was writing productive code on day 0 with virtually no preparation other than that I wanted to try it out. Many people find it easy. Obviously some people, many people, find it hard. Life's weird like that.

                • legerdemain 3 years ago

                  OK, simple issue that a beginning Rust user runs into immediately:

                    - function arguments are moved into the called function
                  
                    - you can call a function with a ref, and then you can keep using it in the calling function, because there is a blanket impl of Copy for refs
                  
                    - you can call a function with a ref mut, and then you can keep using it in the calling function because ... ???
                  • nicoburns 3 years ago

                    Because when you pass by reference, you lend the value (or to phrase it the other way around: the function borrows the value). And so when the function’s done, it gives you the value back (because that’s how borrowing works, in the everyday sense of the word, as well as in th Rust sense)

                    • legerdemain 3 years ago

                      This is either wrong or incomplete. When you call a function `f()` with some `&mut`, the callee gets a `&mut`, not a borrowed `&mut &mut`. The callee doesn't borrow the ref mut, it gets the actual ref mut, and for some time you have multiple mutable references in the same scope. How?

                      • Filligree 3 years ago

                        Because the calling function isn't using it at the time. Rust isn't proving something about the number of references in memory; it's proving something about the dynamic behaviour of the program, i.e. that there can be at most one reference actively being used to mutate it, at a time.

                        Hence there's no problem cloning a mutable reference implicitly, when calling a function. Notice that you can't for instance send it to another thread, however.

                      • zaphar 3 years ago

                        There are not multiple mutable references in the same scope. The mutable reference is only valid for the scope of the function `f()`. This doesn't sound like a problem understanding mutable references but a problem understanding scope.

                      • scotty79 3 years ago

                        It gets `&mut *&mut` (where * doesn't mean a pointer, just deref)

                        Not sure if that's good that this feature is automatic. Maybe it wouldn't be too bad to call functions like that for consistency:

                            go_break_stuff(&mut *my_stuff_ref);
                        
                        if you want to keep using the reference after the call.
                      • nicoburns 3 years ago

                        The function isn’t borrowing the reference. It’s using the reference to borrow the value thats being pointed to by the reference.

                        • legerdemain 3 years ago

                          I was looking for the word "reborrowing," which I think is crucial for beginners trying to form an effective mental model of Rust semantics.

                  • insanitybit 3 years ago

                    You add '.clone()' or '&', the code compiles, you move on.

              • jcranmer 3 years ago

                From the standpoint of being productive in Rust, this is what you need to know:

                * An object has a lifetime. This generally lasts until overwritten or destroyed at end of scope.

                * You can loan out references to this object. You can either have a single &mut, xor an unlimited number of & references, but not both.

                * References cannot outlast the lifetime of the object they point to.

                * If you have a &mut reference to an object, you can give out another &mut reference to the same object. But you can't use the first reference for the duration of the second.

                * Lifetimes can be named, and you can express criteria of the form "this lifetime must at least/most this long."

                * Bonus: if you have &mut x, you can give out &mut x.a and &mut x.b at the same time. But you can't give out &mut x while such a subreference is live.

                And... that's really all you need. Yeah, there's a lot more rules, and there's definitely fun edge cases around things like temporary objects' lifetimes. But there's no need to know all of that stuff. If you get things wrong, the compiler will come back and slap you in the face and give you an error message--that's the selling point of Rust, getting lifetimes wrong is an error, not silent nonsense--and your task at that point is to figure out if you're really breaking a cardinal rule (two or more mutable references to the same memory location, or references not lasting long enough), or if you didn't enforce sufficiently strict requirements for the bounds of the lifetimes. And the rust compiler is pretty good at telling you what you have to do to get necessary lifetime bounds (sometimes too good--it can suggest fixes to lifetime bounds even when such bounds are unachievable).

                I'd compare this to things like name lookup and overload resolution in C++, which are actually horrendously confusing algorithms that almost nobody understands in their entirety. Yet plenty of people can be productive in C++ because the general principles are well-understood, and if you're at the point where you need to descend into the morass of exceptions and exceptions-to-exceptions, you're probably writing confusing code to begin with.

                • aussiesnack 3 years ago

                  But I've never heard a C++ programmer deny that it's a large and difficult language.

                  I personally never really had a problem with ownership model - probably because what I was doing fell into the simple buckets. Rust's difficulty goes well beyond ownership. I just thought the Rust user forum thread I quoted was a gently funny example of the Rust community's denial: "Rust's not hard, but to get a decent mental model of the borrow checker you need to read the standard library, or if not here's 10000 words on how I think of it". I'm not presenting it as "proof" that Rust is hard - the fact that I couldn't do anything practical with it after more troublesome attempts than with any other language is plenty enough for me to know that. Neither do I think difficulty is 'bad'. Denying it can be though.

                  • jcranmer 3 years ago

                    That's someone who's defining "mental model" as effectively "build a formal model of how this things worth without actually using formalism for everything." Of course everyone in that thread is coming up with very complicated stuff, because OP explicitly asked them to do so.

                    Yes, Rust's borrower checker is more complicated than equivalent features in other languages. If you read the writings of the Rust language developers, you'll notice that they basically admit that it's where Rust sinks its entire complexity budget, so there's no room to spend it anywhere else. But I don't think it's too complex--it's not complex enough for average developers to not be productive. I would contrast this with C++'s template metaprogramming, which I believe to be too complex for average developers to be productive (e.g., trying to write templates that switch based on the types of the parameters, especially before if constexpr).

              • zozbot234 3 years ago

                > Rust is harder than any other mainstream language to do that in.

                It's not hard, you just add boilerplate. Cloning data, using interior mutability, or adding ref counting to deal with cases where multiple "owners" can keep an object around independently.

                Then removing the boilerplate is how you do optimization, once you've gotten things to work. The opposite of other languages where low-level code is the most verbose and least intuitive.

                • aussiesnack 3 years ago

                  > It's not hard,

                  Yep, that's the standard Rust aficionado flat insistence: "You're finding it hard. You're just wrong". Yet with more learning time than I've put into any other language I've learned, I have been unable to use Rust for real (ie. beyond beginner toys). A quick count of langs I've used professionally comes to about 10; I've learned many more to play with, much more successfully than with Rust, in much less time than I've spent with Rust. This reflects the experience of everyone I know who's tried it out (I'm the last man standing, having another crack at it this year).

                  The cultural peculiarity of this is that, in a field that generally lauds effort and intelligence, Rust advocates uniquely consider 'hard' or 'difficult' to be negative criticism. I don't hear this from (say) Haskell or Scala programmers. I certainly don't consider difficulty a negative - it depends on whether the payoffs are commensurate, and for Rust I think they are (I'd just quit otherwise).

                  As I'm 100% satisfied (no matter how often I'm unconvincingly 'corrected') that Rust is indeed harder than most programming languages to learn, I can only categorise this peculiarity as denial. Denial usually has ideological origins. I'm not entirely sure what they are in this case, though I have some ideas. These do feed into a sense that although I like the language, I don't think I like the Rust community much. I get strong religious/culty vibes from it - similar to the worst of the Linux community (for the record I also am a f/t Linux user, and I think that community has moderated considerably over time).

                  • insanitybit 3 years ago

                    Just to be clear, again, lots of people find rust difficult. I found it easy. Lots of people find it easy. That's interesting.

                    Saying "rust is difficult" is silly to me because... it wasn't for me. Saying "rust is easy" is less silly to me because for me it was, but obviously for you it will be more silly because it isn't easy for you.

                    You'll find that many in the rust community are in fact very very sympathetic to your view that it's too hard to learn.

                    • aussiesnack 3 years ago

                      > You'll find that many in the rust community are in fact very very sympathetic to your view that it's too hard to learn.

                      I have not found that. The very comment I'm responding to above is a correction, telling me "it's not hard", not saying on the writer's part that they didn't find it hard, and least of all with any curiosity towards any dev claiming that they find Rust hard, because that view is considered in the Rust commmunity plainly incorrect. This is what I have found, consistently in 2022. I plan to continue with Rust in 2023, but I'll probably largely go it alone.

                      • insanitybit 3 years ago

                        Well I've been around Rust since before 2015 and I'm telling you that tons of people feel the language is too difficult. Maybe you haven't seen that, but you've also seen a tiny fraction of the community relative to me.

                        Going it alone sounds like a recipe for it being very difficult. I'd recommend interacting with the community when you have questions, it'll be very helpful.

                        My only point this entire time is that saying "rust is difficult" is just as wrong as saying "rust is easy". Rust was easy for me, fact. Rust was hard for you, fact. Both things are true.

              • TylerE 3 years ago

                Ok, so how do I do one writer and one or more readers?

  • aussiesnack 3 years ago

    But it's so easy to get a handle on how the borrow checker works! Just check out this thread and all will become clear: https://users.rust-lang.org/t/what-is-a-good-mental-model-of...

    Joke btw. That thread is a hilarious trainwreck - surely the final nail in the coffin for the Rust advocates who so often deny anything about Rust is difficult to learn.

    I don't mean that as an anti-Rust jibe, in fact I'm planning to get back to it this year (having given up in despair last). I like much about it, and think it's tremendously practical for many purposes. But it just is a difficult language, no question.

    • insanitybit 3 years ago

      I mean, some question. I learned Rust with 0 professional experience in any language (I learned it after I dropped out of a CS program after 2.5 years) and I found it pretty damn easy. That was in 2015 when the language was wayyyy less approachable (worse borrow checker, everything used nightly, smaller community, no book).

      Easy is relative. I suspect a major reason I found it easy was because I didn't try to solve lifetime problems, I just cloned things. I also had primarily been using C++ in school so I was pretty familiar with pointers and, to some extent, ownership. Plus my initial foray into CS was driven by a desire to do exploit development professionally, so lower level details weren't scary at all.

      • aussiesnack 3 years ago

        > Easy is relative

        In the case of programming languages, yes, it's relative to the difficulty of other PLs. I've learned many over the years, and found Rust by far the hardest (it's the only one that defeated me). And it's not the most different from others I've learned - lisps are far further from the common languages than Rust is.

        > I suspect a major reason I found it easy was because I didn't try to solve lifetime problems,

        Well yes anything's easy if you skip the hard bits. Learn C without using pointers.

        I personally didn't find ownership & borrows the hardest part - in my case it's the great complexity of many of the commonly used libraries. Rust's complexity bleeds out into the entire ecosystem (a cultural thing).

        • insanitybit 3 years ago

          > In the case of programming languages, yes, it's relative to the difficulty of other PLs.

          I've learned many many PLs at this point. Rust was one of the easiest for me.

          > Well yes anything's easy if you skip the hard bits. Learn C without using pointers.

          That's my entire point. Rust is not hard if you learn the easy parts first. Trying to learn everything at once is not easy, no matter the language. Once the problem becomes just learning the borrow checker it's not that big of a deal. The harder thing to do is learning the borrow checker and traits and closures and blah blah blah.

xiphias2 3 years ago

In my opinion this is the way _not_ to learn Rust. These syntaxes are not important at all, and doesn't introduce lifetimes (which is by far the most important part of the language for deciding whether to use it or not).

Any blog about learning Rust for beginners should just contain information that helps the reader decide _whether_ she should put in the time required for learning it, then refer to the great Rust Programming Language book that's really hard to surpass.

The reference is great as well, though personally I miss a part that formally defines the type system in its current form (there are some papers about lifetimes, but they are very hard to read).

mlindner 3 years ago

It'd be nicer if there was some way of selection which language is shown on the left side. Expecting readers to understand both C++ and Kotlin and Java and Javascript will be a stretch for most.

  • kika 3 years ago

    My experience with Java ended circa 2000 and I never wrote a single line in Kotlin. But I read these examples without noticeable issues.

    • andsoitis 3 years ago

      what have you been programming in the past 20 years?

      • kika 3 years ago

        In approximate chronological order: Python, Lisp, C++ (ATL and Qt), Erlang, JavaScript, TypeScript, PureScript, Rust.

  • heavyset_go 3 years ago

    All of those languages adopt the C-like syntax and semantics, it shouldn't be hard for someone with familiarity with languages in that family to deduce what's being conveyed in code in languages they might not have experience with.

  • clumsysmurf 3 years ago

    I thought there would be an option to select just one, but seems they are indeed just random smatterings of rust vs { Typescript, Javascript, Kotlin, Java, C, and C++ }

  • nine_k 3 years ago

    AFAICT, the expectation is that the reader knows at least one modern programming language from the list, and maybe is acquainted in passing with a couple of others. So at least some comparisons should click.

    (They seemingly don't use more apt comparisons with OCaml and Haskell, for instance, not expecting the reader to know them.)

    • mlindner 3 years ago

      Javascript I've seen a decent amount and tweaked/edited some of code in it but I wouldn't say I "know" it at all. Kotlin I've never even seen before and I know literally nothing about the language. Java I wrote a bit of in high school but haven't touched it in 15 years.

      So yeah there's quite a lot that people wouldn't know.

      • nine_k 3 years ago

        Well, Golang? Pascal? They have similar ways of declaring data types, for instance.

        This guide is not going to magically teach you Rust is there's nothing to compare it to; there are other guides to help those without a background. But it could somehow help people who already have similar concepts in their mind to link them to corresponding concepts in Rust.

        • mlindner 3 years ago

          Golang I've seen code of, but never worked with it. Pascal I've never seen before.

      • Jtsummers 3 years ago

        You've said what languages you don't know, what languages do you know?

        • mlindner 3 years ago

          C, C++, and Python are all languages I've worked professionally in. I'm in the process of learning Rust.

  • 86J8oyZv 3 years ago

    These features aren’t each supported by all those languages though. I also don’t think expecting a dev interested in Rust to understand several C-like languages is unreasonable, at least enough ti understand these straightforward example cases.

deepsun 3 years ago

> Inner functions. Rust also supports inner functions. ...

> Closures (lambdas). Rust supports closures (also called Lambdas, arrow functions or anonymous functions in other languages).

That's misguiding.

Closures are not lambdas. Lambdas are just syntax, but the whole point about closures is that they capture the enclosing environment (have access to variables where it's defined). Rust's documentation states just that. Closures may or may not be lambdas.

In above example of "Inner functions" (which is also a closure) that would be more clearly explained if the inner function used an outside variable. Not all languages can do that.

joaquincabezas 3 years ago

I keep saving these Rust resources for a near future... Am i the only one??

I really hope to start using Rust in 2023, probably for some kind of API gateway experimentation

  • tmtvl 3 years ago

    I saved up Common Lisp resources for a few years and in 2022 I finally decided to sit down and learn it. It was entirely worth it, so I recommend you sit down to learn Rust one weekend. In fact, do it next weekend. Getting started on anything is always better done sooner than later.

    • kika 3 years ago

      joaquincabezas, don't listen to this person. It's a trap. In February I found myself in AirBnB alone with nothing to do, because my wife had to stay back home for an extra week and I waited for her to join me. AirBnB had a decent work desk, decent(-ish) monitor and barely ok keyboard so I decided to learn Rust. Now it's January, I'm at 3000+ LoC of Rust and about 4000 in Dart/Flutter and trying to make the project to ShowHN. Weekend project, my ass. Rust is highly addictive, you've been warned. I tried to get sober, left this project for months on end, but always relapsed.

    • randmeerkat 3 years ago

      > Getting started on anything is always better done sooner than later.

      So true. It’s never too late, you’re never too old, there’s never something else you need to learn first, just do it.

    • avaldez_ 3 years ago

      Nice. I'm doing exactly that with CL, one of my new year's resolutions. Would you mind to share your resources?

      • tmtvl 3 years ago

        Besides the obvious Big 4 (Gentle Intro, PAIP, On Lisp, and Cookbook) I am also quite fond of Lisp in Small Pieces. Aside from those I'd also recommend grabbing the GCL source and building the Info manual so you can browse it in Emacs, it contains a fairly complete copy of the HyperSpec (I know it's available online, but I don't like visiting websites that haven't updated to HTTPS yet).

        Also check out CLiki (the Common Lisp wiki, https://www.cliki.net/), it's very helpful in finding useful libraries, like Alexandria, defstar, trivia, lparallel, and so on.

  • pjmlp 3 years ago

    I can only justify Rust for hobby coding, none of the stuff I do professionaly cares about what Rust offers, compiled managed languages are good enough and have decades of software maturity, and using Rust as translation layer between them and C++ libraries hinders more than it helps.

  • rr808 3 years ago

    Yeah I wish there was a strong jobs market for Rust developers then I'd feel like I wasn't wasting my time.

    • Gigachad 3 years ago

      There aren't many full Rust jobs but there are a whole lot of companies with ever expanding bits of Rust in production. Probably the best way at this point is to push it internally and put together a convincing case for using Rust for new projects.

      • alltheworlds 3 years ago

        This is our company. Frankly, I don't agree with this approach. We're a hedge fund that uses Java for low latency trading systems. Someone pushed for Rust to be used for a new project, because they wanted to use it, and now we have a split codebase. We have duplicate code (eg. connections to external APIs) written in Java and Rust, and now we have two sets of code to maintain whenever one of those APIs change.

        I always get annoyed when I hear about people suggesting to push for a language in a company because they feel it would be a good fit. Sure, maybe it would be, but now the company will always require experts not in one language but two, and developers are no longer as fungible between teams.

        Having said that, from a selfish perspective, I will use it to write some production Rust code in the future.

    • bschwindHN 3 years ago

      What kind of Rust job would you be looking for?

solomatov 3 years ago

This document only briefly mentions interior mutability, which IMO, is one of the most important things to become productive in Rust.

ridiculous_fish 3 years ago

Curious why the author chose not to discuss macros? You encounter them immediately with a Hello World.

  • John23832 3 years ago

    Declarative macros would be too confusing to get into in any way other than "the ! in println! denotes a macro".

ww520 3 years ago

This is an excellent short tutorial. It helps to compare and contrast to other languages.

jason2323 3 years ago

As someone coming from a Java background, this seems useful! Thank you!

skor 3 years ago

any constructive criticism on rust syntax?

  • ridiculous_fish 3 years ago

    With the caveat that syntax is the ultimate bikeshed topic, one (IMO) syntactic wart is the special pattern syntax:

    1. "Operators" have different meanings. `1 | 2` and `1..=2` mean something different in patterns than in expressions. Here is a silly example: https://rust.godbolt.org/z/76Ynrs71G

    2. Ambiguity around when bindings are introduced. Notice how changing a `const` to a `let` breaks the function: https://rust.godbolt.org/z/aKchMjTYW

    3. Can't use constant expressions, only literals. Here's an example of something I expect should work, but does not: https://rust.godbolt.org/z/7GKE73djP

    I wish the pattern syntax did not overlap with expression syntax.

    • scotty79 3 years ago

      > 2.

      This actually bit me in the a__ when I misspelled my enum variant and instead match created a variable named like that, that captured everything and I got only a warning and very wrong code.

      I think there should be `let`s inside match block if matching creates some variables.

  • scotty79 3 years ago

    I don't like how

    fn func<'a>() means that 'a must outlive execution time of func

    but

    T:'a means that references contained in T must live longer than 'a

    and

    'a:'b means 'a must live longer than 'b (that's consistent at least)

    Maybe:

        fn 'a:func() {
    
    or

        fn func() 'a:{
    
    would be better for indicating that 'a should outlive function execution.

    Maybe some directional character would be better than : (> is probably out of question because of <> generics)

    ----

    I feel like structs that don't have 'static lifetimes because they contain some borrows should have it indicated in their name.

    For example:

        struct Handle&<'a> { n:&'a Node }
    
    or even

        struct Handle&'a { n:&'a Node }
    
    or

        struct Handle& 'a:{ n:&'a Node }
    
    to indicate that 'a must outlive the struct.

    Then you could use it:

        let h = Handle& { n: &some_node };
    
    Maybe functions that create non-static struct might have & at the ends in their names.

    Like

        vec![].into_iter().map()
    
    but

        vec![].iter&().map()
        
    
    You could easily see that you are dealing with something you should treat like a borrow because it contains borrows. Such structs would be sort of named, complex borrow and raw '&' borrow would be anonymous or naked borrow.

    Not sure if it would also be nice to differentiate structs with &mut

    ----

    I would just like to separate lifetimes syntax from generics syntax because those two things have nothing to do with each other from the point of view of the user of the language.

    ----

    I would also like to have

    while cond {} else {}

    where else is only entered if cond was false from the start. But that's a wish not specific to Rust.

  • tmtvl 3 years ago

    Using clearly defined bit sizes (i32, f64) rather than legacy naming conventions (int, double) is a good idea, the language could be really quite something if they switch to S-expressions.

  • legerdemain 3 years ago

    Syntax or semantics? Not a lot for syntax... maybe the "turbofish" syntax with generic types is a bit too much line noise: <Foo<_> as Bar<_>>::baz<_>()

    • guilhas 3 years ago

      Definitely, rust looks clear for what is programmatically happening, making it noisier to overview the problem being solved

tomr75 3 years ago

the hardest part and barrier are the concepts behind lifetimes/ownership/borrowing not the syntax

  • scotty79 3 years ago

    I think the hard part is understanding how limited is basic feature set of just Rust.

    That you can write very few interesting programs without venturing into the heap with Box, Rc and such and into internal mutability with Cell and RefCell.

    Then it quickly raises to the power of other languages and surpasses them with "pay for only what you use" mentality.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection