[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)