[HN Gopher] Flattening Rust's learning curve
       ___________________________________________________________________
        
       Flattening Rust's learning curve
        
       Author : birdculture
       Score  : 415 points
       Date   : 2025-05-13 22:25 UTC (1 days ago)
        
 (HTM) web link (corrode.dev)
 (TXT) w3m dump (corrode.dev)
        
       | dmitrygr wrote:
       | > 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 wrote:
         | I'd rather pair program with someone wary of double-linked
         | lists, but is really hot on understanding ownership than the
         | other way around.
        
         | mre wrote:
         | For 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/
        
           | Animats wrote:
           | Rust 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.
        
             | umanwizard wrote:
             | > 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 wrote:
               | Doubly linked lists are rare, but backlinks to the owner
               | are often needed. It's the same problem, mostly.
        
               | mplanchard wrote:
               | Backlinks work fine with weak Arc references, don't they?
        
               | Animats wrote:
               | Yes. 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
        
             | bigstrat2003 wrote:
             | > Rust still needs a way out of that mess.
             | 
             | It has one: use raw pointers and unsafe. People are way too
             | afraid of unsafe, it's there specifically to be used when
             | needed.
        
           | worik wrote:
           | I 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 wrote:
             | > 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 wrote:
               | If 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 wrote:
               | Getting 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.
        
           | sbrother wrote:
           | Apologies 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 wrote:
             | Not 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 wrote:
         | So 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 wrote:
           | The 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 wrote:
           | Note 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
           | 
           | > Error: returning `& my_value` escapes a reference to local
           | variable `my_value`
           | 
           | C# example, https://godbolt.org/z/Y8MfYMMrT
           | 
           | > error CS8168: Cannot return local 'num' by reference
           | because it is not a ref local
        
         | lmm wrote:
         | Because you care about productivity and safety more than l33t
         | h4x0r hazing rituals?
        
         | Ar-Curunir wrote:
         | Because most of my code is not doubly-linked lists!
        
         | scotty79 wrote:
         | It'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 wrote:
       | Does anyone still have trouble learning Rust? I thought that was
       | kind of a 2015 thing
        
         | mre wrote:
         | The 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 wrote:
           | 100%. 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 wrote:
             | Do you mean `Arc<Mutex<T>>`? Yeah, I agree. Wrote a blog
             | post on that topic as well:
             | 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 wrote:
               | Any 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 wrote:
         | For me it was like Haskell. I spent an afternoon on it, my
         | brain hurt too much, and I relegated it to the category of
         | languages that are too complicated for what I need to do with a
         | computer.
         | 
         | Languages I liked, I liked immediately. I didn't need to climb
         | a mountain first.
        
         | LAC-Tech wrote:
         | Yeah I struggled with it.
        
         | whobre wrote:
         | Coming from C++, I don't find it hard to learn. I do find it
         | annoying, don't love the syntax and absolutely hate cargo.
         | 
         | To each his own, I guess....
        
         | lenkite wrote:
         | Unfortunately, yes. I still end up writing C++ instead of Rust
         | for low-level system stuff. Since I also know Go - I usually
         | prefer that when I need lean, middleware services. Learned Rust
         | (somewhat) with great difficulty but still don't use it
         | anywhere. Still haven't figured out how to design effectively
         | using Rust and approaches suggested in that article like
         | clone()/unwrap() stuff and refactor later just leave a bad
         | taste in that mouth.
        
         | devnullbrain wrote:
         | It's very flattering! It's perhaps the only time in internet
         | computing circles where I repeatedly see people argue in a way
         | that boils down to:
         | 
         | 'I'm not as good as learning things at you'
        
           | fsckboy wrote:
           | > _' I'm not as good as learning things at you'_
           | 
           | some of my cs professors in school were really good at
           | learning things at me, but haskell and rust professors are
           | not generally as good at it as that.
        
       | Havoc wrote:
       | I 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 wrote:
         | Async 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 wrote:
           | How are async closures / closure types, especially WRT future
           | pinning?
        
             | echelon wrote:
             | While 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 wrote:
             | Async 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 wrote:
           | That'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-...
        
           | bigstrat2003 wrote:
           | "Just write everything async" is not remotely a good solution
           | to the problem. Not everything needs to be async (in fact
           | most things don't), and it's much harder to reason about
           | async code. The issue is _very much_ not overblown.
        
             | Salgat wrote:
             | Why is async code harder to reason about? I've been using
             | it in C# and the entire point is that it lets you write
             | callbacks in a way that appears nearly identical to
             | synchronous code. If you dive into concurrency (which is a
             | separate thing but can be utilized with async code, such as
             | joining multiple futures at the same time), that parts hard
             | whether you're doing it with async or with explicit
             | threads.
        
               | umanwizard wrote:
               | > Why is async code harder to reason about?
               | 
               | I don't know about C#, but at least in Rust, one reason
               | is that normal (non-async) functions have the property
               | that they will run until they return, they panic, or the
               | program terminates. I.e. once you enter a function it
               | will run to completion unless it runs "forever" or
               | something unusual happens. This is not the case with
               | async functions -- the code calling the async function
               | can just drop the future it corresponds to, causing it to
               | disappear into the ether and never be polled again.
        
               | Const-me wrote:
               | > I've been using it in C#
               | 
               | One reason why async-await is trivial in .NET is garbage
               | collector. C# rewrites async functions into a state
               | machine, typically heap allocated. Garbage collector
               | automagically manages lifetimes of method arguments and
               | local variables. When awaiting async functions from other
               | async functions, the runtime does that for multiple async
               | frames at once but it's fine with that, just a normal
               | object graph. Another reason, the runtime support for all
               | that stuff is integrated into the language, standard
               | library, and most other parts of the ecosystem.
               | 
               | Rust is very different. Concurrency runtime is not part
               | of the language, the standard library defined bare
               | minimum, essentially just the APIs. The concurrency
               | runtime is implemented by "Tokio" external library. Rust
               | doesn't have a GC; instead, it has a borrow checker who
               | insists on exactly one owner of every object at all
               | times, makes all memory allocations explicit, and exposed
               | all these details to programmer in the type system.
               | 
               | These factors make async Rust even harder to use than
               | normal Rust.
        
               | echelon wrote:
               | None of this is scary.
               | 
               | > The concurrency runtime is implemented by "Tokio"
               | external library.
               | 
               | Scare quotes around Tokio?
               | 
               | You can't use Rails without Rails or Django without
               | Django.
               | 
               | The reason Rust keeps this externally is because they
               | didn't want to bake premature decisions into the
               | language. Like PHP's eternally backwards string library
               | functions or Python's bloated "batteries included"
               | standard library chock full of four different XML
               | libraries and other cruft.
               | 
               | > instead, it has a borrow checker who insists on exactly
               | one owner of every object at all times, makes all memory
               | allocations explicit, and exposed all these details to
               | programmer in the type system
               | 
               | Table stakes. Everyone knows this. It isn't hard or
               | scary, it just takes a little bit of getting used to.
               | Like a student learning programming for the first time.
               | It's not even that hard. Anyone can learn it.
               | 
               | It's funny people complain about something so easy. After
               | you learn to ride the bike, you don't complain about
               | learning to ride the bike anymore.
               | 
               | > Rust is very different.
               | 
               | Oh no!
               | 
               | Seriously this is 2025. I can write async Rust without
               | breaking a sweat. This is all being written by people who
               | don't touch the language.
               | 
               | Rust is not hard. Stop this ridiculous meme. It's quite
               | an easy language once you sit down and learn it.
        
               | guappa wrote:
               | Because 1 mistake somewhere will make your whole
               | application stuck and then you must locate which call is
               | blocking.
        
           | lucasyvas wrote:
           | It's completely overblown. Almost every language with async
           | has the same "problem".
           | 
           | I'm not calling this the pinnacle of async design, but it's
           | extremely familiar and is pretty good now. I also prefer to
           | write as much async as possible.
        
             | echelon wrote:
             | The "function color is a problem" people invented a
             | construct that amplifies the seriousness. It's not really a
             | big deal.
        
               | Measter wrote:
               | My biggest issue with the whole "function colour" thing
               | is that many functions have different colours. Like,
               | these two:                   fn foo() -> String
               | fn bar() -> Result<String, Error>
               | 
               | I can't just treat `bar` the same as `foo` because it
               | doesn't give me a String, it might have failed to give me
               | a String. So I need to give it special handling to get a
               | String.                   async fn qux() -> String
               | 
               | This _also_ doesn 't give me a String. It gives me a
               | thing that _can_ give me a String (an `impl Future
               | <Output=String>`, to be more specific), and I need to
               | give it special handling to get a String.
               | 
               | All of these function have different colours, and I don't
               | really see why it's suddenly a big issue for `qux` when
               | it wasn't for `bar`.
        
               | echelon wrote:
               | Excellent retort!
        
           | kaoD wrote:
           | Async's issue is not coloring. It's introducing issues that
           | just don't exist in sync code like pinning, synchronization,
           | future lifetimes...
           | 
           | Coloring just exacerbates the issues because it's viral, not
           | because coloring itself is an issue.
        
           | notnullorvoid wrote:
           | Agreed. Function coloring is a solution (not a problem), one
           | that's better than the alternatives.
           | 
           | The "function coloring problem" people are harming entire
           | ecosystems. In JS for example there are very popular
           | frameworks thay choose to wrap async in sync execution by
           | throwing when encountering async values and re-running parts
           | of the program when the values resolve. The crazy part with
           | these solutions trying to remove coloring, is they don't,
           | they hide it (poorly). So instead of knowing what parts of a
           | program are async you have no idea.
        
       | ants_everywhere wrote:
       | A learning curve measures time on the x axis and progress on the
       | y axis.
       | 
       | A flat learning curve means you never learn anything :-\
        
         | LambdaComplex wrote:
         | "Flattening the derivative of Rust's learning curve" really
         | doesn't roll off the tongue though
        
           | ants_everywhere wrote:
           | Yeah that's true. But it would be on brand for a post that
           | emphasizes the importance of accuracy and attention to
           | detail.
        
           | cozzyd wrote:
           | A steep line still has a flat derivative
        
         | alpinisme wrote:
         | You may be able to draw one that way but it completely neglects
         | the way people use the term ordinarily "a steep learning curve"
         | is not an easy to learn thing.
         | 
         | In point of fact, I think the intended chart of the idiom is
         | effort (y axis) to reach a given degree of mastery (x axis)
        
           | ants_everywhere wrote:
           | I don't think the idiom has in mind any particular curve. I
           | think it's just another case of a misuse becoming idiomatic
           | without any meaning beyond the phrase taken as a unit. E.g.
           | 
           | - another think coming -> another thing coming
           | 
           | - couldn't care less -> could care less
           | 
           | - the proof of the pudding is in the eating -> the proof is
           | in the pudding
           | 
           | It's usually not useful to try to determine the meaning of
           | the phrases on the right because they don't have any. What
           | does it mean for proof to be in a pudding for example?
           | 
           | The idiom itself is fine, it's just a black box that compares
           | learning something hard to climbing a mountain. But learning
           | curves are real things that are still used daily so I just
           | thought it was funny to talk as if a flat one was desirable.
        
             | psychoslave wrote:
             | https://en.m.wikipedia.org/wiki/Learning_curve
        
             | azornathogron wrote:
             | (not related to your overall point)
             | 
             | > - another think coming -> another thing coming
             | 
             | Fascinating. I had never come across this before. I've only
             | ever seen people use "another thing coming".
        
             | prmph wrote:
             | What "steep learning curve" is getting at is a situation
             | where a lot of learning needs to occurs in a short time to
             | make any meaningful progress.
             | 
             | It is not in opposition to a flat learning curve, but a
             | gentle one
        
         | autoexec wrote:
         | What we want is an "effort/difficulty curve" that measures how
         | difficult something typically is over time from introduction to
         | proficiency
        
         | tacitusarc wrote:
         | This is incorrect. A learning curve measures expertise on the x
         | axis and effort on the y axis. Hence the saying "steep learning
         | curve".
        
           | ergonaught wrote:
           | https://en.wikipedia.org/wiki/Learning_curve
        
             | tacitusarc wrote:
             | It 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 wrote:
             | That's interesting. I always intuitively assumed x-axis was
             | progress and y-axis was cumulative effort.
        
           | tacitusarc wrote:
           | Calling 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.
        
         | raincole wrote:
         | It should be called "learning hill" instead.
         | 
         | People (colloquially) use phrases like "steep learning curve"
         | because they imagine learning curve is something you climb up,
         | a.k.a. a hill.
        
         | Zambyte wrote:
         | It also could mean you don't _need_ to learn beyond a certain
         | point.
        
         | fastasucan wrote:
         | But flattening the learning curve doesn't have to mean to make
         | it completely flat, just making it less steep :)
        
       | Animats wrote:
       | It'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.htm...
        
         | ameliaquining wrote:
         | Summarizing 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 wrote:
           | Right. 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 wrote:
           | For 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.
        
             | josephg wrote:
             | Almost all of it.
             | 
             | Rust also has the "single mutable reference" rule. If you
             | have a mutable reference to a variable, you can be sure
             | nobody else has one at the same time. (And the value itself
             | won't be mutated).
             | 
             | Mechanically, every variable can be in one of 3 modes:
             | 
             | 1. Directly editable (x = 5)
             | 
             | 2. Have a single mutable reference (let y = &mut x)
             | 
             | 3. Have an arbitrary number of immutable references (let y
             | = &x; let z = &x).
             | 
             | The compiler can always tell which mode any particular
             | variable is in, so it can prove you aren't violating this
             | constraint.
             | 
             | If you think in terms of C, the "single mutable reference"
             | rule is rust's way to make sure it can slap noalias on
             | every variable in your program.
             | 
             | This is something that would be great to see in rust IDEs.
             | Wherever my cursor is, it'd be nice to color code all
             | variables in scope based on what mode they're in at that
             | point in time.
        
               | bloppe wrote:
               | Ya, I just think the `mut` thing isn't as much of a
               | challenge to newcomers as the memory management aspect.
        
             | mikepurvis wrote:
             | "Rust basically just calls 'free' for you the moment
             | something goes out of scope."
             | 
             | C++ does that too with RAII. Go ahead and use whatever STL
             | containers you like, emplace objects onto them, and
             | everything will be safely single-owned with you never
             | having to manually new or delete any of it.
             | 
             | The difference is that C++'s guarantees in this regard
             | derive from a) a bunch of implementation magic that exists
             | to hide the fact that those supposedly stack-allocated
             | containers are in fact allocating heap objects behind your
             | back, and b) you cooperating with the restrictions given in
             | the API docs, agreeing not to hold pointers to the member
             | objects or do weird things with casting. You can use
             | scoped_ptr/unique_ptr but the whole time you'll be
             | painfully aware of how it's been bolted onto the language
             | later and whenever you want you can call get() on it for
             | the "raw" underlying pointer and use it to shoot yourself
             | in the foot.
             | 
             | Rust formalizes this protection and puts it into the
             | compiler so that you're _prevented_ from doing it  "wrong".
        
               | throwawaymaths wrote:
               | the tradeoff is that ~you have to guess where rust is
               | doing the frees, and _you might be wrong_. in the end
               | this would be strictly equivalent to an explicit
               | instruction to free, with the compiler refusing to
               | compile if the free location broke the rules.
               | 
               | It's really too bad rust went the RAII route.
        
               | whyever wrote:
               | How often do you care about the order in which objects
               | are dropped?
        
               | throwawaymaths wrote:
               | anything where you need to have stuff run in constant
               | time.
        
               | mikepurvis wrote:
               | If you're in hard realtime land then you can't have any
               | allocations at all; that's a pretty different ballgame.
               | 
               | Destructors _should_ be as simple and side-effect free as
               | possible, with the exception of things like locks or file
               | handles where the expectation is very clear that the
               | object going out of scope will trigger a release action.
        
               | swiftcoder wrote:
               | There's no guessing - Rust has well defined drop order.
               | It also has manual drop, should you wish to override the
               | defined order.
        
               | throwawaymaths wrote:
               | sorry i shouldn't have said guess. i meant consider
        
               | imtringued wrote:
               | Okay, but an IDE could just visualize that for you. A
               | linter rule could force you to manually drop if you want
               | to be explicit.
        
               | ShroudedNight wrote:
               | > a bunch of implementation magic that exists to hide the
               | fact that those supposedly stack-allocated containers are
               | in fact allocating heap objects behind your back
               | 
               | The heap is but one source for allocator-backed memory.
               | I've used pieces of stack for this, too. One could also
               | use an entirely staticly sized and allocated array.
        
           | math_dandy wrote:
           | The list in the above comment isn't a summary -- it's a
           | precise definition. It can and must be carefully explained
           | with lots of examples, contrasts with other languages, etc.,
           | but the precise definition itself must figure prominently,
           | and examples and intuition should relate back to it
           | transparently.
        
           | andrewflnr wrote:
           | Practically, I think it suggests that learning the borrow
           | checker should start with learning how memory works, rather
           | than any concepts specific to Rust.
        
         | raincole wrote:
         | And, after someone who doesn't know rust reads this neat and
         | nice summary, they would still know nothing about rust. (Except
         | "this language's compiler must have some black magic in it.")
        
         | ajross wrote:
         | The second bullet in the second section is overpromising badly.
         | In fact there are _many, many, many_ ways to write verifiably
         | correct code that leaves no dangling pointers yet won 't
         | compile with rustc.
         | 
         | Frankly most of the complexity you're complaining about stems
         | from attempts to specify exactly what magic the borrow checker
         | can prove correct and which incantations it can't.
        
           | zigzag312 wrote:
           | A great teaching technique I learned from a very good match
           | teacher is that when explaining core concepts, the simplified
           | definitions don't need to be completely right. They are much
           | simpler to grasp and adding exceptions to these is also quite
           | easy compared to trying to understand correct, but complex,
           | definitions at the beginning.
        
             | ajross wrote:
             | Yeah, but the whole purpose here is "flattening the
             | learning curve", and telling people code will work when it
             | won't is doing the opposite.
             | 
             | That bullet, at its most charitable, defines the "idealized
             | goal" of the borrow collector. The actual device is much
             | less capable (as it must be, as the goal is formally
             | undecidable!), and "learning rust" requires understanding
             | how.
        
               | AStonesThrow wrote:
               | Ironically, most people understand "learning curves"
               | counterintuitively.
               | 
               | If a "learning curve" is a simple X-Y graph with "time"
               | and "knowledge" being on each axis respectively, then
               | what sort of learning curve is preferable: a flatter one
               | or a steep one?
               | 
               | Clearly, if you graph large increases of knowledge over
               | shorter periods of time, a steeper learning curve is more
               | preferable. "Flattening the learning curve" makes it
               | worse!
               | 
               | But for some reason, people always reverse this meaning,
               | and so the common idiom breaks down for people who try to
               | reason it out.
        
               | zigzag312 wrote:
               | Replace "knowledge" with "required knowledge". It's not
               | about how efficiently you can learn, but how much do you
               | need to learn in a specific amount of time. If you need
               | to learn a lot in short amount of time (which is a hard
               | thing to do) the curve is steep. You can flatten the
               | curve by increasing the time you have available or by
               | requiring less knowledge.
        
               | zahlman wrote:
               | > But for some reason, people always reverse this
               | meaning, and so the common idiom breaks down for people
               | who try to reason it out.
               | 
               | Because one imagines the "curve" like physical topology,
               | with the goal of reaching the top.
        
               | zigzag312 wrote:
               | > ... defines the "idealized goal" of the borrow
               | collector. The actual device is much less capable
               | 
               | I think here you expanded on the original point in a good
               | way. I would then continue with adding additional set of
               | points covering the issue in greater detail and a set of
               | examples of where this commonly happens and how to solve
               | it.
        
               | zahlman wrote:
               | > Yeah, but the whole purpose here is "flattening the
               | learning curve", and telling people code will work when
               | it won't is doing the opposite.
               | 
               | "Flattening the learning curve" is perhaps a wrong
               | metaphor - you can't actually change what needs to be
               | learned; you can only make it easier to learn.
               | 
               | Saying something that is _usually_ right and _can be
               | corrected later_ is a standard pedagogical approach - see
               | https://en.wikipedia.org/wiki/Wittgenstein%27s_ladder .
               | To extend the metaphor, the ladder is there to help you
               | climb the learning curve.
        
               | ajross wrote:
               | It's not "usually right" though. Rust can't compile a
               | doubly-linked list[1] without unsafe!
               | 
               | And people trip over this _immediately_ when they start
               | writing Rust, because that kind of code is pervasive in
               | other environments. Thus statements like  "Rust just
               | doesn't like dangling pointers" are unhelpful, because
               | while it's true it's not sufficient to write anything but
               | the most trivial code.
               | 
               | [1] Or basically any graph-like data structure that can't
               | be trivially proven to be acyclic; even lots of DAG-like
               | graphs that "should" be checkable aren't.
        
               | zahlman wrote:
               | People write non-trivial code _all the time_ without
               | worrying about that sort of thing. Quite a lot can be
               | done with plain tree structures. In the real world, your
               | data is flat and even your conventions for interpreting
               | it as non-flat (such as, say, JSON) only create trees
               | that perhaps simulate back-links with another informal
               | protocol.
        
               | ajross wrote:
               | Sigh. _The whole premise of the linked article_ is, in
               | fact, that people hit validation problems with the borrow
               | checker early on when learning rust and that attention is
               | needed to  "flatten the learning curve" to assist their
               | understanding of what we all agree is a unique and
               | somewhat confusing set of semantics relative to competing
               | languages.
               | 
               | Rust flaming is just so terribly exhausting. No matter
               | how reasonable and obvious a point is there's always
               | someone willing to go to the mattresses in a fourty-
               | comment digression about how Rust is infallible.
        
         | psychoslave wrote:
         | This explanation doesn't expose anything meaningful to my mind,
         | as it doesn't define ownership and borrowing, both words being
         | apparently rooted in an analogy with financial asset
         | management.
         | 
         | I'm not acquainted with Rust, so I don't really know, but I
         | wonder if the wording plays a role in the difficulty of concept
         | acquisition here. Analogies are often double edged tools.
         | 
         | Maybe sticking to a more straight memory related vocabulary as
         | an alternative presentation perspective might help?
        
           | Renaud wrote:
           | I find it strange that you relate borrowing and ownership to
           | financial asset management.
           | 
           | From that angle, it indeed doesn't seem to make sense.
           | 
           | I think, but might be completely wrong, that viewing these
           | actions from their usual meaning is more helpful: you own a
           | toy, it's yours to do as tou please. You borrow a toy, it's
           | not yours, you can't do whatever you want with it, so you
           | can't hold on to it if the owner doesn't allow it, and you
           | can't modify it for the same reasons.
        
             | ithkuil wrote:
             | Analogies often leak.
             | 
             | 1. In real life I can borrow a toy from you and while I
             | have that toy in my hands, the owner can exchange ownership
             | with somebody else, while the object is borrowed by me.
             | I.e. in real life the borrowing is orthogonal to ownership.
             | In rust you can't do that.
             | 
             | 2. Borrowing a toy is more akin to how mutable references
             | work in rust. Immutable references allow multiple people to
             | play with the same toy simultaneously, provided they don't
             | change it.
             | 
             | Analogies are just analogies
        
             | psychoslave wrote:
             | What do you mean with usual sense? Maybe it's "financial"
             | that put the interpretation out of the track, but financial
             | comes fidus, that is trust, as in trust that outcomes of
             | reality will meet some expectation of a mental
             | representation.1
             | 
             | "You own a toy" is the first thing a child is teached as
             | wrong assumption by reality if not by careful social
             | education, isn't it? The reality is, "you can play with the
             | toy in some time frame, and sharing with others is the only
             | way we can all benefit of joyful ludic moment, while claims
             | of indefinite exclusive use of the toy despite limited
             | attention span that an individual can spend on it is
             | socially detrimental."
             | 
             | Also memory as an abstract object pragmatically operate on
             | very different ground than a toy. If we could duplicate any
             | human hand grabbable object as information carried by
             | memory holding object, then any economy would virtually be
             | a waste of human attention.
             | 
             | 1 edit: actually I was wrong here, I have been in confusion
             | with "fiduciary". Finance instead comes from french
             | "fin"(end), as in "end of debt".
        
             | xnickb wrote:
             | Many people can borrow your toy to have look at it, but
             | only one person can borrow it and play with it. And they
             | are only allowed to play while no one is watching. And if
             | you want to modify your toy with some tool it's not your's
             | anymore, it yas moved and now belongs to the tool.
             | 
             | I guess I'm trying to say that analogy is of limited use
             | here.
        
           | throwaway81523 wrote:
           | If you've worked inside of CPython or other programs with
           | manual reference counting, the idea of borrowing shows up
           | there, where you receive a reference from another part of the
           | program and then mess with the object without tweaking the
           | reference count, "borrowing" an existing reference because
           | any copies you've of the address will be short lived. The
           | term shows up throughout CPython.
        
           | arnsholt wrote:
           | The way I think about it is more or less in terms of how a C
           | program would work: if you assume a heap allocated data
           | structure, the owner is the piece of code that is responsible
           | for freeing the allocation at the appropriate time. And a
           | reference is just a pointer with some extra compile time
           | metadata that lets the borrow checker prove that the
           | reference doesn't outlive the referent and that there's no
           | mutable aliasing.
        
           | imtringued wrote:
           | You think borrowing a lawn mower or owning a power drill is
           | financial asset management?
        
             | psychoslave wrote:
             | On the most abstract level, even "I" and "think" are
             | misleading notions of what's passing through current
             | attention. So "borrowing" and "owning" are not really great
             | starting point notions to "think" in that sense. But on the
             | more mundane level of mentally handling stuffs, that's an
             | analogy that can have its own merits (and flaws, of
             | course).
        
         | xiphias2 wrote:
         | I think the most important lesson is this:
         | 
         | Ownership is easy, borrowing is easy, what makes the language
         | super hard to learn is that functions must have signatures and
         | uses that together prove that references don't outlive the
         | object.
         | 
         | Also: it's better not store referenced object in a type unless
         | it's really really needed as it makes the proof much much more
         | complex.
        
           | echelon wrote:
           | > Ownership is easy, borrowing is easy
           | 
           | 100%. It's the programmer that needs to adapt to this style.
           | It's not hard by any means at all, it just takes some
           | adjustment.
        
             | geodel wrote:
             | Indeed. Programmers are holding Rust wrong.
        
               | echelon wrote:
               | Programmers new to Rust, you mean.
               | 
               | It's kind of like career Java programmers using
               | JavaScript or Python for the first time and bringing
               | their way of doing things.
        
               | cmrdporcupine wrote:
               | It's more than that. Rust's value & reference passing
               | semantics are completely different from the way most
               | programmer's have trained their entire lives to think
               | about it.
               | 
               | When you pass an argument to a function in Rust, or
               | assign a value into a struct or variable, etc. you are
               | _moving_ it (unless it 's Copy). That's extremely
               | different from any other programming language people are
               | used to, where things are broadly pass by value pass by
               | reference and you can just do that as much as you want
               | and the compiler doesn't care. It's as if in C++ you were
               | doing std::move for every single argument or assignment.
               | 
               | And so as a programmer you have to shift to a mindset
               | where you're thinking about that happening. This is
               | profoundly unintuitive _at first_ but becomes habit over
               | time.
               | 
               | Then having that habit, it's actually a nice reasoning
               | skill when you go back to working in other languages.
        
         | codeflo wrote:
         | That's not explaining ownership, that motivating it. Which is
         | fine. The thing that's hard to explain and learn is how to read
         | function signatures involving <'a, 'b>(...) -> &'a [&'b str] or
         | whatever. And how to understand and fix the compiler errors in
         | code calling such a function.
        
           | throwaway81523 wrote:
           | Is it a lot different from std::unique_ptr in C++?
           | 
           | I thought the Rust Book was too verbose but I liked
           | Comprehensive Rust: https://google.github.io/comprehensive-
           | rust/
           | 
           | I felt like I understood the stuff in the book based on
           | cursory reading, but I haven't tried to actually use it.
        
             | fastasucan wrote:
             | >Is it a lot different from std::unique_ptr in C++?
             | 
             | Is knowing C++ a pre-requisite?
        
               | casey2 wrote:
               | I would say knowing the useless features of c++ is a pre-
               | requisite for learning rust yes. It's yet another c++
               | replacement designed by a committee of phds.
               | 
               | You would think they would be smart enough to realize
               | that a language taking X hours to learn is a language
               | flaw not a user flaw, but modern education focuses on
               | specialization talents rather than general intelligence.
        
               | PhilipRoman wrote:
               | Right but Rust is supposed to be a systems language. We
               | already have dozens of languages that are easy to learn.
               | The whole reason for having compile time memory
               | management is to satisfy the constraints of a systems
               | language...
               | 
               | I don't think it's much harder than learning C or C++
               | which are the only comparable mainstream languages.
        
               | ModernMech wrote:
               | I think it's actually easier, thanks to Cargo and the
               | Crates ecosystem. Some of the hardest things for students
               | are just building and linking code, especially third
               | party libraries.
               | 
               | I run two intermediate programming courses, one where we
               | teach C++, and another where we teach Rust. In the Rust
               | course, by the first week they are writing code and using
               | 3rd party libraries; whereas in the C course we spend a
               | lot of time dealing with linker errors, include errors,
               | segfaults, etc. The learning curve for C/C++ gets steep
               | very fast. But with Rust it's actually quite flat until
               | you have to get into borrowing, and you can even defer
               | that understanding with clone().
               | 
               | By the end of the semester in the C++ course, students'
               | final project is a file server, they can get there in 14
               | weeks.
               | 
               | In Rust the final project is a server that implements
               | LSP, which also includes an interpreter for a language
               | they design. The submissions for this project are usually
               | much more robust than the submissions for the C++ course,
               | and I would attribute this difference to the designs of
               | the languages.
        
               | repelsteeltje wrote:
               | The goal of _some_ languages might be to be easy to
               | learn. But most  "system" languages focus on helping
               | design good software, where "good" might mean reliable,
               | maintainable or performant.
               | 
               | Writing good software most often is not easy. The
               | learning curve of a particular language usually is only a
               | modest part of what it takes.
        
               | ModernMech wrote:
               | IME teaching students Rust, knowing C++ first actually is
               | a detriment to learning because they have a bunch of C++
               | habits to unlearn. Those students "fight the borrow
               | checker" much more than the blank slate students, because
               | they have some idea about how code "should" be written.
        
               | zahlman wrote:
               | Is this still true if they never learned pre-modern C++
               | and are accustomed to using all the std::foo_ptrs and
               | expecting the rule of 3 (or 5) to be taken care of
               | automatically that way?
        
               | ModernMech wrote:
               | The prereq for my Rust course is Java, but there are
               | three kinds of students who come to me:
               | 
               | 1) those who only know java
               | 
               | 2) those who know java and were taught C++ by me. The way
               | I teach that course, they are very familiar with pre-
               | modern C++ because we also learn C.
               | 
               | 3) those who know java and C++ but they learned it on
               | their own.
               | 
               | It's the last group who has the most trouble. IME the
               | exact issue they struggle with is the idea of shared
               | mutable state. They are accustomed to handing out
               | pointers to mutable state like candy, and they don't
               | worry about race conditions, or all the different kinds
               | of memory errors that can occur and lead to
               | vulnerabilities. They don't write code that is easily
               | refactored, or modular. They have a tendency to put
               | everything into a header or one main.cpp file because
               | they can't really get their head around the linking
               | errors they get.
               | 
               | So when they try to write code this way in Rust, the very
               | first thing they encounter is a borrow error related to
               | their liberal sharing of state, and they can't understand
               | why they can't just write code the way they want because
               | it had been working so well for them before (in their
               | very limited experience).
               | 
               | Pedagogically what I have to do is unteach them all these
               | habits and then rebuild their knowledge from the ground
               | up.
        
               | zahlman wrote:
               | > So when they try to write code this way in Rust, the
               | very first thing they encounter is a borrow error related
               | to their liberal sharing of state, and they can't
               | understand why they can't just write code the way they
               | want because it had been working so well for them before
               | (in their very limited experience).
               | 
               | Ah, well, a shame they didn't see the failing tests for
               | the C++ code first ;)
        
             | steveklabnik wrote:
             | > Is it a lot different from std::unique_ptr in C++?
             | 
             | It's both identical and very different, depending on the
             | level of detail you want to get into. Conceptually, it's
             | identical. Strictly speaking, the implementations differ in
             | a few key ways.
        
           | cmrdporcupine wrote:
           | The good news is that idiomatically written good clean Rust
           | code doesn't need to rely on such borrow signatures very
           | often. That's more when you're leaving the norm and doing
           | something "clever."
           | 
           | I know it throws people off, and the compiler error can be
           | confusing, but actual explicit lifetimes as part of a
           | signature are less common than you'd expect.
           | 
           | To me it's a code smell to see a lot of them.
        
         | BlackFly wrote:
         | That really doesn't explain the model because you have
         | completely left out the distinction between exclusive/shared
         | (or mutable/immutable) borrows. Rust made a large number of
         | choices with respect to how it permits such borrows and those
         | do not follow from this brief outline nor from intuition or
         | common sense. For example, the no aliasing rule is motivated
         | not by intuition or common sense but from a desire to optimize
         | functions.
         | 
         | The most complicated aspect of the borrows comes about from the
         | elision rules which will silently do the wrong thing and will
         | work fantastically until they don't at which point the compiler
         | error is pointing at a function complaining about a lifetime
         | parameter of a parameter with the trait method implying that
         | the parameter has to live too long but the real problem was a
         | lifetime in the underlying struct or a previous broken lifetime
         | bound. Those elision rules are again not-intuitive and don't
         | fall out of your explanation axiomatically. They were decisions
         | that were made to attempt to simplify the life of programmers.
        
         | fastasucan wrote:
         | >the real owner has to be a reference-counted cell.
         | 
         | And what is that? Its easy to fall in the trap of making
         | explanations that is very good (if you already understand).
        
         | widforss wrote:
         | The only way I could understand the borrow-checker was to
         | implement my own version. Then it made sense.
        
         | frankie_t wrote:
         | Maybe it's my learning limitations, but I find it hard to
         | follow explanations like these. I had similar feelings about
         | encapsulation explanations: it would say I can hide information
         | without going into much detail. Why, from whom? How is it
         | hiding if I can _see it on my screen_.
         | 
         | Similarly here, I can't understand for example _who_ is the
         | owner. Is it a stack frame? Why would a stack frame want to
         | move ownership to its callee, when by the nature of LIFO the
         | callee stack will always be destroyed first, so there is no
         | danger in hanging to it until callee returns. Is it for
         | optimization, so that we can get rid of the object sooner?
         | Could owner be something else than a stack frame? Why can
         | mutable reference be only handed out once? If I'm only using a
         | single thread, one function is guaranteed to finish before the
         | other starts, so what is the harm in handing mutable references
         | to both? Just slap my hands when I'm actually using multiple
         | threads.
         | 
         | Of course, there are reasons for all of these things and they
         | probably are not even that hard to understand. Somehow, every
         | time I want to get into Rust I start chasing these things and
         | give up a bit later.
        
           | kibwen wrote:
           | _> Why can mutable reference be only handed out once?_
           | 
           | Here's a single-threaded program which would exhibit dangling
           | pointers if Rust allowed handing out multiple references
           | (mutable or otherwise) to data that's being mutated:
           | let mut v = Vec::new();         v.push(42);
           | // Address of first element: 0x6533c883fb10
           | println!("{:p}", &v[0]);                  // Put something
           | after v on the heap         // so it can't be grown in-place
           | let v2 = v.clone();                  v.push(43);
           | v.push(44);         v.push(45);         // Exceed capacity
           | and trigger reallocation         v.push(46);
           | // New address of first element: 0x6533c883fb50
           | println!("{:p}", &v[0]);
        
             | kazinator wrote:
             | The analogous program in pretty much any modern language
             | under the sun has no problem with this, in spite of
             | multiple references being casually allowed.
             | 
             | To have a safe reference to the cell of a vector, we need a
             | "locative" object for that, which keeps track of _v_ , and
             | the offset 0 into v.
        
               | steveklabnik wrote:
               | That's a different implementation, and one you can do in
               | Rust too.
        
             | Someone wrote:
             | > // Put something after v on the heap
             | 
             | > // so it can't be grown in-place
             | 
             | > let v2 = v.clone();
             | 
             | I doubt rust guarantees that "Put something after v on the
             | heap" behavior.
             | 
             | The whole idea of a heap is that you give up control over
             | where allocations happen in exchange for an easy way to
             | allocate, free and reuse memory.
        
               | steveklabnik wrote:
               | That's correct.
        
           | dwattttt wrote:
           | > Why would a stack frame want to move ownership to its
           | callee, when by the nature of LIFO the callee stack will
           | always be destroyed first, so there is no danger in hanging
           | to it until callee returns.
           | 
           | It definitely takes some getting used to, but there's
           | absolutely times when you could want something to move
           | ownership into a called function, and extending it would be
           | wrong.
           | 
           | An example would be if it represents something you can only
           | do once, e.g. deleting a file. Once you've done it, you don't
           | want to be able to do it again.
        
           | lucozade wrote:
           | > _who_ is the owner. Is it a stack frame?
           | 
           | The owned memory may be on a stack frame or it may be heap
           | memory. It could even be in the memory mapped binary.
           | 
           | > Why would a stack frame want to move ownership to its
           | callee
           | 
           | Because it wants to hand full responsibility to some other
           | part of the program. Let's say you have allocated some memory
           | on the heap and handed a reference to a callee then the
           | callee returned to you. Did they free the memory? Did they
           | hand the reference to another thread? Did they hand the
           | reference to a library where you have no access to the code?
           | Because the answer to those questions will determine if you
           | are safe to continue using the reference you have. Including,
           | but not limited to, whether you are safe to free the memory.
           | 
           | If you hand ownership to the callee, you simply don't care
           | about any of that because you can't use your reference to the
           | object after the callee returns. And the compiler enforces
           | that. Now the callee could, in theory give you back ownership
           | of the same memory but, if it does, you know that it didn't
           | destroy etc that data otherwise it couldn't give it you back.
           | And, again, the compiler is enforcing all that.
           | 
           | > Why can mutable reference be only handed out once?
           | 
           | Let's say you have 2 references to arrays of some type T and
           | you want to copy from one array to the other. Will it do what
           | you expect? It probably will if they are distinct but what if
           | they overlap? _memcpy_ has this issue and  "solves" it by
           | making overlapped copies undefined. With a single mutable
           | reference system, it's not possible to get that scenario
           | because, if there were 2 overlapping references, you couldn't
           | write to either of them. And if you could write to one, then
           | the other has to be a reference (mutable or not) to some
           | other object.
           | 
           | There are also optimisation opportunities if you know 2
           | objects are distinct. That's why C added the _restrict_
           | keyword.
           | 
           | > If I'm only using a single thread
           | 
           | If you're just knocking up small scripts or whatever then a
           | lot of this is overkill. But if you're writing libraries,
           | large applications, multi-dev systems etc then you may be
           | single threaded but who's confirming that for every piece of
           | the system at all times? People are generally really rubbish
           | at that sort of long range thinking. That's where these more
           | automated approaches shine.
           | 
           | > hide information...Why, from whom?
           | 
           | The main reason is that you want to expose a specific
           | contract to the rest of the system. It may be, for example,
           | that you have to maintain invariants eg double entry book-
           | keeping or that the sides of a square are the same length.
           | Alternatively, you may want to specify a high level algorithm
           | eg matrix inversion, but want it to work for lots of
           | varieties of matrix implementation eg sparse, square. In
           | these cases, you want your consumer to be able to use your
           | objects, with a standard interface, without them knowing, or
           | caring, about the detail. In other words you're _hiding_ the
           | implementation detail behind the interface.
        
           | kibwen wrote:
           | _> Why would a stack frame want to move ownership to its
           | callee_
           | 
           | Rust's system of ownership and borrowing effectively lets you
           | hand out "permissions" for data access. The owner gets the
           | maximum permissions, including the ability to hand out
           | references, which grant lesser permissions.
           | 
           | In some cases these permissions are useful for performance,
           | yes. The owner has the permission to eagerly destroy
           | something to instantly free up memory. It also has the
           | permission to "move out" data, which allows you to avoid
           | making unnecessary copies.
           | 
           | But it's useful for other reasons too. For example, threads
           | don't follow a stack discipline; a callee is not guaranteed
           | to terminate before the caller returns, so passing ownership
           | of data sent to another thread is important for correctness.
           | 
           | And naturally, the ability to pass ownership to higher stack
           | frames (from callee to caller) is also necessary for
           | correctness.
           | 
           | In practice, people write functions that need the least
           | permissions necessary. It's overwhelmingly common for callees
           | to take references rather than taking ownership, because what
           | they're doing just doesn't require ownership.
        
           | Hackbraten wrote:
           | I think your comment has received excellent replies. However,
           | no one has tackled your actual question so far:
           | 
           | > _who_ is the owner. Is it a stack frame?
           | 
           | I don't think that it's helpful to call a stack frame the
           | owner in the sense of the borrow checker. If the owner was
           | the stack frame, then why would it have to borrow objects to
           | itself? The fact that the following code doesn't compile
           | seems to support that:                   fn main() {
           | let a: String = "Hello".to_owned();             let b = a;
           | println!("{}", a);  // error[E0382]: borrow of moved value:
           | `a`         }
           | 
           | User lucozade's comment has pointed out that the _memory_
           | where the object lives is actually the _thing that is being
           | owned_. So that can't be the owner either.
           | 
           | So if neither a) the stack frame nor b) the memory where the
           | object lives can be called the owner in the Rust sense, then
           | what is?
           | 
           | Could the owner be the _variable_ to which the owned chunk of
           | memory is bound at a given point in time? In my mental model,
           | yes. That would be consistent with all borrow checker
           | semantics as I have understood them so far.
           | 
           | Feel free to correct me if I'm not making sense.
        
             | adastra22 wrote:
             | I believe this answer is correct. Ownership exists at the
             | language level, not the machine level. Thinking of a part
             | of the stack or a piece of memory as owning something isn't
             | correct. A language entity, like a variable, is what owns
             | another object in rust. When that object goes at a scope,
             | its resources are released, including all the things it
             | owns.
        
           | kazinator wrote:
           | > Why would a stack frame want to move ownership to its
           | callee
           | 
           | Happens all the time in modern programming:
           | 
           | callee(foo_string + "abc")
           | 
           | Argument expression foo_string + "abc" constructs a new
           | string. That is not captured in any variable here; it is
           | passed to the caller. Only the caller knows about this.
           | 
           | This situation can expose bugs in a run-time's GC system. If
           | callee is something written in a low level language that is
           | resposible for indicating "nailed" objects to the garbage
           | collector, and it forgets to nail the argument object, GC can
           | prematurely collect it because nothing else in the image
           | knows about that object: only the callee. The bug won't
           | surface in situations like callee(foo_string) where the
           | caller still has a reference to foo_string (at least if that
           | variable is live: has a next use).
        
         | kaycey2022 wrote:
         | I think the Brown University's modifications to the rust book
         | do an excellent job of explaining the borrow checker.
        
         | amelius wrote:
         | Seems incomplete. E.g. what happens if a borrower goes away?
        
           | andromeduck wrote:
           | It stops being borrowed?! What kind of question is this.
        
             | amelius wrote:
             | A question about definitions. Some other options would be:
             | - the object is destroyed         - the program core dumps
             | - it is a compile time error
             | 
             | Assuming the best possible outcome in case of missing
             | information turns out to be a bad strategy in general.
        
         | agumonkey wrote:
         | I often wanted to find writings about the 60s on how they
         | approached system/application state at assembly level. I know
         | Sutherland Sketchpad thesis has a lot of details about data
         | structures but I never read it (except for 2-3 pages).
        
         | littlestymaar wrote:
         | I like how you phrase it, but it's missing the mutable XOR
         | shared for references.
        
       | Waterluvian wrote:
       | Write 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/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 wrote:
         | I'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 wrote:
           | These 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 wrote:
             | I'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 wrote:
       | Rust 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 wrote:
         | Got recommended learning paths? I tend to prefer follow along
         | adventures via video.
        
           | maxbond wrote:
           | Check out Jon Gjengset.
           | 
           | https://www.youtube.com/@jonhoo
        
             | Measter wrote:
             | I 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 wrote:
               | Gjengset 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 wrote:
         | "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.
        
           | kupopuffs wrote:
           | Wow who pissed in your coffee? he likes rust ok?
        
             | codr7 wrote:
             | And he's telling other people they should like it as well,
             | because he has seen the light.
             | 
             | My gut feeling says that there's a fair bit of Stockholm
             | Syndrome involved in the attachments people form with Rust.
             | 
             | You could see similar behavioral issues with C++ back in
             | the days, but Rust takes it to another level.
        
               | awesome_dude wrote:
               | > You could see similar behavioural issues with C++ back
               | in the days
               | 
               | I think that it's happened to some degree for almost
               | every computer programming language for a whiles now -
               | first was the C guys enamoured with their NOT
               | Pascal/Fortran/ASM, then came the C++ guys, then Java,
               | Perl, PHP, Python, Ruby, Javascript/Node, Go, and now
               | Rust.
               | 
               | The vibe coding people seem to be the ones that are
               | usurping Rust's fan boi noise at the moment - every other
               | blog is telling people how great the tool is, or how
               | terrible it is.
        
               | galangalalgol wrote:
               | I think most of us enamoured with rust are c++ refugees
               | glad the pain is lessened. The tooling including the
               | compiler errors really are great though. I like the
               | simplicity of c, but I would still pick rust for any new
               | project just for the crates and knowing I'll never have
               | to debug a segfault. I like pytorch and matlab fine for
               | prototyping. Not much use for in-between languages like
               | go or c# but I like the ergonomics of them just fine. I
               | don't think it is at all weird for people coming from c++
               | or even c to like rust and prefer it over those other
               | languages. We have already paid the cost of admission,
               | and it comes with real benefits.
        
               | ModernMech wrote:
               | Yes! 100% this!
               | 
               | For me, programming with C++ was like building castles
               | out of sand. I could never make them tall enough before
               | they would collapse under their own weight.
               | 
               | But with Rust, I leveled up my abilities and built a
               | program larger than I ever thought possible. And for that
               | I'm _thankful_ to Rust for being a language that actually
               | makes sense to me.
        
           | cadamsdotcom wrote:
           | @gerdesj your tone was unnecessarily rude and mean. Part of
           | your message makes a valid point but it is hampered by
           | unnecessary insults. I hope the rest of your day improves
           | from here.
           | 
           | I don't specifically like Rust itself. And one doesn't need a
           | programming language to discover themselves.
           | 
           | My experience learning Rust has been that it imposes enough
           | constraints to teach me important lessons about correctness.
           | Lots of people can learn more about correctness!
           | 
           | I'll concede- "everyone" was too strong; I erred on the side
           | of overly provocative.
        
             | prmph wrote:
             | It does not teach you any fundamental lessons about
             | correctness. It teaches you lessons about correctness
             | within the framework Rust imposes; that's all
        
         | namuol wrote:
         | > Everyone should learn Rust.
         | 
         | I know this feels like a positive vibe post and I don't want to
         | yuck anyone's yum, but speaking for myself when someone tells
         | me "everyone should" do anything, alarm bells sound off in my
         | mind, especially when it comes to programming languages.
        
           | vacuity wrote:
           | I think everyone should learn many different programming
           | languages, because being exposed to different paradigms helps
           | develop programming skill.
        
             | namuol wrote:
             | Yeah I agree, I enjoy the process. I don't think that's
             | what's behind "everyone should learn rust" in this case,
             | and many cases. It feels like a "cause".
        
         | pjmlp wrote:
         | The compilers maybe not, but static analysers already go a long
         | way, it is a pity that it is still a quixotic battle to make
         | developers adopt them, even if it isn't 100% all the way there.
         | 
         | If it isn't the always hated SecDevOps group of people pushing
         | for the security tooling developers don't care about, at very
         | least on build pipelines, they would keep collecting digital
         | dust.
        
       | sesm wrote:
       | Is 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 wrote:
         | I 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/...
        
           | sesm wrote:
           | > 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 wrote:
             | >> 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 wrote:
             | I 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.
        
             | echelon wrote:
             | Rust is RAII at the compiler level. It's the language spec
             | itself. That's probably the best way to describe the design
             | and intent of Rust's memory model.
             | 
             | When you create a thing, you allocate it. That thing owns
             | it and destroys it, unless you pass that ownership onto
             | something else (which C++ RAII doesn't do very cleanly like
             | Rust can).
             | 
             | Then it does some other nice things to reduce every sharp
             | edge it can:
             | 
             | - No nulls, no exceptions. Really good Option<T> and
             | Result<T,E> that make everything explicit and ensure it
             | gets handled. Wonderful syntactic sugar to make it easy. If
             | you ever wondered if your function should return an error
             | code, set an error reference, throw an exception - that's
             | never a design consideration anymore. Rust has the very
             | best solution in the business. And it does it with rock
             | solid safety.
             | 
             | - Checks how you pass memory between threads with a couple
             | of traits (Send, Sync). If your types don't implement those
             | (usually with atomics and locks), then your code won't pass
             | the complier checks. So multithreaded code becomes provably
             | safe at compile time to a large degree. It won't stop you
             | from deadlocking if you do something silly, but it'll solve
             | 99% of the problems.
             | 
             | - Traits are nicer than classes. You can still accomplish
             | everything you can with classic classes, but you can also
             | do more composition-based inheritance that classes don't
             | give you by bolting traits onto anything you want.
             | 
             | - Rust's standard library (which you don't have to use if
             | you're doing embedded work) has some of the nicest data
             | structures, algorithms, OS primitives, I/O, filesystem,
             | etc. of any language. It's had 40 years of mistakes to
             | learn from and has some really great stuff in it. It's all
             | wonderfully cross-platform too. I frequently write code for
             | Windows, Mac, and Linux and it all just works out of the
             | box. Porting is never an issue.
             | 
             | - Rust's functional programming idioms are super concise
             | and easy to read. The syntax isn't terse.
             | 
             | - Cargo is the best package manager on the planet right
             | now. You can easily import a whole host of library
             | functionality, and the management of those libraries and
             | their features is a breeze. It takes all of sixty seconds
             | to find something you want and bring it into your codebase.
             | 
             | - You almost never need to think about system libraries and
             | linking. No Makefiles, no Cmake, none of that build
             | complexity or garbage. The compiler and cargo do all of the
             | lifting for you. It's as easy as python. You never have to
             | think about it.
        
         | NobodyNada wrote:
         | This might not be exactly what you're looking for, but I really
         | like "References are like jumps":
         | https://without.boats/blog/references-are-like-jumps/
        
         | jandrewrogers wrote:
         | Rust has better defaults for types than C++, largely because
         | the C++ defaults came from C. Rust is more ergonomic in this
         | regard. If you designed C++ today, it would likely adopt many
         | of these defaults.
         | 
         | However, for high-performance systems software specifically,
         | objects often have _intrinsically_ ambiguous ownership and
         | lifetimes that are only resolvable at runtime. Rust has a
         | pretty rigid view of such things. In these cases C++ is much
         | more ergonomic because objects with these properties are
         | essentially outside the Rust model.
         | 
         | In my own mental model, Rust is what Java maybe should have
         | been. It makes too many compromises for low-level systems code
         | such that it has poor ergonomics for that use case.
        
           | Ar-Curunir wrote:
           | > However, for high-performance systems software
           | specifically, objects often have intrinsically ambiguous
           | ownership
           | 
           | What is the evidence for this? Plenty of high-performance
           | systems software (browsers, kernels, web servers, you name
           | it) has been written in Rust. Also Rust does support runtime
           | borrow-checking with Rc<RefCell<_>>. It's just less ergonomic
           | than references, but it works just fine.
        
             | jandrewrogers wrote:
             | Anyone that works on e.g. database kernels that do direct
             | DMA (i.e. all the high-performance ones) experiences this.
             | The silicon doesn't care about your programming language's
             | ownership model and will violate it at will. You can't fix
             | it in the language, you have to accept the behavior of the
             | silicon. Lifetimes are intrinsically ambiguous because
             | objects have neither a consistent nor persistent memory
             | address, a pretty standard property in databases, and a
             | mandatory property of large databases. Yes, you can kind of
             | work around it in idiomatic Rust but performance will not
             | be anything like comparable if you do. You have to embrace
             | the nature of the thing.
             | 
             | The near impossibility of building a competitive high-
             | performance I/O scheduler in safe Rust is almost a trope at
             | this point in serious performance-engineering circles.
             | 
             | To be clear, C++ is not exactly comfortable with this
             | either but it acknowledges that these cases exist and
             | provides tools to manage it. Rust, not so much.
        
               | lenkite wrote:
               | New DB's like Tigerbeetle are written in Zig. Memory
               | control was one of the prime reasons. Rust's custom
               | allocators for the standard library have been a WIP for a
               | decade now.
        
               | Ar-Curunir wrote:
               | You can always fall back to unsafe. Again, there are very
               | few workloads that C/C++ can support which Rust cannot.
        
           | Const-me wrote:
           | Interestingly, CPU-bound high-performance systems are also
           | incompatible with Rust's model. Ownership for them is
           | unambiguous, but Rust has another issue, doesn't support
           | multiple writeable references of the same memory accessed by
           | multiple CPU cores in parallel.
           | 
           | A trivial example is multiplication of large square matrices.
           | An implementation needs to leverage all available CPU cores,
           | and a traditional way to do that you'll find in many BLAS
           | libraries - compute different tiles of the output matrix on
           | different CPU cores. A tile is not a continuous slice of
           | memory, it's a rectangular segment of a dense 2D array.
           | Storing different tiles of the same matrix in parallel is
           | trivial in C++, very hard in Rust.
        
             | winrid wrote:
             | Hard in _safe_ rust. you can just use unsafe in that one
             | area and still benefit in most of your application from
             | safe rust.
        
               | Const-me wrote:
               | I don't use C++ for most of my applications. I only use
               | C++ to build DLLs which implement CPU-bound performance
               | sensitive numeric stuff, and sometimes to consume C++
               | APIs and third-party libraries.
               | 
               | Most of my applications are written in C#.
               | 
               | C# provides memory safety guarantees very comparable to
               | Rust, other safety guarantees are better (an example is
               | compiler option to convert integer overflows into runtime
               | exceptions), is a higher level language, great and
               | feature-rich standard library, even large projects
               | compile in a few seconds, usable async IO, good quality
               | GUI frameworks... Replacing C# with Rust would not be a
               | benefit.
        
               | dwattttt wrote:
               | It does sound like quite a similar model; unsafe Rust in
               | self contained regions, safe in the majority of areas.
               | 
               | FWIW in the case where you're not separating code via a
               | dynamic library boundary, you give the compiler an
               | opportunity to optimise across those unsafe usages, e.g.
               | inlining opportunities for the unsafe code into callers.
        
               | Const-me wrote:
               | > quite a similar model
               | 
               | Yeah, and that model is rather old:
               | https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule In
               | practice, complex software systems have been written in
               | multiple languages for decades. The requirements of
               | performance-critical low-level components and high-level
               | logic are too different and they are in conflict.
               | 
               | > you give the compiler an opportunity to optimise across
               | those unsafe usages
               | 
               | One workaround is better design of the DLL API. Instead
               | of implementing performance-critical outer layers in C#,
               | do so on the C++ side of the interop, possibly injecting
               | C# dependencies via function pointers or an abstract
               | interface.
               | 
               | Another option is to re-implement these smaller functions
               | in C#. Modern .NET runtime is not terribly slow; it even
               | supports SIMD intrinsics. You are unlikely to match the
               | performance of an optimised C++ release build with LTO,
               | but it's unlikely to fall significantly short.
        
               | neonsunset wrote:
               | > LTO
               | 
               | On _some_ workloads (think calls not possible to inline
               | within a hot loop), I found LTO to be a requirement for C
               | code to _match_ C# performance, not the other way around.
               | We 've come a long way!
               | 
               | (if you ask if there are any caveats - yes, JIT is able
               | to win additional perf. points by not being constrained
               | with SSE2/4.2 and by shipping more heavily vectorized
               | primitives OOB which allow doing single-line changes that
               | outpace what the average C library has access to)
        
               | Const-me wrote:
               | > on some workloads, I found LTO to be a requirement for
               | C code to match C# performance
               | 
               | Yeah, I observed that too. As far as I remember, that
               | code did many small memory allocations, and .NET GC was
               | faster than malloc.
               | 
               | However, last time I tested (used .NET 6 back then), for
               | code which churches numbers with AVX, my C++ with SIMD
               | intrinsics was faster than C# with SIMD intrinsics. Not
               | by much but noticeable, like 20%. The code generator was
               | just better in C++. I suspect the main reason is .NET JIT
               | compiler doesn't have time for expensive optimisations.
        
               | neonsunset wrote:
               | > The code generator was just better in C++. I suspect
               | the main reason is .NET JIT compiler doesn't have time
               | for expensive optimisations.
               | 
               | Yeah, there are heavy constraints on how many phases
               | there are and how much work each phase can do. Besides
               | inlining budget, there are many hidden "limits" within
               | the compiler which reduce the risk of throughput loss.
               | 
               | For example - JIT will only be able to track so many
               | assertions about local variables at the same time, and if
               | the method has too many blocks, it may not perfectly
               | track them across the full span of them.
               | 
               | GCC and LLVM are able to leisurely repeat optimization
               | phases where-as RyuJIT avoids it (even if some phases
               | replicate some optimizations happened earlier). This will
               | change once "Opt Repeat" feature gets productized[0], we
               | will most likely see it in NativeAOT first, as you'd
               | expect.
               | 
               | On matching codegen quality produced by GCC for
               | vectorized code - I'm usually able to replicate it by
               | iteratively refactoring the implementation and quickly
               | testing its disasm with Disasmo extension. The main catch
               | with this type of code is that GCC, LLVM and ILC/RyuJIT
               | each have their own quirks around SIMD (e.g. does the
               | compiler mistakenly rematerialize vector constant
               | construction inside the loop body, undoing you hosting
               | its load?). Previously, I thought it was a weakness
               | unique to .NET but then I learned that GCC and LLVM tend
               | to also be vulnerable to that, and even regress across
               | updates as it sometimes happens in SIMD edge cases in
               | .NET. But it is certainly not as common. What GCC/LLVM
               | are better at is if you start abstracting away your SIMD
               | code in which case it may need more help as once you
               | start exhausting available registers due to sometimes
               | less than optimal register allocation you start getting
               | spills or you may be running in a technically correct
               | behavior around vector shuffles where JIT needs to
               | replicate _portable_ behavior but fails to see your
               | constant does not need it so you need to reach out for
               | platform-specific intrinsics to work around it.
               | 
               | [0]: https://github.com/dotnet/runtime/issues/108902
        
               | winrid wrote:
               | I would definitely rather use C# or java in a GUI app,
               | yes.
        
             | arnsholt wrote:
             | That's the tyranny of Godel incompleteness (or maybe Rice's
             | theorem, or even both): useful formal systems can be either
             | sound or complete. Rust makes the choice of being sound,
             | with the price of course being that some valid operations
             | not being expressible in the language. C of course works
             | the other way around; all valid programs can be expressed,
             | but there's no (general) way to distinguish invalid
             | programs from valid programs.
             | 
             | For your concrete example of subdividing matrixes, that
             | seems like it should be fairly straightforward in Rust too,
             | if you convert your mutable reference to the data into a
             | pointer, wrap your pointer arithmetic shenanigans in an
             | unsafe block and add a comment at the top saying more or
             | less "this is safe because the different subprograms are
             | always operating on disjoint subsets of the data, and
             | therefore no mutable aliasing can occur"?
        
           | pjmlp wrote:
           | Java should have been like Modula-3, Eiffel, Active Oberon,
           | unfortunately it did not and has been catching up to rethink
           | its design while preserving its ABI.
           | 
           | Thankfully C# has mostly catched up with those languages, as
           | the other language I enjoy using.
           | 
           | After that, is the usual human factor on programming
           | languages adoption.
        
         | scotty79 wrote:
         | In-place mutability and stack allocation are for speed. This
         | made them have variables and values inside them both as
         | separate first-class citizens. The entire borrow checker is
         | just for tracking variables access so that you don't need to
         | clone values. I'd say that given this one guiding decision
         | there are very little arbitrary ones in Rust. The rest of it
         | pretty much had to be made exactly the way it is. Rust is more
         | of a discovery than a construct.
         | 
         | C++ is just Rust without any attempt at tracking variable
         | access and cloning which leads to a mess because people are too
         | terrible at that to do that manually and ad-hoc. So Rust fixes
         | that.
        
         | steveklabnik wrote:
         | I'm not aware of one, but I'm happy to answer questions.
         | 
         | > in-place mutability
         | 
         | I'm not sure what this means as.
         | 
         | > why encourage stack allocation
         | 
         | This is the same as C++, things are stack allocated by default
         | and only put on the heap if you request it. Control is
         | imporrant
         | 
         | > what problems with C++ does it solve and at what cost
         | 
         | The big one here is memory safety by default. You cannot have
         | dangling pointers, iterator invalidation, and the like. The
         | cost is that this is done via compile time checks, and you have
         | to learn how to structure code in a way that demonstrates to
         | the compiler that these properties are correct. That takes some
         | time, and is the difficulty people talk about.
         | 
         | Rust also flips a lot of defaults that makes the language
         | simpler. For example, in C++ terms, everything is trivially
         | relocatable, which means Rust can move by default, and decided
         | to eliminate move constructors. Technically Rust has no
         | constructors at all, meaning there's no rule of 3 or 5. The
         | feeling of Rust code ends up being different than C++ code, as
         | it's sort of like "what if Modern C++ but with even more
         | functional influence and barely any OOP."
        
       | CobrastanJorji wrote:
       | Regarding the first example, the longest() function, why couldn't
       | the compiler figure it out itself? What is the design flaw?
        
         | mplanchard wrote:
         | You'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 wrote:
           | > 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 wrote:
             | > 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 wrote:
             | Oh 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.
        
         | raincole wrote:
         | It's a design choice.
         | 
         | To make a compiler automatically handle all of the cases like
         | that, you will need to do an extensive static analysis, which
         | would make compiling take forever.
        
           | j16sdiz wrote:
           | Would be nice if an IDE can autofix it.
           | 
           | Maybe autofix as we type, or autofix when it save the
           | document / advance to next line.
        
         | ordu wrote:
         | Compiler can figure that out, but the thing is compiler needs
         | also to understand lifetimes at the site where this function is
         | called. In general case compiler will not look into the code of
         | a called function to see what it does, compiler relies on a
         | function declaration.
         | 
         | That `longest` if defined without explicit lifetimes treated
         | like a lifetime of a return value is the same as of the first
         | argument. It is a rule "lifetime elision", which allows to not
         | write lifetimes explicitly in most cases.
         | 
         | But `longest` can return a second reference also. With added
         | lifetimes the header of the function says exactly that: the
         | lifetime of a return value is a minimum of lifetimes of
         | arguments. Not the lifetime of the first one.
        
       | mdwhatcott wrote:
       | If a language needs an article like this, absolutely begging
       | people to bite the bullet to learn it, maybe that's a language
       | design smell.
       | 
       | Disclaimer: I haven't taken the time to learn Rust so maybe don't
       | take this too seriously..
        
         | mplanchard wrote:
         | I 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 wrote:
         | I 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 wrote:
           | There 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_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 wrote:
             | It's just a programming language, not a religion.
        
               | psychoslave wrote:
               | Well, 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.
        
               | conorjh wrote:
               | the dogmatic culture would probably be my first
               | suggestion. i always ask why are there any CVEs for rust
               | if its "memory-safe" but never get an answer suprisingly
        
               | psychoslave wrote:
               | CVE is not only for memory leak though, while eliminating
               | (or even drastically reducing) such a class of issue is a
               | fair point to advertise, it should not be confused as a
               | magic safety facility that makes go away any security
               | concern.
        
               | steveklabnik wrote:
               | > i always ask why are there any CVEs for rust if its
               | "memory-safe" but never get an answer suprisingly
               | 
               | The answer is straightforward: bugs exist. Even in
               | formally proven software, mistakes can be made. Nothing
               | is perfect.
               | 
               | Additionally, memory safety is a property that when
               | people talk about it, they mean _by default_. All
               | languages contain some amount of non-proven unsafe code
               | in their implementation, or via features like FFI. Issues
               | can arise when these two worlds interact. Yet, real-world
               | usage shows that these cases are quite few compared to
               | languages without these defaults. The exceptions are also
               | a source of the CVEs you're talking about.
        
           | rat87 wrote:
           | its not design by committee its design by Pull request It
           | doesn't have a central
           | https://en.wikipedia.org/wiki/Benevolent_dictator_for_life
           | like python used to so people suggest and implement features
           | as a group, with code counting for a lot (although
           | theoretical issues with safety/design also matter) as opposed
           | to companies arguing for their pet features endlessly without
           | much difference. Look at how long it takes C++ to get any new
           | features.
        
             | rafram wrote:
             | > Look at how long it takes C++ to get any new features.
             | 
             | I'm not sure "it doesn't have enough features" has ever
             | been anyone's complaint about C++.
        
               | rat87 wrote:
               | There are definitely some c++ features that some people
               | have been clamoring for for over a decade
               | 
               | Pattern matching for one(although to be fair that's been
               | in rust from the start)
        
           | yodsanklai wrote:
           | > It's a very complex
           | 
           | I find it relatively simple. Much simpler than C++
           | (obviously). For someone who can write C++ and has some
           | experience wth OCaml/Haskell/F#, it's not a hard language.
        
             | namuol wrote:
             | Sure, C++ has a more complex spec, nobody can argue against
             | that.
             | 
             | Complex is the wrong word. Baffling is a better word. Or
             | counterintuitive, or cumbersome. If "easy enough for
             | someone with experience in C++, OCaml, Haskell, and F#"
             | were the same thing as "not hard" then I don't think this
             | debate would come up so frequently.
        
               | estebank wrote:
               | What you call "baffling", I call "different". Being
               | different doesn't mean it's "complex" or even "hard" (in
               | isolation), but it can be baffling, in the same way that
               | driving on the other side of the road for the first time
               | can feel baffling (but doesn't mean it's "wrong").
        
               | yodsanklai wrote:
               | Of course, this is very subjective. For someone who only
               | knows python or javascript at a superficial level, Rust
               | may seem out of reach. But if you're ok with the most
               | common programming paradigms, I don't find Rust baffling.
               | 
               | I mean, you can't expect to learn a new language in a few
               | days, it'll always take a bit of work. My feeling is that
               | people complaining of the language being hard aren't
               | putting the effort.
               | 
               | My experience is that Rust is a relatively small language
               | which doesn't introduce a lot of new concepts. The syntax
               | is quite intuitive, and the compiler super helpful. The
               | borrower checker was the only new thing for me. I'm not
               | an expert at all, but my experience is that after
               | spending 2 weeks full-time reading books and
               | experimenting, I was able to work professionally with the
               | language without feeling too much friction.
               | 
               | On the other hand, after spending much more time on C++,
               | I don't feel really comfortable with the language.
        
               | icedchai wrote:
               | C++ is a huge and complex language. I worked in it, on
               | and off, from 2002 through 2014 or so and never really
               | felt comfortable, either. Everyone seems to use their own
               | dialect.
               | 
               | (I'm working on learning Rust on my free time.)
        
               | ModernMech wrote:
               | My feeling is that Java / C++ / Python / Javascript are
               | all really the same language when you get down to it.
               | Rust borrows from OCaml more than an other popular
               | imperative languages (the first implementatoin by Graydon
               | Hoare was _in_ OCaml, so the inspirations abound), and
               | therefore it really is quite different to a lot of devs
               | who have never seen the functional paradigm. Good rust is
               | written as a mix of imperative with a strong functional
               | flavoring. Bad Rust is when you try to do everything by
               | mutating arrays over iteration as you would do in
               | imperative languages.
               | 
               | For me, I almost never write "for loops" and "if
               | statements" in Rust; instead I use "functional iterators"
               | and "match expressions", which interface with the borrow
               | checker more nicely.
               | 
               | For example, iterating over an array while modifying it
               | is a common pattern in imperative languages that compiles
               | fine but often causes hard to reason about logic errors
               | during runtime. In Rust, such a thing would cause compile
               | time errors. So instead you rewrite to be more
               | functional, it compiles, and then the magic is it _just
               | works_ , which is a common property of functional
               | languages like Haskell.
               | 
               | IMO a lot of the consternation about the cost of the
               | learning curve is because developers haven't realized
               | once you get past it, the benefit is your code more often
               | is just correct and therefore you run into fewer runtime
               | bugs. In other languages you get the code compiling
               | faster, but then you spend a great deal of time debugging
               | things at runtime which you didn't anticipate at compile
               | time.
        
         | rvz wrote:
         | Maybe Rust is so complex, it is even more complex for an LLM to
         | generate correct code (one-shot) without hallucinating non-
         | existent functions.
         | 
         | Would rather have that than all the issues that JavaScript or
         | any other weakly typed and dynamically typed language.
        
           | namuol wrote:
           | There _are_ more than two programming languages, though. I
           | feel like most of the debates about Rust devolve into the
           | same false choice between safety and ease.
           | 
           | Before Rust I was hearing the same argument from Haskell or
           | Scala developers trying to justify their language of choice.
           | 
           | I know Rust is here to stay, but I think it's mostly because
           | it has a viable ecosystem and quality developer tools. Its
           | popularity is _in spite of_ many of its language features
           | that trade that extra 1% of safety for 90% extra learning
           | curve.
        
             | NitpickLawyer wrote:
             | > features that trade that extra 1% of safety for 90% extra
             | learning curve.
             | 
             | I remember both MS and goog having talks about real-world
             | safety issues in the range of 50% of cases were caused by
             | things that safe rust doesn't allow (use after free,
             | dangling pointers, double free, etc). The fact that even
             | goog uses it, while also developing go (another great
             | language with great practical applications) is telling imo.
        
         | remram wrote:
         | I don't know how to read your comment other than "nothing hard
         | is worth doing". Some things have benefits and drawbacks, is
         | the existence of drawbacks always a non-starter for you?
         | 
         | I'm trying to phrase this as delicately as I can but I am
         | really puzzled.
         | 
         | If someone wrote an article about how playing the harp is
         | difficult, just stick with it... would you also say that
         | playing the harp is a terrible hobby?
        
           | etbebl wrote:
           | Maybe people need persuading to learn Rust not just because
           | they think it's hard, but also because they think it's bad?
           | Not everything hard is worth doing. Difficulty is just one of
           | the factors to consider.
           | 
           | I started to learn Rust, but I was put off by the heavy
           | restrictions the language imposes and the attitude that this
           | is the only safe way. There's a lack of acknowledgement, at
           | least in beginner materials, that by choosing to write safe
           | Rust you're sacrificing many perfectly good patterns that the
           | compiler can't understand in exchange for safety. Eventually
           | I decided to stop because I didn't like that tradeoff (and I
           | didn't need it for my job or anything)
        
             | whyever wrote:
             | > by choosing to write safe Rust you're sacrificing many
             | perfectly good patterns that the compiler can't understand
             | in exchange for safety
             | 
             | Historically, programmers drastically overestimate their
             | ability to write perfectly safe code, so it's an enormous
             | benefit if the compiler is able to understand whether it's
             | actually safe.
        
               | Ragnarork wrote:
               | The first part of your statement feels true, although
               | that's... unverified and lacks actual backing up.
               | 
               | The second part of your statement is very debatable based
               | on what safe means in this case, and whether it's an
               | enormous benefit for a given situation.
               | 
               | There's plenty of stories [0][1] about Rust getting in
               | the way and being very innappropriate for certain tasks
               | and goals, and those "enormous benefits" can become
               | "enormous roadblocks" in different perspectives and use
               | cases.
               | 
               | In my personal and very subjective opinion I think Rust
               | can be very good when applied to security applications,
               | realtime with critical safety requirements (in some
               | embedded scenarios for example), that sort of stuff. I
               | think it really gets in the way too much in other
               | scenarios with demanding rules and pattern that prevent
               | from experimenting easily and exploring solutions
               | quickly.
               | 
               | [0]https://barretts.club/posts/rust-for-the-engine/
               | [1]https://loglog.games/blog/leaving-rust-gamedev/
        
             | atoav wrote:
             | The thing is, _if you want to learn Rust_ this site
             | contains good advice on how to do it. I know, because I
             | learned Rust.
             | 
             | Rust isn't a language you should pick up if you're not
             | ready to put in the work. Just like you shouldn't go for
             | full blown automotive grade C coding if you just want to
             | learn coding quickly to get a job or something.
             | 
             | Rust has a steep learning curve, but the harder part (as
             | mentioned in the article) is to unlearn patterns from other
             | programming languages if you think you're already a good
             | programmer.
        
               | prmph wrote:
               | I think one can understand Rust and still dislike it? Not
               | every criticism of Rust comes from thinking it is too
               | hard. I appreciate it for what it is and the problems it
               | tries to solve. I just don't like many aspects of design
               | of the language, seeing it as unnecessarily ugly for
               | achieving it's aims.
        
               | pessimizer wrote:
               | I think the person you replied to never said "only people
               | who do not understand Rust dislike it," or anything
               | similar to that.
               | 
               | Even pretending that they did, I don't know if
               | "appreciat[ing]" Rust means that you're saying that you
               | "understand" it. It seems like choosing a different word
               | in the second sentence of a two sentence argument may be
               | an subtle way of hinting that you _don 't_ know Rust,
               | although you've read articles about Rust and made
               | judgements about it. If this is true, then it doesn't
               | strongly support the first statement.
        
               | prmph wrote:
               | I'm not sure you fully grasp what I'm saying.
               | 
               | I'm seeking to draw a distinction between disliking rust
               | for the real (or perceived) difficulty of learning/using
               | it, and disliking it on principle, because you don't like
               | it's trade-offs, approach to achieving it aims, syntax,
               | type system, etc. This dichotomy is meaningful
               | irrespective of the level of experience one has with
               | Rust, beyond a certain level (and for the record I
               | believe I have the requisite level of knowledge of rust
               | to have an informed opinion on it).
               | 
               | For example, I don't know much Haskell. It seems to me
               | (and to many other I read online) like it would be
               | difficult to learn (and maybe use), although I'm familiar
               | with functional languages in general. However, based on
               | the little I've learned about it so far, it is a language
               | I'd absolutely love to dig much deeper into as time
               | permits, because almost everything about it makes so much
               | sense to me.
               | 
               | Here's something amazing, I started to design my ideal
               | language, before I started learning Haskell, and almost
               | every language construct in Haskell I learn about seems
               | to match exactly how I'd designed my language by
               | coincidence (even down to keywords like "where", "do"
               | blocks, etc.)
        
           | casey2 wrote:
           | Let me help you
           | 
           | If it takes the average person 1 million hours to learn rust
           | then the average person won't learn rust
           | 
           | If it takes the average person 1 hour to learn rust then the
           | average person will learn rust.
           | 
           | If you were designing a language which would you pick all
           | else being equal?
           | 
           | To your question, no but I wouldn't be puzzled when most
           | people pick up a guitar. (Both are so much more intuitive
           | than any programming language so the metaphor sets false
           | expectations. Slick political move, but probably just turns
           | more people off of Rust)
        
             | dwattttt wrote:
             | > If you were designing a language which would you pick all
             | else being equal?
             | 
             | But why would you think all else is equal? You might not
             | agree with the tradeoffs Rust makes, and it's not as if
             | there's a perfect language for all uses, but it absolutely
             | makes hard software easier to write.
             | 
             | I've had the opportunity to debug weird crazy memory
             | corruption, as well as "wow it's hard to figure out how to
             | design this in Rust", and having come to terms with things
             | much like this blog post I now get more work done, with
             | less corruption _and_ design problems.
        
         | cheikhcheikh wrote:
         | > maybe that's a language design smell
         | 
         | why
        
         | zaptheimpaler wrote:
         | Learning any programming language at all feels 10x as hard to
         | beginners, so you might as well say programming is not worth
         | learning period in this case. Anything new has a learning curve
         | to it.
        
         | devjab wrote:
         | I think you can have a lot of debate on the design decisions on
         | Rust, but I don't think the need for these articles tell you a
         | lot about the language itself. I'd argue that Python needs
         | articles like this more so than Rust does, but for entirely
         | different reasons. In two decades of more and more programmers
         | who aren't coming from an engineering background, I've yet to
         | see anyone who used a Python generator or slots. Data Classes
         | are less rare, but mainly in the form of pydantics "version".
         | Which doesn't exactly matter for a lot of Python code... This
         | is a world where 4chan can serve 4 million concurrent users an
         | apache server running a 10k line PHP file neither of which have
         | been updated since 2015... so you can be fine doing inefficient
         | and entirely in-memory Python code 95% (or more) of the time.
         | 
         | That doesn't mean you should though. Imagine how much energy is
         | being wasted globally on bad Python code... The difference is
         | of course that anyone can write it, and not everyone can write
         | Rust. I'm not personally a big fan of Rust, I'd chose Zig any
         | day of the week... but then I'd also choose C over C++, and I
         | frankly do when I optimise Python code that falls in those last
         | 5%. From that perspective... of someone who really has to
         | understand how Python works under the hood and when to do what,
         | I'd argue that Rust is a much easier langauge to learn with a
         | lot less "design smell". I suppose Python isn't the greatest
         | example as even those of us who love it know that it's a
         | horrible language. But I think it has quite clearly become the
         | language of "everyone" and even more so in the age of LLM.
         | Since our AI friends will not write optimised Python unless you
         | specifically tell them to use things like generators and where
         | to use them, and since you (not you personally) won't because
         | you've never heard about a generator before, then our AI
         | overlords won't actually help.
        
         | bsder wrote:
         | > If a language needs an article like this, absolutely begging
         | people to bite the bullet to learn it, maybe that's a language
         | design smell.
         | 
         | The problem with articles like this is that they don't really
         | get to the heart of the problem:
         | 
         | There are programs that Rust will simply not let you write.
         | 
         | Rust has good reasons for this. However, this is fundamentally
         | different from practically every programming language that
         | people have likely used before where you can write the most
         | egregious glop and get it to compile and sometimes even kinda-
         | sorta run. You, as a programmer, have to make peace with not
         | being able to write certain types of programs, or Rust is not
         | your huckleberry.
        
           | swiftcoder wrote:
           | > There are programs that Rust will simply not let you write.
           | 
           | Can you specify a few of these programs?
           | 
           | I can see where Rust might not allow you to write something
           | the _way you want to_ , but I fail to see how a program would
           | not be expressible in rust...
        
             | steveklabnik wrote:
             | They mean in Safe Rust. Unsafe is included in Rust for this
             | reason.
        
               | swiftcoder wrote:
               | Is safe rust not Turing complete? I can see the argument
               | that a purist "safe rust only" program might be slow, but
               | it still will be expressible
        
               | steveklabnik wrote:
               | Turning completeness doesn't take efficiency into
               | account, nor the reality of things like "call into the
               | operating system so that you can display output" that are
               | necessary when building real systems.
        
               | swiftcoder wrote:
               | With respect, I think that's moving the goalposts. If we
               | have defined out of existence all forms of I/O, then what
               | are we actually discussing?
        
               | steveklabnik wrote:
               | We haven't, that's why Turing completeness is not
               | relevant for the question at hand.
               | 
               | I can implement the non-IO parts of Brainfuck with safe
               | Rust, so it is Turing Complete. That doesn't change the
               | fact that there are useful programs not expressible in
               | it.
        
           | empath75 wrote:
           | > There are programs that Rust will simply not let you write.
           | 
           | If you're writing purely safe code, I will say this is true
           | in a practical sense, but you can almost always use unsafe to
           | write whatever you think rust won't let you do.
        
         | melodyogonna wrote:
         | Rust design decisions are pretty hard to understand sometimes,
         | Mojo is another language with a borrow-checker but it is not
         | nearly as hard to learn as Rust due to making a few decisions.
         | First is value semantics, in Rust people are told to always
         | clone when learning, why isn't this semantics built into the
         | language? It is what you have in most static languages - C,
         | C++, Go, etc. This is the mental model many people come to Rust
         | with.
         | 
         | Secondary, Mojo's lifetime does not tell the compiler when a
         | value is safe to use but when it is safe to delete, in this way
         | the lifetime is not scope based, references will extend the
         | lifetime of the value they reference, but values will be
         | destroyed immediately after their last use. In Mojo you'll
         | never see "value does not live long enough".
         | 
         | Just these two design decisions defines away so many ergonomic
         | issues.
        
           | tcfhgj wrote:
           | > people are told to always clone when learning, why isn't
           | this semantics built into the language?
           | 
           | Because cloning as opposed to copying is expensive and it
           | generates a new instance of a type. In C, you don't clone,
           | you simply copy the struct or pointer, which will lead to a
           | pointer to the same memory or a struct with members pointing
           | to the same memory.
           | 
           | C++ on the other hand has a copy constructor, and you have to
           | move explicitly, often generating unnecessary copies (in the
           | sense of clone)
           | 
           | > Mojo's lifetime does not tell the compiler when a value is
           | safe to use but when it is safe to delete,
           | 
           | What happens if you pass the variable mutably to a function?
        
             | melodyogonna wrote:
             | > What happens if you pass the variable mutably to a
             | function?
             | 
             | What happens in what manner? Mojo uses ASAP memory model,
             | values will always be destroyed at the point of its last
             | use. Mojo dataflow analysis will track this.
             | 
             | In terms of safety, Mojo will enforce `alias xor
             | mutability` - like in Rust.
             | 
             | > C++ on the other hand has a copy constructor, and you
             | have to move explicitly, often generating unnecessary
             | copies (in the sense of clone)
             | 
             | Mojo also has copy and move constructors, but unlike in C++
             | these are not synthesised by default; the type creator has
             | to either explicitly define the constructors or add a
             | synthesiser. In Mojo, you can have types that are not
             | copyable and not movable, these types can only be passed by
             | reference. You can also have types that are copyable but
             | not movable, or movable but not copyable.
        
           | dsego wrote:
           | > but values will be destroyed immediately after their last
           | use
           | 
           | Is this reference counting?
        
             | melodyogonna wrote:
             | Nah, deterministic compiler analysis. Something they call
             | ASAP memory management
        
         | jbs789 wrote:
         | The article focuses on the learning curve rather than the
         | problem Rust is solving, as an observation. Think you need both
         | of those to draw a conclusion as to whether it's worth doing.
        
         | BlackFly wrote:
         | The truth is that by the time you are a senior developer, you
         | will have encountered the lessons that make rust worth learning
         | but may not have truly understood all the implications.
         | 
         | Many people will think, I have a garbage collected language,
         | rust has nothing to teach me. Even in garbage collected
         | languages, people create immutable types because the
         | possibility of shared references with mutability makes things
         | incredibly chaotic that they look for immutability as a sort
         | panacea. However, once you have immutable types you quickly
         | realize that you also need ergonomic ways of modifying those
         | objects, the methods you create to do so are often more
         | cumbersome than what would be permitted for a mutable object.
         | You wish there was some way to express, "There is a time where
         | this object is mutable and then it becomes immutable." Enter
         | the borrow checker.
         | 
         | Once you are borrow checking... why are you garbage collecting?
         | Well, expressing those timelines of mutability and existence is
         | a cost because you need to understand the timeline and most
         | people would rather not spend that energy--maybe mutability or
         | the poor ergonomics of immutable objects wasn't so bad. So, I
         | garbage collect because I do not want to understand the
         | lifetimes of my objects. Not understanding the lifetimes of
         | objects is what makes shared mutability hard. Immutability
         | eliminates that problem without requiring me to understand.
         | Rust can teach this lesson to you so that you make an informed
         | choice.
         | 
         | Of course, you can also just listen to me and learn the same
         | lesson but there is value for many people to experience it.
        
           | zahlman wrote:
           | > Not understanding the lifetimes of objects is what makes
           | shared mutability hard.
           | 
           | Well, no; in my experience the difficulty overwhelmingly
           | comes from thinking about the semantics. I.e.: these two
           | clients currently share a mutable object; _should they_
           | observe each others ' mutations? Or: if I clone this object,
           | will I regret _not_ propagating the change to other clients?
        
         | atoav wrote:
         | As someone who learned Rust, bur mostly uses Python in the day
         | to day, I don't think Rust has a language design smell. It is
         | just a very strict language with some of the strictness out
         | there to ruin your day if you try to program Rust like it isn't
         | Rust.
         | 
         | What that means is for example, if you have high aesthetical
         | ideals and try to write object oriented code you will hit a
         | brick wall eventually. Why? Notnbecause Ruwt is a bad language,
         | but because you try to write Rust like it is Java or something.
         | 
         | Rust is a very nice language if you respect that there are
         | Rust-ways of doing things that and that these ways are more
         | data oriented than you might be used to.
         | 
         | The strictness can be daunting for beginners, but with
         | increasing complexity it becones an absolute godsend. Where in
         | other languages I find errors only when they happen, most Rust
         | code just works (provided you write it in a Rust way), because
         | the errors will caught during compilation.
         | 
         | That doesn't prevent logic errors, but these can be addressed
         | with the absolute stellar test integrations. Now Rust is not
         | all roses, but it is certainly a language worth learning even
         | if you never use it. The ways it mitigates certain classes of
         | errors can be turned into good coding practises for other
         | languages as well.
        
       | worik wrote:
       | Surrender! to compile
       | 
       | Weather the ferocious storm
       | 
       | You will find, true bliss
        
       | ajross wrote:
       | > 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 wrote:
         | So 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 wrote:
         | Not 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 wrote:
         | I 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.
        
           | ajross wrote:
           | > Cloning small objects is lightning fast
           | 
           | It's really not, it's the way python works. Heap allocations
           | are "fast" on modern CPUs that are too fast to measure for
           | most stuff, but they're much (much) slower than the function
           | call and code you're going to use to operate on whatever the
           | thing it was you cloned.
           | 
           | Code that needs memory safety and can handle performance
           | requirements like this has _many_ options for source
           | language, almost none of which require blog posts to
           | "flatten the learning curve".
           | 
           | (And to repeat: it's much slower than a GC which doesn't have
           | to make the clone at all. Writing Rust that is "Slower Than
           | Java" is IMHO completely missing the point. Java is boring as
           | dirt, but super easy!)
        
             | pkolaczk wrote:
             | Cloning doesn't imply heap allocation. Depends on the type.
        
               | ajross wrote:
               | If the object had a stack-bounded lifetime, the borrow
               | checker would have been able to prove the analysis
               | though. The advice is to clone things it can't, which
               | pretty much requires that it go into the general heap.
               | I'm sure there are some interesting counterexamples, but
               | the situation you're imagining seems kinda academic.
        
         | nemothekid wrote:
         | > _significantly faster for this kind of code_
         | 
         | "Significantly" and "this kind" are load bearing sentences
         | here. In applications where _predictable_ latency is desired,
         | cloning is better than GC.
         | 
         | This is also the baby steps of learning the language. As a
         | programmer gets better they will recognize when they are making
         | superflous clones. Refactoring performance-critical stuff in
         | FFI, however, is painful and wont get easier with time.
         | 
         | Furthermore, in real applications, this only really applies to
         | Strings and vectors. In most of my applications most `clones`
         | are of reference types - which is only marginally more
         | expensive than memory sharing under a GC.
        
         | kaoD wrote:
         | > At that point you might as well be writing Java or Go or
         | whatever though.
         | 
         | And miss Option, Result, proper enums, powerful pattern
         | matching, exhaustive pattern matching, affine types, traits,
         | doctests... and the many other QoL features that I sorely miss
         | when I drop to e.g. TS/Node.
         | 
         | I'm not using Rust for the borrow checker, but it's nice to
         | have when I need it to hold my hand and not that much of an
         | issue when I don't. I wanted to like Go but I just can't.
         | 
         | Dropping to no_std though... that was a traumatic experience.
        
       | 8s2ngy wrote:
       | It 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 wrote:
         | I 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 wrote:
         | Your 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.
        
         | zahlman wrote:
         | Out of curiousity, how many other programming languages were
         | you familiar with before Rust?
        
       | doug_durham wrote:
       | This 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 wrote:
       | >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 wrote:
         | C++ 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 wrote:
         | Just 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 wrote:
           | Even 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
        
         | joatmon-snoo wrote:
         | Strings are like time objects: most people and languages only
         | ever deal with simplified versions of them that skip a lot of
         | edge cases around how they work.
         | 
         | Unfortunately going from most languages to Rust forces you to
         | speedrun this transition.
        
         | winrid wrote:
         | The question is worded weird for fun. One is a string slice
         | (like char*) and one is String or &String, which is closer to
         | an object.
        
         | umanwizard wrote:
         | I'm not sure why it's counterintuitive that &str and String are
         | different things. Do you also find it counterintuitive in C++
         | that std::string is different from const char* ? What about
         | &[u8] and Vec<u8> ?
        
           | dezgeg wrote:
           | Better analogy is std::string_view vs std::string
        
             | scotty79 wrote:
             | Nah. &str is const char* exactly. It's as primitive as
             | types in rust get.
        
               | tuetuopay wrote:
               | Nope. `&str` includes the length of the slice, which
               | `const char*` does not. `std::string_view` is the proper
               | analogy.
        
               | umanwizard wrote:
               | Another difference is that &str is guaranteed to be
               | utf-8, whereas const char* can be any encoding (or no
               | encoding).
        
               | pdpi wrote:
               | Also, &str is closer to const uint8_t* than it is to
               | const char*. Chars are signed by default and are _at
               | least_ 8 bits, but can be wider.
        
             | umanwizard wrote:
             | Technically that's a bit closer, yes, but way more people
             | have heard of char* than string_view, and char* is similar
             | _enough_ to &str that the analogy still works.
        
         | akewovtsn wrote:
         | Python community famously learned the hard way that sometimes
         | the programmer needs to know that there are multiple kinds of
         | string.
         | 
         | Personally, I've been using to_owned instead. Some of the
         | people looking at my code don't write rust, and I figure it
         | makes things a bit easier to understand.
        
         | darthrupert wrote:
         | On the other side where this question is not asked we have
         | things like                   > "1" + 2          3
         | 
         | And it's utter madness that everyone does anything important
         | with languages like that.
        
       | mellosouls wrote:
       | Good article, thoughtful and well-written and (idioms aside) much
       | of it applicable to learning other languages.
        
       | MasterYoda wrote:
       | What is it that make that rust is said to have steep learning
       | curve compared to other programing languages (in the same
       | category)?
        
         | umanwizard wrote:
         | It 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 wrote:
         | Mostly borrow checker.
         | 
         | Lifetime syntax can be off putting.
        
       | jillesvangurp wrote:
       | Rust 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 wrote:
         | > 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 wrote:
           | Macros 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.
        
             | steveklabnik wrote:
             | At the same time, you don't need to author macros. I have
             | basically never written one, for example.
        
             | toprerules wrote:
             | Hard disagree, macros almost always lead the "too clever
             | for you own good" even when the macro system is safe.
             | Macros should always be used sparingly, and I think that
             | Rust teeters on the edge of encouraging too much complexity
             | for the sake of convenience. As a systems programmer I am
             | allergic to unnecessary levels of indirection that make it
             | more difficult for me to reason about what the system is
             | actually doing and how performance and hardware interaction
             | will be affected.
        
               | ModernMech wrote:
               | I agree that if you're using macros to create unnecessary
               | levels of indirection that make things more difficult to
               | reason about, that's not advisable. One shouldn't do that
               | with macros or any other abstraction.
               | 
               | With functions for instance, sometimes people get carried
               | away very straightforward linear code, and atomize it
               | into a hundred little functions that all call one
               | another, all in the name of reducing code duplication.
               | Doing so is an abuse of functions, but one wouldn't say
               | that functions should be used sparingly.
               | 
               | I think that macros are an area where many programmers
               | don't have a lot of experience, and so they also throw
               | out some best practices in order to wrap their heads
               | around what they're doing.
               | 
               | But macros can be very helpful if properly applied, and
               | Rust makes that more ergonomic, and safe, to do.
               | 
               | For example, if I have to write 100 functions that all
               | are similar in structure, but have a slightly different
               | function signature. I would reach for a macro. I don't
               | think it reduces clarity at all, and it increases
               | maintainability because I don't have to change code in
               | 100 functions if an adjustment needs to be made.
               | 
               | Another area where macros is very useful is in creating
               | DSLs within Rust. This is usually the domain of LISPs,
               | but it's an ergonomic way to write tests little scripts.
        
         | devnullbrain wrote:
         | >it's very different from other languages. That's intentional
         | but also an obstacle.
         | 
         | It's very different from a lot of the languages that people are
         | typically using, but all the big features and syntax came from
         | somewhere else. See:
         | 
         | >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.
         | 
         | Well, yeah, but they generally don't like types at all. You
         | won't have much knowledge to draw on if that's all you've ever
         | done, unless you're learning another language in the same space
         | with the same problems.
        
           | syklemil wrote:
           | Python is growing type annotations at a brisk pace though,
           | and Typescript is cannibalizing Javascript at an incredible
           | speed. Between that and even Java getting ADTs, I suspect the
           | people who whine about "type nerds" are in for some rough
           | years as dynamic languages lose popularity.
           | 
           | And I suspect the people who are familiar with seeing
           | something like `dict[str, int]` can map that onto something
           | like `HashMap<String, i32>` without actually straining their
           | brains, and grow from there.
        
       | truth_seeker wrote:
       | Compared to other programming languages, Rust's compiler and
       | linters go a long way to implement best practices at build time.
        
       | jamil7 wrote:
       | The 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 wrote:
       | My 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 wrote:
         | Have you had a look at Nim? I think you may like it.
        
           | mkaic wrote:
           | I 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 wrote:
         | C 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.
        
           | 0x000xca0xfe wrote:
           | C is elegant because as an extremely powerful programming
           | language used to create an uncountable number of high-profile
           | projects it's simple enough that I feel optimistic I could
           | write a C compiler myself if it was really necessary.
           | 
           | It may be impractical for some tasks but the power:complexity
           | rate is very impressive. Lua feels similar in that regard.
        
             | dwattttt wrote:
             | The mechanism for sharing definitions, 'include' aka "copy
             | file here", is absolutely not elegant. The absolute minimum
             | you could do maybe, but not elegant.
        
               | 0x000xca0xfe wrote:
               | Not to mention the whacky preprocessor as a whole...
               | 
               | I would say includes/standard library/compilers going
               | crazy on UB is part of the infrastructure or ecosystem
               | around the language and not the language itself. And I
               | agree absolutely, while C the language is beautiful, the
               | infra around it is atrocious.
        
         | syklemil wrote:
         | There's no discussing taste, especially with syntax. Personally
         | I find the Rust syntax unoffensive, while the Go syntax comes
         | off kind of ... weird, with type signatures especially looking
         | kind of like run-on sentences by someone who hates punctuation.
         | C's type signatures come off as a turgid mess to me; stuff like
         | this https://www.ericgiguere.com/articles/reading-c-
         | declarations.... is just a design mistake as far as I'm
         | concerned. And Python ... kind of goes into the "ah, I give up,
         | slap an `Any` signature on it" territory.
         | 
         | And some people love that! It just ain't for everyone.
        
       | umajho wrote:
       | > .... For instance, "a trait is a bit like an interface" is
       | wrong, ...
       | 
       | Wait. What's wrong with this statement?
        
         | steveklabnik wrote:
         | I 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 wrote:
           | That makes sense.
        
       | pjmlp wrote:
       | Traits 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 wrote:
       | Ownership 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 wrote:
       | One 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 wrote:
       | For 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 wrote:
       | "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 wrote:
         | "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 wrote:
           | The 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.
        
             | vacuity wrote:
             | I think Java pointers wouldn't count as raw pointers
             | despite having many similar characteristics.
        
             | devnullbrain wrote:
             | >and then access the data at that location
             | 
             | Or many other locations, by many other authors, at many
             | other times, or simultaneously.
        
             | tremon wrote:
             | It all depends on how you define computer science vs
             | computer engineering. In all my CS classes, not once did I
             | need to deal with pointer arithmetic or memory layout.
             | That's because my CS classes were all theoretical,
             | concerning pseudocode and algorithmic complexity. Mapping
             | the pseudocode onto actual hardware was never a
             | consideration.
             | 
             | In contrast, there was hardly ever a computer engineering
             | class where I could ignore raw memory addresses. Whether it
             | was about optimizing a memory structure for cache layout or
             | implementing some algorithm efficiently on a resource-
             | anemic (mmu-less) microcontroller, memory usage was never
             | automatic.
        
           | zahlman wrote:
           | The importance of a concept is not related to its frequency
           | of direct use by humans. The
           | https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_equation
           | is one of the most important concepts in options trading, but
           | AFAIK quants don't exactly sit around all day plugging values
           | into it.
        
       | GenshoTikamura wrote:
       | > 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 wrote:
         | But 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 wrote:
           | Sounds like an abusive relationship if im being honest. Your
           | programming language shouldnt constrict you in those ways.
        
             | lagniappe wrote:
             | It helps to understand the cultural ethos of the original
             | rust devs, and the situation that gave rise to it.
        
             | jplusequalt wrote:
             | >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 wrote:
               | Also: 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.
        
             | hbn wrote:
             | Every programming language has constrictions by the nature
             | of having syntax.
             | 
             | In JavaScript you can declare a variable, set it to 5
             | (number), and then set it to the "hello" (string), but
             | that's not allowed in e.g. C. Is C constricting me too much
             | because I have to do it in C's way?
        
               | dgfitz wrote:
               | I believe you can do that in C pretty easily with a void
               | pointer, someone correct me if I'm mistaken.
               | 
               | Should you? Different question entirely.
        
               | quietbritishjim wrote:
               | But you can't add 2 void pointers and seamlessly get
               | integer addition if they point at integers or
               | concatenation if they point at strings.
               | 
               | (You could build your own custom data types that have
               | type metadata in a shared header and an addition function
               | that uses it, but then you're building your own custom
               | language on top which isn't really the same thing.)
               | 
               | So yes C really does restrict you in some ways that
               | Javascript doesn't.
        
               | dgfitz wrote:
               | That wasn't the constraint I was responding to, you moved
               | the goalposts! :)
        
             | ModernMech wrote:
             | Abusive relationships involve coercion, control, fear, and
             | often violate personal autonomy and consent. One party
             | dominates the other in a _harmful_ way. Using Rust is not
             | harmful.
             | 
             | Placing restrictions on the programs a programmer can write
             | is not abusive. The rules exist to ensure clarity, safety,
             | performance, and design goals. In an abusive relationship,
             | rules are created to control or punish behavior, often
             | changing capriciously and without reason or consultation.
             | By contrast, Rust is designed by a group of people who work
             | together to advance the language according to a set of
             | articulated goals. The rules are clear and do not change
             | capriciously.
             | 
             | Abuse causes emotional trauma, isolation, and long-term
             | harm. Rust may cause feelings of frustration and annoyance,
             | it may make you a less efficient programmer, but using it
             | does not cause psychological or physical harm found in
             | abusive relationships.
        
           | pessimizer wrote:
           | Works that way with learning a spoken language, too. I
           | couldn't learn my second language until I stopped thinking I
           | was supposed to judge whether things in the language were
           | "good" or not. Languages aren't meant to be "good" in a
           | beauty contest sense, they're supposed to be useful. Accept
           | that they _are_ useful because many, many people use them,
           | and just learn them.
           | 
           | I probably wouldn't have been able to do that with Rust if I
           | hadn't been an Erlang person previously. Rust seems like
           | Erlang minus the high-overhead Erlangy bits plus extreme type
           | signatures and conscious memory-handling. Erlang where only
           | "zero-cost abstractions" were provided by the language and
           | the compiler always runs Dialyzer.
        
       | Meneth wrote:
       | Feels like a cult manual. "Do all things our way! Don't question
       | anything!"
        
         | noodletheworld wrote:
         | If 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 wrote:
         | That's how every student forced to take a programming elective
         | feels about any programming language.
        
       | ramon156 wrote:
       | These 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 wrote:
       | Sorry, 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 wrote:
       | The 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 wrote:
       | I'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 wrote:
         | It'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 wrote:
         | It'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 wrote:
         | As 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 wrote:
         | Rust will prevail only if it fixes C++ interop
        
       | nmeofthestate wrote:
       | "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 wrote:
         | cargo fix can automatically fix some, but not all, issues.
        
         | zahlman wrote:
         | Because 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 wrote:
       | As 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 wrote:
         | I 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 wrote:
           | For 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.
        
         | RealityVoid wrote:
         | As a low level programmer, my biggest pain with rust was the
         | type system. More precisely the traits and the trait bounds.
         | 
         | I suspect if you have C++ experience it's simpler to grokk, but
         | most of the stuff I wrote was C and a bunch of the stuff Rust
         | did were not familiar to me.
        
       | the__alchemist wrote:
       | I 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 wrote:
         | +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 wrote:
         | You 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 wrote:
         | > 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.
        
       ___________________________________________________________________
       (page generated 2025-05-14 23:01 UTC)