13 ms·
Flattening Rust’s learning curve
- dmitrygr 1y ago> Treat the borrow checker as a co-author, not an adversary Why would I pair-program with someone who doesn’t understand doubly-linked lists?
- dwattttt 1y agoI'd rather pair program with someone wary of double-linked lists, but is really hot on understanding ownership than the other way around.
- deleted 1y ago[deleted]
- mre 1y agoFor people who don't get the reference, this might be referring to the notoriously gnarly task of implementing a doubly-linked lists in Rust [1] It is doable, just not as easy as in other languages because a production-grade linked-list is unsafe because Rust's ownership model fundamentally conflicts with the doubly-linked structure. Each node in a doubly-linked list needs to point to both its next and previous nodes, but Rust's ownership rules don't easily allow for multiple owners of the same data or circular references. You can implement one in safe Rust using Rc<RefCell<Node>> (reference counting with interior mutability), but that adds runtime overhead and isn't as performant. Or you can use raw pointers with unsafe code, which is what most production implementations do, including the standard library's LinkedList. https://rust-unofficial.github.io/too-many-lists/ https://rust-unofficial.github.io/too-many-lists/
- deleted 1y ago[deleted]
- Animats 1y agoRust still needs a way out of that mess. It's conceptually possible to have compile time checking for this. Think of RefCell/Weak and .upgrade() and .borrow() being checked at compile time. I've discussed this with some of the Rust devs. The trouble is traits. You'd need to know if a trait function could borrow one of its parameters, or something referenced by one of its parameters. This requires analysis that can't be done until after generics have been expanded. Or a lot more attributes on trait parameters. This is a lot of heavy machinery to solve a minor problem.
- School-Cotton 1y ago> Rust still needs a way out of that mess. In practice, it really doesn't. The difficulty of implementing doubly linked lists has not stopped people from productively writing millions of lines of Rust in the real world. Most programmers spend less than 0.1% of their time reimplementing linked data structures; rust is pretty useful for the other 99.9%.
- Animats 1y agoDoubly linked lists are rare, but backlinks to the owner are often needed. It's the same problem, mostly.
- mplanchard 1y agoBacklinks work fine with weak Arc references, don’t they?
- Animats 1y agoYes. But the Arc has to wrap a Mutex, which means you have to lock to get access. It's a dual of the Rc/RefCell/borrow mechanism. The trouble with calling .lock() is that there is a potential for deadlock. There are some people working on static analysis for deadlock prevention, which is a dual of the static analysis for double borrow protection problem. We're maybe a PhD thesis or two from a solution. Here's some current research, out of Shanghai.[1] Outlines the theory, but code does not yet seem to be available. [1] https://arxiv.org/pdf/2401.01114 https://arxiv.org/pdf/2401.01114
- worik 1y agoI am working on a code base, that among its many glories and poo balls every list is a doubly linked list. Stop! If you are using a doubly linked list you (probably) do not have to, or want to. There is almost no case where you need to traverse a list in both directions (do you want a tree?) A doubly linked list wastes memory with the back links that you do not need. A singly linked list is trivial to reason about: There is this node and the rest. A doubly linked list more than doubles that cognitive load. Think! Spend time carefully reasoning about the data structures you are using. You will not need that complicated, wasteful, doubly linked list
- dmitrygr 1y ago> There is almost no case where you need to traverse a list in both directions But you might need to remove a given element that you have a pointer to in O(1), which a singly linked list will not do
- dwattttt 1y agoIf that's a specific use case you need to handle, it's O(1) again if you have a pointer to both the node to be removed and the previous node. Whether it's more efficient to carry a second pointer around when manipulating the list, or store a second pointer in every list node (aka double linked list) is up to your problem space. Or whether an O(n) removal is acceptable.
- MeetingsBrowser 1y agoGetting the pointer to that element means randomly hopping around the heap to traverse the list though. Linked lists are perfect for inserting/deleting nodes, as long as you never need to traverse the list or access any specific node.
- dmitrygr 1y agoYou’re assuming no other data structure points to the element. It may. Example: implement a cache. Each element is: key, value, linked list node for hash table bucket, linked list node for LRU. Hash table to look up element. Element is both a member of hash table and of linked list. Linked list is used as LRU for feeling memory when needed. LRU never traversed but often needs removal and reinsertion.
- sbrother 1y agoApologies since I have not taken the time to learn rust yet, but I've written a lot of modern C++. Is the ownership model kind of like std::unique_ptr and std::move, and `Rc<RefCell<Node>>` the same idea as `std::shared_ptr`? But less idiomatic? Or do I have the wrong idea?
- khuey 1y agoNot really, because Rust enforces a "many readers or one writer" invariant on everything that has no C++ equivalent. That invariant is precisely what makes the doubly-linked list case hard (because every interior node in the list would be readable from two places, which means it can never be written to).
- pornel 1y agoSo that you learn that loaning is for giving temporary shared^exclusive access within a statically-known scope, and not for storing data. Trying to construct permanent data structures using non-owning references is a very common novice mistake in Rust. It's similar to how users coming from GC languages may expect pointers to local variables to stay valid forever, even after leaving the scope/function. Just like in C you need to know when malloc is necessary, in Rust you need to know when self-contained/owning types are necessary.
- mplanchard 1y agoThe biggest thing I’ve run into where I really want self-referential types is for work that I want to perform once and then cache, while still needing access to the original data. An example: parsing a cookie header to get cookie names and values. In that case, I settled on storing indexes indicating the ranges of each key and value instead of string slices, but it’s obviously a bit more error prone and hard to read. Benchmarking showed this to be almost twice as fast as cloning the values out into owned strings, so it was worth it, given it is in a hot path. I do wish it were easier though. I know there are ways around this with Pin, but it’s very confusing IMO, and still you have to work with pointers rather than just having a &str.
- pjmlp 1y agoNote that some users of GC languages that support stack allocation, are used that it is a compiler error trying to have such a pointer/reference. D example, https://godbolt.org/z/bbfbeb19a https://godbolt.org/z/bbfbeb19a > Error: returning `& my_value` escapes a reference to local variable `my_value` C# example, https://godbolt.org/z/Y8MfYMMrT https://godbolt.org/z/Y8MfYMMrT > error CS8168: Cannot return local 'num' by reference because it is not a ref local
- lmm 1y agoBecause you care about productivity and safety more than l33t h4x0r hazing rituals?
- Ar-Curunir 1y agoBecause most of my code is not doubly-linked lists!
- scotty79 1y agoIt's not that it doesn't understand doubly linked list. It's just that you don't understand their consequences and have no objections against turning your program state briefly into inconsistent bullshit to facilitate them. The compiler minds. Unless you use Rc<>. That's what this language has for expressing inconsistency. Or unsafe {} if you are cocky. Borrows are not named pointers for a reason.
- woah 1y agoDoes anyone still have trouble learning Rust? I thought that was kind of a 2015 thing
- mre 1y agoThe thing is, once you internalized the concepts (ownership, borrowing, lifetimes), it's very hard to remember what made it difficult in the first place. It's "curse of knowledge" in some sense. What's changed since 2015 is that we ironed out some of the wrinkles in the language (non-lexical lifetimes, async) but the fundamental mental model shift required to think in terms of ownership is still a hurdle that trips up newcomers.
- echelon 1y ago100%. Newcomers still struggle a bit, especially if they've never used C/C++ before. A good way to get people comfortable with the semantics of the language before the borrow checker is to encourage them to clone() strings and structs for a bit, even if the resulting code is not performant. Once they dip their toes into threading and async, Arc<Lock<T>> is their friend, and interior mutability gives them some fun distractions while they absorb the more difficult concepts.
- mre 1y agoDo you mean `Arc<Mutex<T>>`? Yeah, I agree. Wrote a blog post on that topic as well: https://corrode.dev/blog/prototyping/ https://corrode.dev/blog/prototyping/ The title is a bit of a misnomer, but it's about beginner-friendly escape hatches in the language. Perhaps it's useful to newcomers.
- echelon 1y agoAny lock, but that's generally the best choice. Great post! It's got a ton of advice for being productive, and it should be especially useful for beginners.
- SoftTalker 1y ago
- Havoc 1y agoI thought it was quite manageable at beginner level…though I haven’t dived into async which I gather is a whole different level of pain
- echelon 1y agoAsync and the "function color" "problem" fall away if your entire app is in an async runtime. Almost 90% of the Rust I write these days is async. I avoid non-async / blocking libraries where possible. I think this whole issue is overblown.
- spion 1y agoHow are async closures / closure types, especially WRT future pinning?
- echelon 1y agoWhile I'd like to have it, it doesn't stop me from writing a great deal of production code without those niceties. When it came time for me to undo all the async-trait library hack stuff I wrote after the feature landed in stable, I realized I wasn't really held back by not having it.
- mplanchard 1y agoAsync closures landed in stable recently and have been a nice QoL improvement, although I had gotten used to working around their absence well enough previously that they haven’t been revolutionary yet from the like “enabling new architectural patterns” perspective or anything like that. I very rarely have to care about future pinning, mostly just to call the pin macro when working with streams sometimes.
- sodality2 1y agoThat’s not a solution to the coloring problem any more than making everything red was in 2015 (ie, all the tradeoffs mentioned in the article [0] still apply). [0]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
- ants_everywhere 1y agoA learning curve measures time on the x axis and progress on the y axis. A flat learning curve means you never learn anything :-\
- tacitusarc 1y agoThis is incorrect. A learning curve measures expertise on the x axis and effort on the y axis. Hence the saying "steep learning curve".
- ergonaught 1y agohttps://en.wikipedia.org/wiki/Learning_curve https://en.wikipedia.org/wiki/Learning_curve
- tacitusarc 1y agoIt is unclear how this comment was meant; in any case, it is appreciated. As stated in the link: “The common English usage aligns with a metaphorical interpretation of the learning curve as a hill to climb.” Followed by a graph plotting x “experience” against y “learning.”
- saretup 1y agoThat’s interesting. I always intuitively assumed x-axis was progress and y-axis was cumulative effort.
- tacitusarc 1y agoCalling it inaccurate was too harsh; my definition only became common usage in 1970, and the original “time vs learning” is still used in academic circles.
- nchmy 1y agoAcademic circles? That's how it is just used in general. You're the anomaly.
- Animats 1y agoIt's like reading "A Discipline of Programming", by Dijkstra. That morality play approach was needed back then, because nobody knew how to think about this stuff. Most explanations of ownership in Rust are far too wordy. See [1]. The core concepts are mostly there, but hidden under all the examples. - Each data object in Rust has exactly one owner. - Ownership can be transferred in ways that preserve the one-owner rule. - If you need multiple ownership, the real owner has to be a reference-counted cell. Those cells can be cloned (duplicated.) - If the owner goes away, so do the things it owns. - You can borrow access to a data object using a reference. - There's a big distinction between owning and referencing. - References can be passed around and stored, but cannot outlive the object. (That would be a "dangling pointer" error). - This is strictly enforced at compile time by the borrow checker. That explains the model. Once that's understood, all the details can be tied back to those rules. [1] https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html https://doc.rust-lang.org/book/ch04-01-what-is-ownership.htm...
- ameliaquining 1y agoSummarizing a set of concepts in a way that feels correct and complete to someone who understands them, is a much easier task than explaining them to someone who doesn't. If we put this in front of someone who's only worked with call-by-sharing languages, do you think they'll get it right away? I'm skeptical.
- Animats 1y agoRight. If you come to Rust from C++ and can write good C++ code, you see this as "oh, that's how to think about ownership". Because you have to have a mental model of ownership to get C/C++ code to work. But if you come from Javascript or Python or Go, where all this is automated, it's very strange.
- bloppe 1y agoFor me it really clicked when I realized ownership / lifetimes / references are just words used to talk about when things get dropped. Maybe because I have a background in C so I'm used to manual memory management. Rust basically just calls 'free' for you the moment something goes out of scope. All the jargon definitely distracted me from grasping that simple core concept.
- Waterluvian 1y agoWrite a CHIP8 emulator! Bonus: do it with no heap allocation. This actually makes it easier because you basically don’t deal with lifetimes. You just have a state object that you pass to your input system, then your guest cpu system, then your renderer, and repeat. And I mean… look just how incredibly well a match expression works for opcode handling: https://github.com/ablakey/chip8/blob/15ce094a1d9de314862abbe21416a2660ae97625/src/chip8.rs#L194 https://github.com/ablakey/chip8/blob/15ce094a1d9de314862abb... My second (and final) rust project was a gameboy emulator that basically worked the same way. But one of the best things about learning by writing an emulator is that there’s enough repetition you begin looking for abstractions and learn about macros and such, all out of self discovery and necessity.
- namuol 1y agoI’ve found emulators to be a pretty poor first project for rust specifically for the reasons you alluded to: That you need to know to write it without heap allocation (or other hoop jumping so long as you avoid juggling lifetimes) when so much literature and example emulator code doesn’t do this is a recipe for a bad experience. Ask me how I know. If you’re going to write an emulator in this style, why even use an imperative language when something like Haskell is designed for this sort of thing?
- Waterluvian 1y agoThese emulators already exist in basically every language, so why do anything? The point is the journey, which doesn’t need to be the shortest, most optimal path possible.
- namuol 1y agoI’m saying it’s not optimal for learning the language, not that it’s not worth doing. I’ve worked on 3 different emulators for fun over the last few years, my first in rust. It was a bad experience for learning rust because I was following prior art which relied heavily on shared data structures and lots of poking randomly at blocks of RAM, a very natural way to think when you’re engrossed in the mechanics of an 8-bit CPU. I had a better time writing a raycaster and later a path tracer, although by then I had learned to avoid dealing with the borrow checker…
- cadamsdotcom 1y agoRust is wonderful but humbling! It has a built in coach: the borrow checker! Borrow checker wouldn't get off my damn case - errors after errors - so I gave in. I allowed it to teach me - compile error by compile error - the proper way to do a threadsafe shared-memory ringbuffer. I was convinced I knew. I didn't. C and C++ lack ownership semantics so their compilers can't coach you. Everyone should learn Rust. You never know what you'll discover about yourself.
- noman-land 1y agoGot recommended learning paths? I tend to prefer follow along adventures via video.
- maxbond 1y agoCheck out Jon Gjengset. https://www.youtube.com/@jonhoo https://www.youtube.com/@jonhoo
- Measter 1y agoI wouldn't agree with that. Jon's content is great, but it's really not aimed at beginners, and some of his stuff really gets into the weeds.
- maxbond 1y agoGjengset was very helpful to me as a beginner, and the "Crust of Rust" and "Decrusted" series are aimed at beginners, but mileage varies and there's room for more suggestions if anyone has them.
- gerdesj 1y ago"Rust is wonderful but humbling!" It's an abstraction and convenience to avoid fiddling with registers and memory and that at the lowest level. Everyone might enjoy their computation platform of their choice in their own way. No need to require one way nor another. You might feel all fired up about a particular high level language that you think abstracts and deploys in a way you think is right. Not everyone does. You don't need a programming language to discover yourself. If you become fixated on a particular language or paradigm then there is a good chance you have lost sight of how to deal with what needs dealing with. You are simply stroking your tools, instead of using them properly.
- sesm 1y agoIs there a concise document that explains major decisions behind Rust language design for those who know C++? Not a newbie tutorial, just straight to the point: why in-place mutability instead of other options, why encourage stack allocation, what problems with C++ does it solve and at what cost, etc.
- abirch 1y agoI think the major decisions behind Rust is being explicit and making the programmer make decisions. No NULLs, no Implicit conversions, no dangling pointers. Lifetimes, Optional, Results, each Match branch needs to exist, etc. Side note: Stack allocation is faster to execute as there's a higher probability of it being cached. Here is a free book for a C++ to Rust explanation. https://vnduongthanhtung.gitbooks.io/migrate-from-c-to-rust/content/ https://vnduongthanhtung.gitbooks.io/migrate-from-c-to-rust/...
- sesm 1y ago> being explicit and making the programmer make decisions Why RAII then? > C++ to Rust explanation I've seen this one. It is very newbie oriented, filled with trivial examples and doesn't even have Rust refs to C++ smart pointers comparison table.
- landr0id 1y ago>> being explicit and making the programmer make decisions >Why RAII then? Their quote is probably better rephrased as _being explicit and making the programmer make decisions when the compiler's decision might impact safety_ Implicit conversion between primitives may impact the safety of your application. Implicit memory management and initialization is something the compiler can do safely and is central to Rust's safety story.
- BlackFly 1y agoI would say that RAII is very explicit: Resource Acquisition Is Initialization. When you initialize the struct representing the resource you are acquiring the resource. If you have a struct representing a resource you have the resource. Knowing this, you are also acquiring a call to drop when it goes out of scope. I would argue that the difference here isn't explicitness. Instead, I would argue that rust is favoring a form of explicitness together with correctness. You have to clean up that resource. I have seen arguments that you should be allowed to leak resources, and I am sympathetic, but if we agree on explicitness as a goal then perhaps you might understand the perspective that a leak should be explicit and not implicit in a the lack of a call a some method. Since linear types are difficult to implement auto-drops are easier if you favor easily doing the correct thing. If you want to leak your resource, stash it in some leak list or unsafe erase it. That is the thing that should be explicit: the unusual choice, not all choices and not the usual choice alone. But yeah, the drop being implicit in the explicit initialization does lead to developers ignoring it just like a leak being implicit if you forget to call a function often leads to unintentionally buggy programs. So when a function call ends they won't realize that a large number of objects are about to get dropped. To answer your original question, the rationale is not in one concise location but is spread throughout the various RFCs that lead to the language features.
- CobrastanJorji 1y agoRegarding the first example, the longest() function, why couldn't the compiler figure it out itself? What is the design flaw?
- mplanchard 1y agoYou’re passing in two references and returning a reference. The compiler knows the returned reference must be tied to one of the incoming references (since you cannot return a reference to something created within the function, and all inputs are references, the output must therefore be referencing the input). But the compiler can’t know which reference the result comes from unless you tell it. Theoretically it could tell by introspecting the function body, but the compiler only works on signatures, so the annotation must be added to the function signature to let it determine the expected lifetime of the returned reference.
- NobodyNada 1y ago> Theoretically it could tell by introspecting the function body, but the compiler only works on signatures Note that this is an intentional choice rather than a limitation, because if the compiler analyzed the function body to determine lifetimes of parameters and return values, then changing the body of a function could be a non-obvious breaking API change. If lifetimes are only dependent on the signature, then its explicit what promises you are or are not making to callers of a function about object lifetimes, and changing those promises must be done intentionally by changing the signature rather than implicitly.
- j16sdiz 1y ago> changing the body of a function could be a non-obvious breaking API change This. Many trival changes breaks API. This is not ideal for library developers. You can argue it is broken already, but this is forcing the breakage onto every api caller, not just some broken caller.
- mplanchard 1y agoOh yes, I didn’t mean to make it sound like a problem. I personally strongly prefer signature-based typing vs like in Typescript where you can easily be returning an entirely unintentional type and not realize it until you try to use it in an explicitly typed context down the line. I also imagine it’s much faster for the type-checking pass of the compiler to just look at the signatures.
- mdwhatcott 1y ago[flagged]
- mplanchard 1y agoI suspect an article like this says more about the author than the language. Note I’m not being critical of the author here. I think it’s lovely to turn your passion into trying to help others learn.
- LAC-Tech 1y agoI have taken the time to learn rust and you're absolutely right. It's a very complex, design-by-committee language. It has brilliant tooling, and is still much less complex than it's design-by-committee competitor C++, but it will never be easy to learn.
- worik 1y agoThere is a trade off. Rust gave us fast, and safe. It did not give us "easy to learn". I think it is a very good example of why "design by committee" is good. The "Rust Committee" has done a fantastic job Thank you They say a camel is a horse designed by a committee (https://en.wiktionary.org/wiki/a_camel_is_a_horse_designed_by_a_committee https://en.wiktionary.org/wiki/a_camel_is_a_horse_designed_b...) Yes: * Goes twice as far as a horse * On half the food and a quarter the water of a horse * Carries twice as much as a horse Yes, I like design by committee. I have been on some very good, and some very bad committees, but there is nothing like the power of a good committee Thank you Rust!
- LAC-Tech 1y agoIt's just a programming language, not a religion.
- psychoslave 1y agoWell, it does look like there is a will to mimic religious social structure in the community, be it as a satiric form of it. I mean, I guess they purposefully named their pancakes cargo, as in "cargo cult", didn't they? Rustacean, rustomicon, and the other few words I saw leak out of the community all seem to go in the same spirit. I'm almost surprised they didn't went with more fancy terms for these core concepts of ownership and borrowing. Perl was also full of religious stuff like blessing your object, though Larry was actually more in the "true devot" side of the line.
- worik 1y agoSurrender! to compile Weather the ferocious storm You will find, true bliss
- ajross 1y ago> Use String and clone() and unwrap generously; you can always refactor later At that point you might as well be writing Java or Go or whatever though. GC runtimes tend actually to be significantly faster for this kind of code, since they can avoid all those copies by sharing the underlying resource. By the same logic, you can always refactor the performance-critical stuff via your FFI of choice.
- landr0id 1y agoSo long as you're aware that you're not optimizing, it's fine. Trying to build something useful as a new Rust dev while worrying about lifetimes is going to be quite challenging, unless your intention is to specifically learn about lifetimes and the borrow checker. Yes the borrow checker is central to Rust, but there are other features to the language that people _also_ need to learn and explore to be productive. Some of these features may attract them to Rust (like pattern matching / traits / etc.)
- rafram 1y agoNot to mention that even though you can always refactor later, will you really? It’s much easier not to. In my experience, hobbyist Rust projects end up using unwrap and panic all over the place, and it’s a giant mess that nobody will ever refactor.
- mikepurvis 1y agoI went through this the first year that I did Advent of Code in rust, like okay I read in all the strings from the input file and now they're in a vector, so I'm going to iterate the vector and add references to those strings into this other structure, but of course they're still owned by the original vector, that's awkward. Oh wait I can iter_into and then I get owned objects and that ownership can be transferred to the other structure instead, but now I need them to also be keys in a map, do I use references for that too? Cloning small objects is lightning fast, turns out in a lot of these cases it makes sense to just do the clone, especially when it's a first pass. The nice thing is that at least rust makes you explicitly clone() so you're aware when it's happening, vs other languages where it's easy to lose track of what is and isn't costing you memory. So you can see that it's happening, you can reason about it, and once the bones of the algorithm are in place, you can say "okay, yes, this is what should ultimately own this data, and here's the path it's going to take to get there, and these other usages will be references or clones.
- 8s2ngy 1y agoIt took me a few tries to get comfortable with Rust—its ownership model, lifetimes, and pervasive use of enums and pattern matching were daunting at first. In my initial attempt, I felt overwhelmed very early on. The second time, I was too dogmatic, reading the book line by line from the very first chapter, and eventually lost patience. By then, however, I had come to understand that Rust would help me learn programming and software design on a deeper level. On my third try, I finally found success; I began rewriting my small programs and scripts using the rudimentary understanding I had gained from my previous encounters. I filled in the gaps as needed—learning idiomatic error handling, using types to express data, and harnessing pattern matching, among other techniques. After all this ordeal, I can confidently say that learning Rust was one of the best decisions I’ve made in my programming career. Declaring types, structs, and enums beforehand, then writing functions to work with immutable data and pattern matching, has become the approach I apply even when coding in other languages.
- explodes 1y agoI had quite a similar experience. During the 3rd attempt at learning, everything seemed to click and I was able to be effective at writing a few programs. This is all despite a long career as a programmer. Seems like some things just take repetition. The "Dagger" dependency injection framework for the JVM took me 3 'learning attempts' to understand as well. May say more about myself than about learning something somewhat complicated.
- ModernMech 1y agoYour experience matches an observation I have made, that when C++ developers approach Rust for the first time they often "fight the borrow checker" when they use C++ idioms in Rust. Then they start to learn Rust idioms, and bring them back to C++, which causes them to write more robust code despite not having and borrow checking at all.
- surajrmal 1y agoFor the most part true, but there exists patterns I can do safely and easily in c++ which I cannot in rust. Structured concurrency being one of the major ones. If a child object takes a reference to the parent, I shouldn't need to do it by way of an Arc, but because of the fact that leaking memory is safe, this isn't possible to do in rust without using the unsafe keyword. So I end up with more refcounting that I want. (This is often in the context of async). I don't bring this pattern back to c++ with me.
- doug_durham 1y agoThis reads like a list of symptoms of what's wrong with the ergonomics of Rust. This is not to bash Rust. It has its uses. But you need to balance what you are sacrificing for what you are getting.
- baalimago 1y ago>For instance, why do you have to call to_string() on a thing that’s already a string? It's so hard for me to take Rust seriously when I have to find out answers to unintuitive question like this
- 3836293648 1y agoC++ is a horribly cmplicated language, sometimes I have to cast something to an int when it's already an integer. /s I have a hard time understanding why people have such a hard time accepting that you need to convert between different text representations when it's perfectly accepted for numbers.
- SkiFire13 1y agoJust because a language is not high level enough to have a unique concept of "string" type doesn't mean you shouldn't take it seriously.
- swiftcoder 1y agoEven very high-level languages don't have singular concepts of string. Every serious language I can think of differentiates between: - A sequence of arbitrary bytes - A sequence of non-null bytes interpreted as ASCII - A sequence of unicode code points, in multiple possible encodings
- int_19h 1y agoI can't think of many languages that differentiate between #1 and #2 in your list. And languages that differentiate between #1 and #3 generally don't refer to #1 as "strings" at all, so you still have a single definitive notion of what a string is (and some other things that are emphatically not strings).
- swiftcoder 1y agoYeah, a bunch of languages don't strongly differentiate between all three of these. For example, C++ differentiates between #1 and #2 (although it has woefully inadequate out-of-box support for #3). Python (> 3) calls #1 bytes / bytearray, and calls #3 string. #2 is only really supported for FFI with C (i.e. ctypes.c_char_p and friends)
- mellosouls 1y agoGood article, thoughtful and well-written and (idioms aside) much of it applicable to learning other languages.
- MasterYoda 1y agoWhat is it that make that rust is said to have steep learning curve compared to other programing languages (in the same category)?
- School-Cotton 1y agoIt doesn’t. The only language I know of in the same category as Rust is C++, which is much harder to learn and use.
- hu3 1y agoMostly borrow checker. Lifetime syntax can be off putting.
- jillesvangurp 1y agoRust has a few big hurdles for new users: - it's very different from other languages. That's intentional but also an obstacle. - it's a very complex language with a very terse syntax that looks like people are typing with their elbows and are hitting random keys. A single character can completely change the meaning of a thing. And it doesn't help that a lot of this syntax deeply nested. - a lot of its features are hard to understand without deeper understanding of the theory behind them. This adds to the complexity. The type system and the borrowing mechanism are good examples. Unless you are a type system nerd a lot of that is just gobblygook to the average Python or Javascript user. This also makes it a very inappropriate language for people that don't have a master degree in computer science. Which these days is most programmers. - it has widely used macros that obfuscate a lot of things that further adds to the complexity. If you don't know the macro definitions, it just becomes harder to understand what is going on. All languages with macros suffer from this to some degree. I think LLMs can help a lot here these days. When I last tried to wrap my head around Rust that wasn't an option yet. I might have another go at it at some time. But it's not a priority for me currently. But llms have definitely lowered the barrier for me to try new stuff. I definitely see the value of a language like users. But it doesn't really solve a problem I have with the languages I do use (kotlin, python, typescript, etc.). I've used most popular languages at some point in my life. Rust is unique in how difficult it is to learn.
- greazy 1y ago> it has widely used macros that obfuscate a lot of things that further adds to the complexity. This is what's stumped me when learning Rust. It could be the resources I used, whixh introduced macros early on with no explanation.
- ModernMech 1y agoMacros are introduced early in Rust for a couple reasons. 1. println!() is a macro, so if you want to print anything out you need to grapple with what that ! means, and why println needs to be a macro in Rust. 2. Macros are important in Rust, they're not a small or ancillary feature. They put a lot of work into the macro system, and all Rust devs should aspire to use and understand metaprogramming. It's not a language feature reserved for the upper echelon of internal Rust devs, but a feature everyone should get used to and use.
- truth_seeker 1y agoCompared to other programming languages, Rust's compiler and linters go a long way to implement best practices at build time.
- jamil7 1y agoThe whole learning curve seems overblown to me. You don’t need to grok every part of the language to start using it and being productive.
- TheChaplain 1y agoMy problem with rust is not the learning curve, but the absolute ugliness of the syntax. It's like Perl and C++ template metaprogramming had a child. I just can't stand it. Python is my favourite, C is elegance in simplicity and Go is tolerable.
- Oreb 1y agoHave you had a look at Nim? I think you may like it.
- mkaic 1y agoI love the look and feel of Nim, but found it to be stuck in a weird chicken-and-egg situation where it didn't have enough of a following to have a Convenient Package For Everything, ultimately turning me off it. Of course I recognize that the only way a language gets a Convenient Package For Everything is if it gets popular, but still...
- krior 1y agoC may be simple, but its too simple to be called elegant. The lack of namespacing comes to mind. Or that it is a staticly typed language, whose type system is barely enforced (you have to declare all types, but sometimes it feels like everything decays to int and *void without the right compiler incantations). Or the build system, where you have to learn a separate language to generate a separate language to compile the program (which a both also not really simple and elegant in my eyes). Or null-terminated strings: to save some 7 bytes per string (on modern platforms) C uses one of the most dangerous and unelegant constructs in the popular part of the programming-world. Or the absolutely inelegant error handling, where you either return an in-band-error-value, set a global variable or both or just silently fail. Or the standard-library, that is littered with dangerous functions. Or the reliance of the language definition on undefined behaviour, that forces you to read a 700-page, expensive document back to back to know whether a vital check in your program might be ignored by compilers or when your program might shred your hard drive, despite you never instructing it to do so. Or... C has a simple syntax, but it is most certainly not elegant.
- umajho 1y ago> …. For instance, “a trait is a bit like an interface” is wrong, … Wait. What's wrong with this statement?
- steveklabnik 1y agoI think this is just a mistake, in that “a bit like” is correct. The ways in which it are different depend on which language you are taking the concept of “interface” from, but the statement that it’s like one is accurate.
- umajho 1y agoThat makes sense.
- pjmlp 1y agoTraits are yet another way of doing the CS concept of interfaces, regardless of the author's opinion. Polymorphic dispatch on a set of known operations, that compose a specific type.
- writebetterc 1y agoOwnership and lifetimes are arguably not about Rust, but about how we construct programs today. Big, interdependent, object graphs are not a good way of constructing programs in Rust or C++.
- mprovost 1y agoOne approach that I don't see often enough is to focus on learning a subset of the language first. For example, in my own book on Rust, I skip teaching lifetimes. It's not necessary to write functions with lifetimes to build quite a few fully functioning programs. The same with macros (although the fact that their signatures are opaque doesn't make it easy for beginners). On the other hand, I disagree with the advice to rely on copy() or clone() - it's better to learn about borrowing from the beginning since it's such a fundamental part of the language.
- scotty79 1y agoFor me the most important thing about Rust is to understand that it's a langue with value semantics. Which makes it completely different than every mainstream language you encountered so far. Variable in rust is not a label you can pass around and reuse freely. It's a fixed size physical memory that values can be moved into or moved out of. Once you understand that everything makes sense. The move semantics, cloning, borrowing, Sized, impl. Every language design element of rust is a direct consequence of that. It's the values that get created, destroyed and moved around and variables are actual first-class places to keep them with their own identity separate from values that occupy them. It's hard to notice this because Rust does a lot to pretend it's a "normal" language to draw people in. But for anyone with experience in programming that attempts to learn Rust I think this realization could make the process at least few times easier. It's hard to shift to this new paradigm and embrace it, so in the meantime feel use a lot of Rc<> and cloning if you just need to bang out some programs like you would in any other mainstream language.
- cyber1 1y ago"Safe" Rust is generally a simple language compared to C++. The borrow checker rules are clean and consistent. However, writing in it isn't as simple or intuitive as what we've seen in decades of popular systems languages. If your data structures have clear dependencies—like an acyclic graph then there's no problem. But writing performant self-referential data structures, for example, is far from easy compared to C++, C, Zig, etc. On the opposite, "Unsafe" Rust is not simple at all, but without it, we can't write many programs. It's comparable to C, maybe even worse in some ways. It's easy to break rules (aliasing for exmaple). Raw pointer manipulation is less ergonomic than in C, C++, Zig, or Go. But raw pointers are one of the most important concepts in CS. This part is very important for learning; we can't just close our eyes to it. And I'm not even talking about Rust's open problems, such as: thread_local (still questionable), custom allocators (still nightly), Polonius (nightly, hope it succeeds), panic handling (not acceptable in kernel-level code), and "pin", which seems like a workaround (hack) for async and self-referential issues caused by a lack of proper language design early on — many learners struggle with it. Rust is a good language, no doubt. But it feels like a temporary step. The learning curve heavily depends on the kind of task you're trying to solve. Some things are super easy and straightforward, while others are very hard, and the eventual solutions are not as simple, intuitive or understandable compared to, for example, C++, C, Zig, etc. Languages like Mojo, Carbon (I hope it succeeds), and maybe Zig (not sure yet) are learning from Rust and other languages. One of them might become the next major general-purpose systems language for the coming decades with a much more pleasant learning curve.
- make3 1y ago"raw pointers are one of the most important concepts in CS" that's a reach and a half, I don't remember the last time I've used one
- wepple 1y agoThe concept of being able to reference a raw memory address and then access the data at that location directly feels pretty basic computer science. Perhaps you do software engineering in a given language/framework? A clutch is fundamental to automotive engineering even if you don’t use one daily.
- GenshoTikamura 1y ago> Stop resisting. That’s the most important lesson > Accept that learning Rust requires... > Leave your hubris at home > Declare defeat > Resistance is futile. The longer you refuse to learn, the longer you will suffer > Forget what you think you knew... Now it finally clicked to me that Orwell's telescreen OS was written in Rust
- atoav 1y agoBut it is true. My own biggest mistake when learning Rust was that I tried to torce Object Oriented paradigms on it. That went.. poorly. As soon as I went "fuck it, I just do it like you want" things went smoothly.
- rikafurude21 1y agoSounds like an abusive relationship if im being honest. Your programming language shouldnt constrict you in those ways.
- lagniappe 1y agoIt helps to understand the cultural ethos of the original rust devs, and the situation that gave rise to it.
- jplusequalt 1y ago>Your programming language shouldn't constrict you in those ways Says who? Programming languages come in all shapes and sizes, and each has their tradeoffs. Rust's tradeoff is that the compiler is very opinionated about what constitutes a valid program. But in turn it provides comparable performance to C/C++ without many of the same bugs/security vulnerabilities.
- atoav 1y agoAlso: everybody can write a program that does the thing it is intended to do. That is the easy part. The hard part is writing a program that does not do things it isn't intended to do while existing in a ever changing environment and even be subjected to changes of its own source code. So the hard part isn't getting code to work, it is ensuring it is only working in the intended ways, even when your co-worker (or your future self) acts like an unhinged, unristricted idiot. And that means using enforced type systems, validation, strict rules. If you are a beginner cobbling hobby programs an anything-goes approach to software may feel nice and like freedom, but beyond a certain level of complexity it will land you in a world of pain. Any great C programmer whose code I ever had the pleasure of reading has a plethora of unwritten rules they enforce through their heads. And these rules exist there for a reason. When you have a language that enforces these rules for you, that gives you the freedom to dare more, not less, as certain things would be very risky with manual checking. It is like the foam pit in extreme sports. While it is certainly more manly to break your neck in ten consecutive tripple-backflip tries, you are going to get there faster with a foam pit where you can try out things. And the foam pit transforms the whole scene, becaus people can now write code that before would crash and burn without feeling restricted. Funny how that goes.
- Meneth 1y agoFeels like a cult manual. "Do all things our way! Don't question anything!"
- noodletheworld 1y agoIf you’re not prepared to be humble when you learn, you’re not actually trying to learn. (Or not trying very hard anyway), There’s a difference between “do it like this” and “keep an open mind when you do this”. You can learn rust any way you want; this is just a guide for how to learn it effectively. A fairer comparison would be learning Japanese by going to Japan and insisting on speaking English except in Japanese language classes. Yes, you can do that. …but it is not the most effective way to learn. The best way to learn is full immersion. It’s just harder. If you don’t want that, don’t do it. It’s not a cult. That’s just lazy flippant anti-rust sentiment.
- echelon 1y agoThat's how every student forced to take a programming elective feels about any programming language.
- commisson 1y ago[dead]
- ramon156 1y agoThese replies mainly revealed to me the general response people have on being corrected. Its very easy to become a stubborn programmer after being in the industry for so long. I'd advise these people to personally figure out why they're so against compiler suggestions. Do you want to do things differently? What part stops you from doing that?
- jokoon 1y agoSorry, but it's easier to learn basic C++/C, and let beginner developers get lessons from a basic linter or clang-tidy for about the same result, for a fraction of the developer cost. I want rust to be adopted and I believe companies should force it, but you will not get adoption from young developers and even less from senior C++ developers. Not to mention rewriting existing C++ code in rust, which cost would be astronomical, although I do believe companies should invest in rewriting things in rust because it's the right thing to do.
- 999900000999 1y agoThe only way I think I’ll learn rust is if there’s a surge of job opportunities paying 300 K and up which require it. The potential is definitely there, it looks like it might compete with C++ in the quant . But we already have ocaml . From Jane Street, at least for me if you’re going to tell me, it’s time to learn an extremely difficult programming language, I need to see the money. So far my highest paid programming job was in Python
- jmull 1y agoI'm not sure there are many cases where I would choose rust. I'm open to it. I just think in any given situation there would most likely be a better option. Perhaps it will become prevalent enough that it will make sense in the future.
- lvass 1y agoIt's definitely the perfect language for writing a browser from scratch, as it was designed for almost 20 years ago. Of course nowadays it's already completely dominating that area, and their creator's "unix" has taken over the world and was not overtaken by something a random dude called ladybird.
- coldpie 1y agoIt's the best fit I'm aware of for any task where correctness is worth the dev time hit. That includes systems programming tasks, where you're shuffling memory and files and system resources around and an error could kill your program or corrupt resources; or as a library for critical parts of your program, which could then provide an interface for other languages to use that allow for faster development & prototyping.
- toprerules 1y agoAs systems programmer there are no better options. C is inherently unsafe, C++ is awful to work with and unsafe without careful use of pre-made safe abstractions that can't catch everything at runtime... I have written C for decades and love the language, but Rust has convinced me that we need to evolve beyond the 70s. There's no excuse anymore.
- rx123dx 1y agoRust will prevail only if it fixes C++ interop
- nmeofthestate 1y ago"You will have a much better time if you re-read your code to fix stupid typos before pressing “compile.”" This is a strange one - I thought the rust compiler had famously helpful error messages, so why would I want to pore over my code looking for stupid typos when I can let the compiler find them for me? I am guaranteed to make stupid typos and want the computer to help me fix them.
- steveklabnik 1y agocargo fix can automatically fix some, but not all, issues.
- zahlman 1y agoBecause if you're "guaranteed to make stupid typos" and you don't have the habit of repeatedly trying again after small changes (which becomes harder to establish when compilation takes longer), then you're likely to make multiple stupid typos at a time. And if you check for them yourself first, you have a decent chance to catch and fix several at a time - whereas it's often not the brightest idea to try to respond to multiple compiler errors at a time, no matter how helpful they are.
- toprerules 1y agoAs a systems programmer I found Rust relatively easy to learn, and wonder if the problem is non-systems programmers trying to learn their first systems language and having it explicitly tell them "no, that's dangerous. no, that doesn't make sense". If you ask a front end developer to suddenly start writing C they are going to create memory leaks, create undefined behavior, create pointers to garbage, run off the end of an array, etc. But they might "feel" like they are doing great because there program compiles and sort of runs. If you have already gotten to the journeyman or mastery experience level with C or C++ Rust is going to be easy to learn (it was for me). The concepts are simply being made explicit rather than implicit (ownership, lifetimes, traits instead of vtables, etc).
- SyrupThinker 1y agoI think this is good insight, and I would extend this further to “coming from a less strict language to a very strict one”. As someone who self-learned Rust around 1.0, after half a year of high school level Java 6, I’ve never had the problems people (even now) report with concepts like the ownership system. And that despite Rust 1.0 being far more restrictive than modern Rust, and learning with a supposedly harder to understand version of “The Book”. I think it’s because I, and other early Rust learners I’ve talked to about this, had little preconceived notions of how a programming language should work. Thus the restrictions imposed by Rust were just as “arbitrary” as any other PL, and there was no perceived “better” way of accomplishing something. Generally the more popular languages like JS or Python allow you to mold the patterns you want to use sufficiently, so that they fit into it. At least to me with languages like Rust or Haskell, if you try to do this with too different concepts, the code gets pretty ugly. This can give the impression the PL “does not do what you need” and “imposes restrictions”. I also think that this goes the other way, and might just be a sort of developed taste.
- steveklabnik 1y agoFor whatever it's worth, I used to do a lot of teaching, and I have a similar hunch to > I think it’s because I, and other early Rust learners I’ve talked to about this, had little preconceived notions of how a programming language should work. Thus the restrictions imposed by Rust were just as “arbitrary” as any other PL, and there was no perceived “better” way of accomplishing something.
- the__alchemist 1y agoI seem to have eased into a rust programming style that dodges these, perhaps at the cost of some optimizations. Using the first example, for example (The article suggests this): Don't return an `&str`; use those for transient things only like function parameters; not for struct fields or returned types. I'm starting to wonder what I'm missing out by doing this. Not addressed in the article: Any tips for using the more abstract features, like Cow etc? I hit a problem with this today, where a lib used Cow<&str> instead of String, and the lifetime errors bubbled up into my code. edit: I found this amusing about the article: They demo `String` as a param, and `&str` as a return type for triggering errors; you can dodge these errors simply by doing the opposite!
- ansc 1y ago+1, would be super interesting to learn when this case makes sense! I am doing great with just using owned in structs for single, and (A)Rc for multiple, but it would be cool to learn.
- vlmutolo 1y agoYou can definitely get around a lot of the pain points by using owned types like String as much as possible instead of borrowed types like &str. This is even generally recommended; there’s often no benefit to using the more advanced features of the language. Usually the advanced features come in when you’re looking for better performance. It helps performance a lot to use reference types (borrowed types) to eliminate deep copies (and allocations) with .clone() in a loop, for example. Library authors usually don’t have the luxury of knowing how their code will be used downstream, so diligent authors try to make the code reasonably performant and use these advanced language features to do so. You never know if the consumer of your library will use your function in a hot loop.
- steveklabnik 1y ago> I'm starting to wonder what I'm missing out by doing this. It depends. In some cases, you aren't missing anything. In others, you may lose a bit of efficiency by doing some otherwise un-needed copying. Depending on what you're doing that may be irrelevant. > Any tips for using the more abstract features, like Cow etc? I hit a problem with this today, where a lib used Cow<&str> instead of String, and the lifetime errors bubbled up into my code. You can do the same thing as you do with &str by calling into_owned on the Cow, you'd get a String back.