[HN Gopher] Rust concepts I wish I learned earlier
       ___________________________________________________________________
        
       Rust concepts I wish I learned earlier
        
       Author : rck
       Score  : 314 points
       Date   : 2023-01-18 15:14 UTC (7 hours ago)
        
 (HTM) web link (rauljordan.com)
 (TXT) w3m dump (rauljordan.com)
        
       | jasonjmcghee wrote:
       | Small note- at least one of the links on mobile does not respect
       | the text column width of the page, so the actual page width is
       | wider than the text and horizontally scrollable.
        
       | perrygeo wrote:
       | Nice article, but I'm not sure I like using the Deref trait just
       | to make code "cleaner" - it does the opposite IMO, making it
       | harder to understand what's going on.
       | 
       | Deref is convenient for builtin "container" types where the only
       | thing you'll ever do is access the singular value inside it. But
       | sprinkling it everywhere can get confusing ("why are we assigning
       | a &str to a struct here?")
        
         | TuringTest wrote:
         | In addition to containers, it seems to be useful for 'wrapper'
         | types adding decorators to the 'base' object without having to
         | wrap/unwrap the new type in each operation.
         | 
         | Classic OOP would use inheritance to achieve this, while
         | something like Deref allows you to use it with all the added
         | behavior - without losing the possibility to assign to it
         | values of the base type.
        
           | perrygeo wrote:
           | A good discussion of why using Deref to simulate inheritance
           | is considered an "anti-pattern": https://rust-
           | unofficial.github.io/patterns/anti_patterns/der...
        
             | jcuenod wrote:
             | Thanks for this; I saw it, and it made me twitch, but I
             | didn't know why... All I had was "composition over
             | inheritance"
        
       | lakomen wrote:
       | God almighty, that language is sl fugly to look at
        
       | kris-s wrote:
       | The author mentions the following:                   fn add(x:
       | Option<i32>, y: Option<i32>) -> Option<i32> {             if
       | x.is_none() || y.is_none() {                 return None;
       | }             return Some(x.unwrap() + y.unwrap());         }
       | The above looks kind of clunky because of the none checks it
       | needs to perform, and it also sucks that we have to extract
       | values out of both options and construct a new option out of
       | that. However, we can much better than this thanks to Option's
       | special properties! Here's what we could do              fn
       | add(x: Option<i32>, y: Option<i32>) -> Option<i32> {
       | x.zip(y).map(|(a, b)| a+b)         }
       | 
       | Do folks really prefer the latter example? The first one is so
       | clear to me and the second looks inscrutable.
        
         | virtualritz wrote:
         | TLDR; learning Rust through canonical code in tutorials often
         | requires the student to learn bits about the language that are
         | more advanced than the actual problem the resp. tutorial tries
         | to teach how to solve in Rust. ;)
         | 
         | I prefer the latter now that I understand how all the
         | Result/Option transformations work. As a beginner this would be
         | hard to read but the former looks clunky.
         | 
         | Clippy also got pretty good lately at suggesting such
         | transformations instead of if... blocks. I.e. I guess that
         | means they are considered canonical.
         | 
         | In general I find canonical Rust often more concise than what a
         | beginner would come up with but it does require deeper
         | understanding. I guess this is one of the reasons why Rust is
         | considered 'hard to learn' by many people.
         | 
         | You could actually teach Rust using pretty verbose code that
         | would work but it wouldn't be canonical (and often also not
         | efficient, e.g. the classic for... loop that pushes onto a Vec
         | vs something that uses collect()).
        
           | mprovost wrote:
           | This is very true - to fully explain a "hello world" program
           | you'd have to dive into macro syntax... When writing my Rust
           | book I often start by showing the naive solution, and then
           | later move to more canonical code once I've introduced more
           | syntax that enables something more elegant. But I'm aware
           | that I'm showing something non-optimal to start. Maybe that
           | loses some trust, like people think they're learning
           | something only to be told later on that it's wrong? On the
           | other hand if you start with the optimal solution you have to
           | teach so much syntax that it's overwhelming. I expect that
           | some folk want every example to be perfect, but I'm going
           | with an approach where you iterate through each chapter and
           | as you progress through the whole book the examples get
           | better and more idiomatic.
        
             | steveklabnik wrote:
             | This is basically an eternal battle when teaching.
             | Personally I prefer to try and stay to only idiomatic code
             | if at _all_ possible, but there 's pros and cons to each
             | approach.
             | 
             | (This is one reason why I'm glad other people are also
             | writing books! Not everyone likes my style!)
        
         | h4x0rr wrote:
         | I'd prefer a match approach
        
         | [deleted]
        
         | runevault wrote:
         | Once you know what map does with an option I'd say it is mostly
         | pretty readable. Basically map (when run against an Option
         | value) is a way to say "if the value passed in has a value run
         | this function on it otherwise return None.
        
         | edflsafoiewq wrote:
         | First one isn't idiomatic anyway with return. You can just do
         | Some(x? + y?) though.
        
         | civopsec wrote:
         | What's there to say? It works the same way as `zip` does for an
         | iterator over a `Vec`. So if you understand `zip` as applied to
         | an iterator over `Vec`, then you might understand `zip` applied
         | to `Option`.
         | 
         | In other words:
         | 
         | Y is clear if you already understand X. Like how returning
         | early is simple to understand if you understand if-blocks.
         | 
         | That's the problem with replying to these kinds of questions:
         | the response is so short and context-dependent that it can look
         | curt.
         | 
         | EDIT: the first code is also inelegant in that it effectively
         | checks if both values are `None` twice in the `Some` case: once
         | in the if-condition and once in `unwrap()` (the check that
         | panics if you try to unwrap a `None`).
        
         | adwn wrote:
         | Both are rather ugly. This is much more idiomatic:
         | match (x, y) {             (Some(x), Some(y)) => Some(x + y),
         | _ => None,         }
        
           | dullcrisp wrote:
           | Don't know Rust, but wouldn't this have to be:
           | (Some(x), Some(y)) => Some(x + y)         else => None
        
             | [deleted]
        
             | steveklabnik wrote:
             | You're correct, except that "else" is a keyword and so
             | cannot be used there. You'd want                 _ => None,
             | 
             | instead, which is the "catch all" arm.
             | 
             | (For those that didn't catch it, the parent code is trying
             | to use None, but it's actually a tuple, and there's four
             | different cases here, not two. So the catch-all arm is
             | better than spelling each of them out in this case.)
        
             | adwn wrote:
             | You're right - fixed. That's what I get from writing code
             | in a simple text area.
        
               | wizzwizz4 wrote:
               | Your "fixed" version is also broken (at time of writing).
               | :-)
        
               | adwn wrote:
               | Fixed again. I'm starting to run out of excuses... ;-)
        
             | [deleted]
        
           | kzrdude wrote:
           | This has been a thing since Rust 1.0. Just use the beautiful
           | properties of match (or the "later" `if let`, of course). I
           | prefer this and wish I could say it was idiomatic, but some
           | tools like clippy push users over to helper methods instead
           | of simple applications of match.
        
           | 0x457 wrote:
           | Pretty sure clippy will tell you to rewrite it as:
           | if let (Some(x), Some(y)) = (x, y) {            Some(x + y)
           | } else {            None         }
           | 
           | `match` in place of `if` looks weird. IMO example with `zip`
           | is better though.
        
             | steveklabnik wrote:
             | Clippy will not complain about the parent's code. It's not
             | really in place of an if; there's four cases there. To be
             | honest, I find 'if let... else None' to be worse looking
             | than the match, though I'm unsure if I truly prefer the zip
             | version to the Some(x? + y?) version.
        
         | woodruffw wrote:
         | Using `zip` feels excessively clever to me. I'd probably prefer
         | something in between, like `and_then` followed by `map`, or
         | just matches.
        
           | linhns wrote:
           | Not sure if it's only me but after using `zip` for the first
           | time in any language, I tend to overuse it too much while
           | there are better, more idiomatic alternatives.
        
           | civopsec wrote:
           | If you are fine with using `map` then I don't see how this is
           | "clever". `zip` is basically "map2".
        
         | trevor-e wrote:
         | Yea the latter code is unreadable to me and feels obfuscated,
         | like the author is trying to force functional programming where
         | it doesn't belong.
        
           | Gigachad wrote:
           | I've done a non trivial amount of functional programming and
           | know what zip and map do but I can't off the top of my head
           | work out how that example works.
        
             | lilyball wrote:
             | `x.zip(y)` evaluates to `Option<(i32, i32)>`
        
               | [deleted]
        
           | [deleted]
        
         | rom-antics wrote:
         | I think the idiomatic way to write that is:                 fn
         | add(x: Option<i32>, y: Option<i32>) -> Option<i32> {
         | Some(x? + y?)       }
        
         | jxcl wrote:
         | You do get better about using the functional operators as you
         | use them, and they can be incredibly powerful and convenient in
         | certain operations, but in this case he's missing the simplest
         | implementation of this function using the `?` operator:
         | fn add2(x: Option<i32>, y: Option<i32>) -> Option<i32> {
         | Some(x? + y?)         }
        
           | [deleted]
        
           | bkolobara wrote:
           | Wait! The ? operator works with the `Option<T>` type? I
           | assumed it was only reserved for `Result<T,E>`.
        
             | misnome wrote:
             | I also (written a few tools in rust, dabble now and then)
             | was under the impression that it only worked with Result.
             | Is this recent or just never encountered?
        
               | steveklabnik wrote:
               | ? is able to be used with Option as of Rust 1.22,
               | released in November of 2017 https://blog.rust-
               | lang.org/2017/11/22/Rust-1.22.html
        
               | misnome wrote:
               | Well, I guess this counts as a concept I wish I learned
               | earlier!
        
               | [deleted]
        
             | steveklabnik wrote:
             | To _slightly_ elaborate on yccs27 's good answer, at the
             | moment, you can use ? for both Option<T> and Result<T, E>,
             | but you can only use them in functions that return the same
             | type, that is                 fn
             | can_use_question_mark_on_option() -> Option<i32> {
             | fn can_use_question_mark_on_result() -> Result<i32, ()> {
             | 
             | if you have them mixed in the body, they won't work
             | directly. What you should/can do there depends on
             | specifics. For example, if you have a function that returns
             | Option and you have a Result inside, and you want any E to
             | turn into None, you can add .ok() before the ? and then it
             | still looks nice. (The compiler will even suggest this
             | one!)
        
               | pavon wrote:
               | And an even smaller caveat: If you use older (distro
               | provided) Rust versions note that it may look like there
               | is some partial implementation of allowing mixing with
               | NoneError, etc. Ignore this. It doesn't work, and was
               | removed in later versions.
        
               | nicoburns wrote:
               | But also: don't use your distro provided version of Rust.
               | It's intended for compiling distro packages that depend
               | on Rust, not for developing with Rust. Get Rust from
               | https://rustup.rs
        
               | pavon wrote:
               | And hence for writing rust code you would like to package
               | for your distro of choice. I publish my software because
               | I want others to use it, and including it in distro repos
               | is the most friendly way to do that.
        
             | yccs27 wrote:
             | It currently works with `Result` and `Option` types, and
             | with RFC#3058 will work with all types implementing the
             | `Try` trait.
        
               | satvikpendem wrote:
               | Monadic do notation equivalent at that point, I suppose.
        
               | yccs27 wrote:
               | Although that would be really fun, the proposed `Try`
               | basically only allows you to call the `Result<_, E>` by a
               | different name.
        
         | ryanianian wrote:
         | As someone new to Rust, I look at the latter and see `.zip()`
         | (apparently unrelated to python zip?), and then a lambda/block
         | which intuitively feels like a heavyweight thing to use for
         | adding (even though I'm like 90% sure the compiler doesn't make
         | this heavyweight).
         | 
         | By comparison, the first one is straightforward and obvious.
         | It's certainly kinda "ugly" in that it treats Options as very
         | "dumb" things. But I don't need to read any docs or think about
         | what's actually happening when I read the ugly version.
         | 
         | So TLDR: This reads a bit like "clever" code or code-golfing,
         | which isn't always a bad thing, especially if the codebase is
         | mature and contributors are expected to mentally translate
         | between these versions with ease.
        
           | kaba0 wrote:
           | It is kinda common pattern for some FP languages (Haskell),
           | so it doesn't seem too clever to me.
        
           | civopsec wrote:
           | > So TLDR: This reads a bit like "clever" code or code-
           | golfing, which isn't always a bad thing, especially if the
           | codebase is mature and contributors are expected to mentally
           | translate between these versions with ease.
           | 
           | You contradict yourself. You can't deride it as "clever"
           | (whatever the quotes mean) and then in the next breath say
           | that it might be a practical style.
           | 
           | And yes, Rust code in the wild does look like this.
        
           | steveklabnik wrote:
           | > (even though I'm like 90% sure the compiler doesn't make
           | this heavyweight).
           | 
           | Yes, as of Rust 1.66, this function compiles to
           | example::add:           xor     edi, 1           xor     edx,
           | 1           xor     eax, eax           or      edx, edi
           | sete    al           lea     edx, [rsi + rcx]           ret
        
           | edflsafoiewq wrote:
           | It's the same zip, just think of an Option as being a list of
           | length at most 1. [] = None, [1] = Some(1), etc.
        
           | leshow wrote:
           | What you find "clever" or not is really a function of what
           | you are most used to seeing. There are likely many folks who
           | use combinators frequently who find them easier to read,
           | myself included.
           | 
           | The first example, to me, is the worst of all worlds, if you
           | want to be explicit use `match`. Otherwise, having a
           | condition then using `unwrap` just feels like it's needlessly
           | complicated for no reason... Just adding my subjective
           | assessment to the bikeshed.
        
         | nicoburns wrote:
         | I'd probably write this                   fn add(x:
         | Option<i32>, y: Option<i32>) -> Option<i32> {             match
         | (x, y) {                 (Some(x), Some(y)) => Some(x + y),
         | _ => None,             }         }
        
           | Yoric wrote:
           | Agreed. That's way more readable.
        
           | [deleted]
        
       | geewee wrote:
       | Having worked with Rust for a little while (about a year) none of
       | this stuff is particularly esoteric. However it is a great list
       | of things that are good to know, but you don't necessarily need
       | all that often (or learn from the official rust book)
        
         | jxcl wrote:
         | You're right, but for those learning Rust, there's _so much_ to
         | learn, that having occasional reminders of the more basic
         | things is really handy. I've been hobby programming in Rust for
         | years and professionally for about 6 months, and I still picked
         | up one or two simple things from this list.
        
       | kris-nova wrote:
       | Great read -- the author should feel proud. This made my morning.
        
       | linhns wrote:
       | This is somewhat off-topic but it's nice to see someone using
       | Zola for their own blog, awesome SSG built on Rust!
        
       | cormacrelf wrote:
       | "Fed up with the borrow checker? Try writing your own immutable
       | linked list." Yeah, that'll help!
        
         | Ygg2 wrote:
         | You mean use immutable data structures?
        
       | draw_down wrote:
       | [dead]
        
       | ElderKorihor wrote:
       | > Use impl types as arguments rather than generic constraints
       | when you can
       | 
       | Eep. No. At least, not in anything public. Universal impl trait
       | arguments can't be turbofished, so you're placing an unnecessary
       | constraint on your caller.
        
       | ihnorton wrote:
       | Really annoyed by the borrow checker? Use immutable data
       | structures         ... This is especially helpful when you need
       | to write pure code similar to that seen in Haskell, OCaml, or
       | other languages.
       | 
       | Are there any tutorials or books which take an immutable-first
       | approach like this? Building familiarity with a functional subset
       | of the language, before introducing borrowing and mutability,
       | might reduce some of the learning curve.
       | 
       | I suspect Rust does not implement as many FP-oriented
       | optimizations as GHC, so this approach might hit performance
       | dropoffs earlier. But it should still be more than fast enough
       | for learning/toy datasets.
        
         | steveklabnik wrote:
         | > I suspect Rust does not implement as many FP-oriented
         | optimizations as GHC
         | 
         | It's more complicated than this; sometimes FPish code is super
         | nice and easy and compiles well (see the zip example in this
         | very thread!) and sometimes it's awkward and hard to use and
         | slower.
        
         | jayy-lmao wrote:
         | Personally I've done a small amount of reading of the Hands On
         | Functional Programming in Rust book. I've also found Scott
         | Wlaschin's F# resources quite transferable (though you may run
         | into some issues with passing async functions around as
         | arguments).
        
       | lesuorac wrote:
       | Well not in the article but I saw somebody doing a guard clause
       | that I started to copy.
       | 
       | i.e.
       | 
       | ```
       | 
       | let min_id = if let Some(id) = foo()? { id } else { return; }
       | 
       | ...
       | 
       | let bar = if let Some(bar) = baz()? { bar } else { return; }
       | 
       | ..
       | 
       | // vs
       | 
       | if let Some(id) = foo()? {
       | 
       | ...
       | 
       | if let Some(bar) = baz()? {
       | 
       | ..
       | 
       | }}
       | 
       | ```
       | 
       | It's nice to also do `if let (Some(x), Some(y)) = ...` but
       | sometimes you need the result of `x` to do a few things before
       | you can get `y` (or maybe don't went to evaluate `y` depending on
       | `x`).
       | 
       | ---
       | 
       | I like the `where` syntax more than the example formatting.
       | 
       | ```
       | 
       | fn x<T>(t: T) where T: Foo {}
       | 
       | ```
        
         | cormacrelf wrote:
         | Have you heard about let-else? It's a recent syntax addition.
         | That example translates as                   let Some(min_id) =
         | foo() else { return };         // continue coding at same
         | indent
        
       | wizzwizz4 wrote:
       | > _Normally, Rust would complain,_
       | 
       | Not in the example given. There's nothing wrong with creating an
       | Rc<T> loop; the borrow checker doesn't come into the picture.
       | 
       | > _That is, you could mutate the data within an Rc as long as the
       | data is cheap to copy. You can achieve this by wrapping your data
       | within a Rc <Cell<T>>._
       | 
       | T: Copy is only a bound on the .get() method. You can do this
       | even if the data is expensive to copy, so long as you always swap
       | in a valid representation of T. (I sometimes write
       | Cell<Option<T>>, where it makes sense to replace with a None
       | value.)
       | 
       | > _Embrace unsafe as long as you can prove the soundness of your
       | API,_
       | 
       | In other words: avoid unsafe except as a last-ditch resort.
       | 
       | > _& impl Meower_
       | 
       | Should be impl Meower, if you want the same behaviour as the
       | explicit-generic version.
       | 
       | > _Many tutorials immediately jump to iterating over vectors
       | using the into_iter method_
       | 
       | Out of interest, what tutorials? I've never read one that does
       | that!
       | 
       | > _Instead, there are two other useful methods on iterators_
       | 
       | Methods on many iterables in std. Not on iterators (nor iterables
       | in general).
       | 
       | > _You can wrap the following types with PhantomData and use them
       | in your structs as a way to tell the compiler that your struct is
       | neither Send nor Sync._
       | 
       | ... You're doing it wrong. EUR30 says your unsafe code is
       | unsound.
       | 
       | > _Embrace the monadic nature of Option and Result types_
       | 
       | Er, maybe? But try boring ol' pattern-matching first. It's
       | usually clearer, outside examples like these ones (specially-
       | chosen to make the helper methods shine). I'd go for if let, in
       | this example - though if the function really just returns None in
       | the failure case, go with the ? operator.
       | 
       | > _For example, writing a custom linked list, or writing structs
       | that use channels, would typically need to implement a custom
       | version of Drop._
       | 
       | No, you don't typically need to. Rust provides an automatic
       | implementation that _probably_ does what you need already. Custom
       | drop implementations are mainly useful for unsafe code.
       | 
       | > _Really annoyed by the borrow checker? Use immutable data
       | structures_
       | 
       | No. _No._ Haskell has all sorts of optimisations to make
       | "immutable everything" work, and Rust's "do what I say" nature
       | means none of those can be applied by the compiler. If you want
       | to program this way, pick a better language.
       | 
       | > _Instead, you can define a blanket trait that can make your
       | code a more DRY._
       | 
       | There are no blanket _traits_. There are blanket trait
       | _implementations_ , and you've missed that line from your
       | example.
       | 
       | All in all, this is a good article. There are some good tips in
       | there, but I wouldn't recommend it to a beginner. I _would_
       | recommend the author revisit it in a few months, and make some
       | improvements: a tutorial _designed_ when you 're green, but with
       | the _experience_ from your older self, can be a really useful
       | thing.
        
         | [deleted]
        
         | friedman23 wrote:
         | The book "Programming Rust" (very good book btw) only mentions
         | iter() and iter_mut() briefly and focuses on into_iter because
         | the IntoIterator trait only implements into_iter.
         | 
         | You can replicate iter() with `(&x).into_iter()` and you can
         | replicate iter_mut() with `(&mut x).into_iter()` and obviously
         | `x.into_iter()` consumes the contents.
        
       | joshfee wrote:
       | > there is a lot of excellent Rust content catering to beginners
       | and advanced programmers alike. However, so many of them focus on
       | the explaining the core mechanics of the language and the concept
       | of ownership rather than architecting applications.
       | 
       | The author then goes on to write an article largely covering the
       | mechanics of the language rather than architecting applications.
        
         | rauljordan2020 wrote:
         | hah got me there. Content on that topic will be coming soon
         | after this post :). This was my first foray into writing about
         | the language
        
           | jyu wrote:
           | architecture patterns and antipatterns would be a welcome
           | contribution
        
           | tmpz22 wrote:
           | My wishlist would include design patterns for business
           | applications in Rust, where a beginner-intermediate level
           | Rust programmer could learn the language and how to use the
           | language practically at the same time.
        
             | neilv wrote:
             | Rust is a systems programming language, with a large number
             | of systems programming-motivated concepts to learn before
             | you don't get stuck.
             | 
             | I suspect, if someone is looking for copy&paste code
             | patterns for business applications (like CRUD)... they're
             | going to get stuck in situations where they hit brick walls
             | that the cargo-culting rituals didn't cover.
             | 
             | Will they have somehow learned enough Rust by then to solve
             | their problem, or will they be frantically posting
             | StackOverflow questions on what code to copy&paste to do
             | what they need... and end up creating new brick walls that
             | are even harder to extricate themselves from?
             | 
             | With lots of business applications developers on Agile-ish
             | methodology, where workers have to continually be able to
             | claim they are "completing tasks" at a rapid pace, I think
             | Rust would make them cry. It's hard to call a task complete
             | when it won't compile. And working with
             | borrowing/lifetimes/etc. is almost always going to take
             | longer than (what Agile very-short-term thinking rewards)
             | leaning on copy&paste and GC and mutating with abandon for
             | the current task, while letting any increased defects due
             | to that become new tasks.
             | 
             | And when those Rust business developer workers are crying
             | and slipping on their deliverables, the leads/managers who
             | chose to use Rust (with people who really just want to use
             | something more like Python or Ruby or JavaScript)... are
             | going to get called onto the carpet to explain. Live by the
             | Agile theatre, die by the Agile theatre.
             | 
             | (At a couple previous startups, where there was systems
             | programming to do in addition to business-y apps, I've
             | actually proposed using Rust as a way to attract certain
             | kinds of genuinely enthusiastic developers, and to make it
             | harder for some kinds of low-quality code to slip in. But I
             | probably wouldn't do Rust if I only had a normal business
             | application, and only normal business developers who were
             | asking for code examples to cargo-cult.)
        
               | jayy-lmao wrote:
               | Full time Rust web dev here (have been so for about a
               | year).
               | 
               | Feature delivery was slow to start due to familiarity
               | with the language, the business domain, and the project.
               | Now that the patterns within our project have been
               | established (EDA, Vertical Slice, some DDD for those
               | interested) it's actually proving quite easy to work on.
               | 
               | Have been a ts dev in a past life, and while I wouldn't
               | necessarily reach for Rust for future green fields it has
               | been quite pleasing to work with for the last year.
        
         | [deleted]
        
       | endorphine wrote:
       | > There are two kinds of references in Rust, a shared reference
       | (also known as a borrow)
       | 
       | Is that really what they're called? It seems confusing to me: if
       | it's shared (i.e. many people use it at the same time) how can it
       | also be borrowed (i.e. one person has it)?
        
         | steveklabnik wrote:
         | There are multiple ways of referring to this stuff.
         | 
         | * mutable/immutable (this is the way the book refers to it and
         | used to be the 'official' terms but I don't know if they've
         | changed that since I left, partially this is the name because
         | you spell it "&mut".)
         | 
         | * shared/exclusive (this is the name that some people wish we
         | had called mutable/immutable, a very very long time ago thing
         | called the "mutapocalypse.")
         | 
         | Both sets are adjectives for "borrow."
         | 
         | I agree with you that "shared borrow" can feel like a
         | contradiction in terms.
         | 
         | In general, they are duals of each other: it depends if you
         | want to start from a place of "can I change this" or "who all
         | has access to this," making each term more clear depending on
         | the perspective you take.
        
       | woodruffw wrote:
       | Nice list!
       | 
       | This is a nitpick on my part, but this part on PhantomData:
       | 
       | > Tells the compiler that Foo owns T, despite only having a raw
       | pointer to it. This is helpful for applications that need to deal
       | with raw pointers and use unsafe Rust.
       | 
       | ...isn't _quite_ right: `_marker: marker::PhantomData <&'a T>,`
       | doesn't tell the compiler that `Foo` owns `T`, but that instances
       | of `Foo` can't outlive its `bar` member. `bar` is in turn
       | borrowed, since a pointer is "just" an unchecked reference.
       | 
       | You can see this in the playground[1]: `foo1` and `foo2` have the
       | same borrowed `bar` member, as evidenced by the address being
       | identical (and the borrow checker being happy).
       | 
       | Edit: What I've written above isn't completely correct either,
       | since the `PhantomData` member doesn't bind any particular
       | member, only a lifetime.
       | 
       | [1]: https://play.rust-
       | lang.org/?version=stable&mode=debug&editio...
        
         | cormacrelf wrote:
         | Not quite, it doesn't tell the compiler much about how Foo
         | relates to its bar field unless you also constrain the public
         | API for creating a Foo. If Foo is constructed out of a 'new'
         | method with this signature:                   impl<'a, T>
         | Foo<'a, T> {             pub fn new(bar: &'a T) -> Self {
         | Self { bar, _marker: PhantomData }             }         }
         | 
         | ... AND the bar field is private, then you are ensuring the Foo
         | container doesn't outlive the original bar pointer. If you
         | don't constrain creation and access to the bar field then
         | people can just write foo.bar = &new_shorter_borrow as *const
         | T; and then the lifetimes are absolutely unrelated, foo.bar
         | will become invalid soon. A classic example of doing this
         | correctly is core::slice::IterMut, and the slice.iter_mut()
         | method.
         | 
         | A short illustration (note how only one of the Foos creates a
         | compile time error): https://play.rust-
         | lang.org/?version=stable&mode=debug&editio...
         | 
         | A short explanation of what you're telling the compiler with
         | PhantomData (without accounting for the new method/ constraints
         | on creation) is that Foo appears to have a &'a T field, for the
         | purposes of the outside world doing type and borrow checking on
         | Foo structs and their type parameters. That does two things:
         | 
         | (1) Due to implied lifetime bounds, Foo's generics also
         | automatically become struct Foo<'a, T: 'a>, so this ensures T
         | outlives 'a. That will prevent you accidentally making
         | Foo<'static, &'b str>.
         | 
         | (2) You don't have an unused lifetime, which is an error. We
         | always wanted Foo to have a lifetime. So this enables you to do
         | that at all.
         | 
         | Edit: and (3) it can change the variance of the lifetimes and
         | type parameters. That doesn't happen here.
        
           | woodruffw wrote:
           | Genuinely asking: what about this is different from what I
           | said in the original comment? It's possible that I'm using
           | the wrong words, but my understanding of Rust's ownership
           | semantics is that "owns a reference" and "borrows the
           | underlying value" are equivalent statements.
        
             | cormacrelf wrote:
             | You can have a PhantomData without a bar field. You could
             | have baz and qux fields as well. The compiler does not know
             | the lifetime in the phantom data is connected to any other
             | field whatsoever. It doesn't look through the other fields
             | searching for *const T. It's just a lifetime and is
             | connected to T, not any particular pointer to T. The bar
             | field happens to have a T in it, so the T: 'a constraint
             | does apply to what you store inside the pointer, but the
             | *const T is lifetime-erased as it relates to the pointer,
             | so you have to ensure that the lifetime of the *const T
             | matches up with the lifetime your Foo type claims it has
             | (in its documentation). You must do that manually by
             | constraining the struct creation and field access. You
             | would also typically have a safe method to turn bar back
             | into a borrow in some way (like IterMut::next does) and
             | when you do, you will want to be sure that the pointer is
             | actually valid for the lifetime you say it is.*
        
               | woodruffw wrote:
               | Thanks for the explanation! I can see how my nitpick
               | wasn't right then, either. I'm going to make an edit
               | noting that.
        
       | atemerev wrote:
       | Yes, most of my code deals with graph-like data structures. It is
       | _really_ hard to understand how to write this in Rust. Just
       | doesn't fit in my head.
        
       | 1980phipsi wrote:
       | The one about exclusive references is good.
        
       | friedman23 wrote:
       | I'll share an architectural pattern from Rust that is pretty
       | ubiquitous but not really mentioned in the official books.
       | 
       | Skip the whole weak rc nonsense and jump directly to using an
       | allocator (like slotmap) when dealing with cyclic data
       | structures. Wrap the allocator in a a struct to allow for easy
       | access and all the difficulty from rust cyclic datastructures
       | disappears.
        
       | anderskaseorg wrote:
       | > _With the code above, Rust does not know whether we want to
       | clone Arc or the actual inner value, so the code above will
       | fail._
       | 
       | This is incorrect. The code works fine as written; Rust will let
       | you write .clone() and it will clone the Arc (without cloning the
       | inner value). More generally, methods on a wrapper type are
       | searched before methods found via auto-deref. It's often
       | considered better style to write Arc::clone(...), but that's for
       | human readability, not the compiler. There's a Clippy lint for it
       | (https://rust-lang.github.io/rust-clippy/master/#clone_on_ref...)
       | but it's turned off by default.
        
         | antman wrote:
         | I read this 3-4 times and realized why people stick to python
         | though
        
           | waiseristy wrote:
           | RE: Any article about the GIL
        
           | [deleted]
        
           | anderskaseorg wrote:
           | Because, what, Python doesn't have any language details
           | related to method lookup that might require a sentence or two
           | of technical explanation? __getattr__, __getattribute__,
           | __get__, base classes, metaclasses, __mro__, __mro_entries__,
           | __dict__, __slots__, __call__, bound and unbound methods--
           | none of this has ever confused someone looking at it for the
           | first 3-4 times?
           | 
           | I assume you're going to reply that you don't need to think
           | about all this when writing most Python. And that's true of
           | most Rust too. Don't get me wrong, Rust does have some unique
           | complexity that Rust programmers need to learn in exchange
           | for its unique safety guarantees--but method lookup isn't
           | where the complexity is.
        
           | spoiler wrote:
           | Python is not immune to this (for different reasons
           | though)... If you want to be snide online, make sure you're
           | correct first :)
        
           | Yoric wrote:
           | Certainly, many applications don't need this kind of
           | complexity.
           | 
           | But if you're working on one who does, you're typically glad
           | to use Rust :)
        
           | nequo wrote:
           | Yeah. Speed and memory efficiency don't come for free. Use
           | Rust when you benefit enough to compensate for the cost of
           | grokking the plumbing.
        
             | Gigachad wrote:
             | Arc is a low level tool for writing multi threaded
             | programs. If it scares you back to Python you could also
             | just write single threaded Rust.
        
             | atemerev wrote:
             | There are many simpler languages with speed and memory
             | efficiency (Nim, Crystal and the like). Rust somehow became
             | even more complex than C++; what an achievement (not).
        
               | ben-schaaf wrote:
               | > Rust somehow became even more complex than C++
               | 
               | I use C++ professionally and the small subset of it that
               | I've learnt is already way more complicated than rust.
        
               | nequo wrote:
               | Two things that might help Rust a lot despite the
               | complexity are the tooling and the ecosystem. Cargo is
               | good, the compiler is extremely helpful, and there are a
               | lot of crates to build on for all sorts of tasks.
               | 
               | For example, if I need to use simulated annealing to
               | solve an optimization problem, there already exist
               | libraries that implement that algorithm well.[1]
               | Unfortunately, the Haskell library for this seems to be
               | unmaintained[2] and so does the OCaml library that I can
               | find.[3] Similarly, Agda, Idris, and Lean 4 all seem like
               | great languages. But not having libraries for one's tasks
               | is a big obstacle to adoption.
               | 
               | Nim looks very promising. (Surprisingly so to me.)
               | Hopefully they will succeed at gaining wider recognition
               | and growing a healthy ecosystem.
               | 
               | [1] E.g., https://github.com/argmin-rs/argmin
               | 
               | [2] https://hackage.haskell.org/package/hmatrix-
               | gsl-0.19.0.1 was released in 2018. (Although there are
               | newer commits in the GitHub repo,
               | https://github.com/haskell-numerics/hmatrix. Not too sure
               | what is going on.)
               | 
               | [3] https://github.com/khigia/ocaml-anneal
        
               | chowells wrote:
               | Fwiw, hmatrix-gsl works trivially and painlessly. Why
               | does a working library need changes?
        
               | nequo wrote:
               | Good to know that hmatrix-gsl works too. In this case, I
               | went down the Rust route instead of the Haskell one. I
               | use Haskell too, just not for simulated annealing.
               | 
               | My main domain is scientific computing and I get nervous
               | about the prospect of not being able to run my code 5 or
               | 10 years down the road (somewhat above the typical
               | lifespan of a project that ends up published in my
               | discipline). GHC gets updates that sometimes require
               | library maintainers to update their libraries. Here is a
               | list of a few:
               | 
               | https://github.com/fumieval/Haskell-breaking-changes
               | 
               | Rust promises no breaking changes in the stable part of
               | the language. Hopefully this promise will hold up.
        
           | friedman23 wrote:
           | The complexity exposed by Rust is overwhelming for people
           | that haven't done any low level programming.
           | 
           | It's not just lifetimes, a basic Rust function can expose the
           | ideas of exclusive and non-exclusive ownership, traits,
           | generics with and without trait bounds, algebraic data types,
           | closures, closures that move, async, async blocks, smart
           | pointers, macros, trait objects, etc etc.
           | 
           | There is absolutely a learning curve and it makes me
           | understand why Golang is the way it is. The language authors
           | of Golang saw the business needs of Google, realized that
           | they need to get domain experts that are not programming
           | savants to write code and created a language that would allow
           | the largest number of non programmer domain experts be
           | productive coders.
           | 
           | That being said, the Golang developers went too far and let
           | too many of their biased ideas for what is "simple" infect
           | the language. In what way is it simple for a language to not
           | be null safe. I have a bunch of other complaints too.
           | 
           | But anyway, I don't think Rust is going to be a revolution in
           | writing software unfortunately. I don't think enough people
           | will be able to learn it for companies to be able to hire for
           | Rust positions.
        
             | shortcake27 wrote:
             | Rust keeps getting hyped so I thought I should give it a
             | go. I've been writing JS/TS, Ruby, PHP, Python for 10+
             | years.
             | 
             | It was a real struggle, and I wasn't enjoying it at all.
             | "Programming Savant" is a great way to put it because all
             | the people I know who like Rust also happen to be the
             | smartest engineers. In the end I gave up because I wasn't
             | getting any value. I was also trying to find a Rails
             | equivalent, tried Actix and I didn't like that either.
        
               | shepherdjerred wrote:
               | Rust is a good replacement for C/C++. It's not a good
               | replacement for the languages you listed.
        
               | Gigachad wrote:
               | Disagree, I've used it to replace Ruby at work. I haven't
               | had any criticisms of the language itself, only the
               | library ecosystem and community knowledge make it
               | difficult. I spent a lot of time working out what the
               | right library is or working without examples for some
               | libraries. In the end the finished product is as easy to
               | work on as our Ruby code once the patterns are
               | established and libraries imported.
        
               | kenhwang wrote:
               | Rust is a decent replacement for any static compiled
               | language too. It's honestly a breath of fresh air if you
               | actually need to use parallelism and had to experience
               | that with other static languages.
               | 
               | Even my peers that transitioned from ruby to rust had a
               | pretty smooth time since ruby's functional-ish,
               | enumerable dependence, and blocks style coding lends
               | itself well to rust's idioms.
               | 
               | But if you've only ever stayed in iterative dynamic
               | language land and don't remember university coursework on
               | programming languages, compilers, or operating systems,
               | yes, you'll probably be in for a world of pain and
               | frustration.
        
               | satvikpendem wrote:
               | Disagree, I have a web server in Actix that I would have
               | written in TypeScript and Node, or Python and Django, or
               | Ruby and Rails, and the Rust version is similarly high
               | level but far faster and more efficient. I recommend
               | people read Zero To Rust In Production if they want to
               | build a web API in Rust.
               | 
               | https://zero2prod.com
        
               | renewiltord wrote:
               | I had the opposite experience. I wanted a quick http
               | server that would proxy some requests where I couldn't
               | control the client and the real servers. I hadn't written
               | either language at the time, but I had it in seconds in
               | Golang and fully functional and I used actix and halfway
               | through the dependencies disappeared and then afterwards
               | it was still a hassle even with help from people on the
               | subreddit and discord.
               | 
               | Reduced to its barebones, the problem is:
               | 
               | 1. Expose a HTTP server
               | 
               | 2. Make n HTTP requests
               | 
               | 3. Combine their output
               | 
               | 4. Return the result
               | 
               | But I was definitely expecting to have an outcome in a
               | few hours. This might be a language that it takes more
               | than that time for me to be proficient enough to use
               | async code in.
               | 
               | Here's an example from my friend, almost exactly my
               | problem and what was suggested: https://old.reddit.com/r/
               | rust/comments/kvnq36/does_anyone_kn...
        
               | satvikpendem wrote:
               | Not sure what you mean by dependencies disappearing.
               | 
               | > _This might be a language that it takes more than that
               | time for me to be proficient enough to use async code
               | in._
               | 
               | Yes, it does take more time to learn the language, but
               | once you do, it's pretty straightforward. Rust is a
               | different way of thinking (a Meta Language dialect in C
               | clothing, as I like to call it; it looks like C but it's
               | definitely not the same semantically) while Go is similar
               | enough to C-based languages and web based languages like
               | Javascript that it's relatively easy to get started with.
               | 
               | It's much the same as learning Haskell or APL, the entire
               | paradigm will be different and can't be picked up in a
               | few hours. However, if you already have functional or
               | array programming experience, Haskell and APL will seem
               | straightforward. It is because you have a lot of
               | C-language or JS based experience that Go was easy for
               | you.
        
               | tayo42 wrote:
               | > because all the people I know who like Rust also happen
               | to be the smartest engineers.
               | 
               | I wish people didnt say or think things like this. a lot
               | of the times people get into these complex things and
               | talk over people to make them selves look smart. or make
               | things look more complicated then they really are to
               | improve their image. i ran into this a lot when dealing
               | with rust. if you can't do complex things in a clear and
               | simple way, your just regular.
        
             | shepherdjerred wrote:
             | Go and Rust solve different problems. Rust is a very
             | complicated language, but it's a much safer language for
             | systems programming. I don't think that there is a better
             | language for systems programming.
             | 
             | Go is probably the most widely used 'simple' language, and
             | it's primarily used for applications. You can find some
             | examples of using Go for systems-like programming, but
             | that's not where it shines.
        
           | antonvs wrote:
           | Python is not even remotely an option for the sorts of
           | programs Rust is intended for.
        
           | vvillena wrote:
           | Those types are wrappers that keep a reference to some data.
           | In any language, it's common to encounter issues when people
           | confuse copying the underlying data with copying just the
           | container around the data and keep pointing to the same
           | thing.
           | 
           | In Python, a list of lists has the same behavior.
           | l = [ [ 1, 2, 3 ] ]         l2 = l.copy()
           | l[0][0] = 99              print(l)  // [[99,2,3]]
           | print(l2) // [[99,2,3]]
        
             | itronitron wrote:
             | there was similar confusion regarding pass by reference and
             | pass by value when Java first came out.
        
           | Shish2k wrote:
           | It's basically as complicated as "If a subclass and a parent
           | class both define a `bar()` method, and you have an instance
           | of the subclass named `foo`, calling `foo.bar()` will call
           | the subclass version"
           | 
           | I actually find that simpler than some of the spaghettis you
           | can create with Python's multiple inheritance :P
        
         | efnx wrote:
         | The types will be different depending on which operation is
         | used to clone so I wouldn't really worry about this one too
         | much.
        
       | badrequest wrote:
       | I love how Rust has so many footguns that you need to read a
       | dozen blogs from no less than a month ago to avoid utter
       | confusion.
        
         | friedman23 wrote:
         | I consider footguns to be things that cause me to waste a ton
         | of effort and build architecturally unsound code. The things in
         | the article are actually just basic language concepts you need
         | to understand to be productive.
         | 
         | So to use the foot gun analogy, if you don't know the stuff in
         | the article you wont even be able to pull the trigger.
         | 
         | An actual footgun is something like monkeypatching in python or
         | ruby. Or running for_each with an async callback in javascript.
        
         | beoberha wrote:
         | Quite the opposite. Rust won't even let you pull the trigger.
         | C++ on the other hand is very much littered with footguns.
        
         | coder543 wrote:
         | Now _that 's_ a strawman if I've ever seen one.
        
         | anonymous_sorry wrote:
         | Footguns is the exactly what rust doesn't have.
         | 
         | Some might criticise it for forcing you through a complex
         | process to acquire a firearms license, selling you gun with an
         | efficient safety and then insisting that you wear extremely
         | heavily armoured shoes.
        
       | cassepipe wrote:
       | One thing I wish Rust and C++ had and that I have only seen in
       | Carbon is pass-by-reference by default + an explicit syntax to
       | pass by copy + for rust, some syntax to mark that we are passing
       | a mutable/exclusive reference.
        
       | puffoflogic wrote:
       | Author has confused the drop functions. `Drop::drop` and
       | `mem::drop` have nothing to do with each other.
        
         | woodruffw wrote:
         | Hm? `mem::drop` is defined in terms of `Drop`[1].
         | 
         | Are you thinking of `mem::forget`?
         | 
         | [1]: https://doc.rust-lang.org/std/mem/fn.drop.html
        
           | coder543 wrote:
           | The complete function definition is provided in the
           | documentation there, and it isn't defined in terms of Drop.
           | It's just a single line function with an empty body.
           | 
           | In fact, mem::drop will accept any value, whether it
           | implements Drop or not.
           | 
           | The author of the article is definitely quite confused about
           | Drop vs mem::drop. mem::drop is _not_ an implementation of
           | Drop.
        
             | woodruffw wrote:
             | Oh, I see what you mean: it does look like they've confused
             | `mem::drop` with an implementation of `Drop`.
             | 
             | > In fact, mem::drop will accept any value, whether it
             | implements Drop or not.
             | 
             | This doesn't mean that it isn't defined in terms of Drop,
             | because there is no such thing as !Drop in Rust. Even
             | things that are Copy are implicitly Drop, it's just that
             | the copy is dropped instead of the value.
        
               | steveklabnik wrote:
               | > Even things that are Copy are implicitly Drop, it's
               | just that the copy is dropped instead of the value.
               | 
               | While that mental model could make sense in an abstract
               | way, it's not literally true. Copy types are forbidden to
               | implement Drop.                   fn takes_drop<T:
               | Drop>(t: T) {             todo!()         }
               | fn main() {             takes_drop(5i32);         }
               | 
               | gives                   error[E0277]: the trait bound
               | `i32: Drop` is not satisfied          -->
               | src/main.rs:6:20           |         6 |
               | takes_drop(5i32);           |         ---------- ^^^^ the
               | trait `Drop` is not implemented for `i32`           |
               | |           |         required by a bound introduced by
               | this call           |         note: required by a bound
               | in `takes_drop`          --> src/main.rs:1:22           |
               | 1 |     fn takes_drop<T: Drop>(t: T) {           |
               | ^^^^ required by this bound in `takes_drop`
        
               | woodruffw wrote:
               | Thanks for the example! Yeah, I'm realizing my framing
               | (around the Drop trait, and not "droppability" as a
               | concept) was incorrect.
               | 
               | Would it be more accurate to say that Rust guarantees the
               | droppability of owned values? I know there's a guarantee
               | that &'static items never have their underlying value
               | dropped, but that works because you can never actually
               | hold the static value itself, only its reference.
        
               | steveklabnik wrote:
               | > Would it be more accurate to say that Rust guarantees
               | the droppability of owned values?
               | 
               | I'm not really sure, exactly, since "droppability" isn't
               | really a thing that's talked about, because as you're
               | sort of getting at here, it's universal, and therefore
               | not really an interesting concept.
               | 
               | > I know there's a guarantee that &'static items never
               | have their underlying value dropped,
               | 
               | Even this is sort of... not correct. Consider this
               | program:                 #[derive(Debug)]       struct
               | Boom;              impl Drop for Boom {           fn
               | drop(&mut self) {               println!("BOOM");
               | }       }              use std::mem;              static
               | mut FOO: Boom = Boom;              fn main() {
               | let mut s = Boom;                      unsafe {
               | dbg!(&FOO);                          mem::swap(&mut FOO,
               | &mut s);                          dbg!(&FOO);           }
               | }
               | 
               | This will print BOOM, as the initial Boom is swapped out
               | from behind the reference and then dropped.
        
               | coder543 wrote:
               | I think your second paragraph is a misinterpretation of
               | how Rust works.
               | 
               | Everything isn't implicitly Drop. Drop is an explicit
               | cleanup mechanism that types can opt into.
               | 
               | If it helps you to think of it conceptually as everything
               | having an implicit no-op Drop, then I guess that's fine,
               | but that's not what is happening.
               | 
               | There is an automatic Drop "glue code" for types that
               | contain types that implement Drop, so that those will get
               | called, of course. But `i32` does not have Drop, at all.
               | 
               | > Even things that are Copy are implicitly Drop, it's
               | just that the copy is dropped instead of the value.
               | 
               | You cannot implement Drop on a Copy type, so Drop
               | literally never gets called on Copy types. You can't put
               | non-Copy types inside a Copy type, so there isn't even
               | Drop glue code. And no, it isn't implicitly Drop at all!
               | And it has nothing to do with a copy being dropped
               | instead of the original value. Drop isn't a universal
               | trait.
               | 
               | I also seem to remember in the early post-1.0 days that
               | whether a type implemented Drop or not would
               | significantly impact lifetime analysis, requiring some
               | occasionally obtuse workarounds. Rust lifetime analysis
               | accepts many more correct solutions these days, and it
               | has been awhile since I wrote a lot of Rust code, so I
               | don't recall how it is these days.
        
               | woodruffw wrote:
               | > If it helps you to think of it conceptually as
               | everything having an implicit no-op Drop, then I guess
               | that's fine, but that's not what is happening in the
               | generated code.
               | 
               | I understand that types that don't implement Drop do not
               | _literally_ have an implicit `Drop` trait implemented for
               | them by the compiler.
               | 
               | What I meant is that there is no "undroppable" type in
               | Rust: the best you can do is make the type panic in a
               | custom Drop implementation, but _any_ function that takes
               | ownership of a value is effectively described as
               | forwarding, dropping, or forgetting that value based on
               | the lifetime /type of its return. In other words,
               | `mem::forget` can _only_ be defined in terms of Drop (or
               | default drop behavior for a type) in terms of ownership
               | semantics, because its signature admits no other
               | possibilities.
        
               | coder543 wrote:
               | > In other words, `mem::forget` can only be defined in
               | terms of Drop (or default drop behavior for a type) in
               | terms of ownership semantics, because its signature
               | admits no other possibilities.
               | 
               | But again, Drop is a destructor trait. It might be
               | confusing that this shares a name with the concept of
               | "dropping" in Rust, which is when a value goes out of
               | scope, but they're not the same thing. Not every value
               | has Drop, and mem::drop doesn't just work for values that
               | are Drop. It is not defined in terms of Drop, but just
               | Rust's ownership semantics.
               | 
               | In fact, you can define a `drop` function that _only_
               | accepts Drop types: https://play.rust-
               | lang.org/?version=stable&mode=debug&editio...
               | 
               | Although I am disappointed that the automatically
               | generated Drop glue doesn't "count" for this purpose, and
               | there isn't a higher level Drop trait, so this isn't a
               | fully general solution.
               | 
               | I also don't know where the concept of "undroppable" came
               | from for this conversation. Taken literally, that would
               | mean that the compiler would _emit an error_ any time a
               | value of that type would need to be dropped, so those
               | values could only exist in functions that either return
               | them or return `!`, or as global static values. I never
               | suggested that was a possibility, and Rust does not
               | support types that are undroppable, but it does support
               | types that are not Drop.
        
               | woodruffw wrote:
               | Thanks for the explanation! Yeah, I'm realizing that I'm
               | using these terms ambiguously: I do understand the
               | difference between dropping and Drop, but I tend to think
               | (incorrectly!) of the latter as an "implementation" of
               | the former, when it really isn't.
        
       ___________________________________________________________________
       (page generated 2023-01-18 23:01 UTC)