[HN Gopher] Trivia About Rust Types
       ___________________________________________________________________
        
       Trivia About Rust Types
        
       Author : todsacerdoti
       Score  : 91 points
       Date   : 2022-06-07 10:59 UTC (12 hours ago)
        
 (HTM) web link (www.thecodedmessage.com)
 (TXT) w3m dump (www.thecodedmessage.com)
        
       | gameswithgo wrote:
       | I really enjoy some of the optimization details like when the
       | memory overhead of Option<> can be elided completely. Other
       | languages with Options will actually toss data up on the heap
       | that doesn't need to be because it is in a Option and, or at the
       | very least always take up a byte with the tag.
       | 
       | Details like this let you use the safer and more ergonomic
       | solutions by default rather than having to worry if they are
       | worth the overhead.
        
         | tialaramex wrote:
         | Indeed. Also more broadly Rust's types _do not exist_ at
         | runtime, and so you get the New Type idiom. If we care about
         | the difference between a Row and a Column in the program, we
         | can create distinct Row and Column types, and Rust won 't allow
         | you to use a Row when you need a Column, even if they're both
         | "just integers", yet at runtime there's no cost for this, if
         | the obvious representation of Row and Column is a 32-bit
         | integer that fits nicely in the general purpose registers and
         | takes up 4 bytes of RAM, that's how they're both represented at
         | runtime.
         | 
         | You can do a lot of this in languages like C++ but there are
         | some pernicious limits that Rust didn't have e.g. C++ can't
         | conceive of Zero Size Types.
        
       | RcouF1uZ4gsC wrote:
       | > Did you know that Option<T> implements IntoIterator yielding
       | 0/1 elements, and you can then call Iterator::flatten to make
       | that be 0/n elements if T: IntoIterator?
       | 
       | I thought this was so cool, being able to treat Option like a 0/1
       | collection.
        
         | pornel wrote:
         | Personally I dislike this (Result has it too), because it gives
         | bizarre errors if you forget to unwrap a result:
         | let collection = get_stuff();          for item in collection {
         | }
         | 
         | This can unexpectedly give you one item of iterable `Result`
         | instead of items of the collection if `get_stuff()` returns a
         | `Result<Vec>` or `Option<Vec>`.
         | 
         | Such mistake won't make it to production thanks to strict type
         | checking, but it manifests in surprising and misleading error
         | messages that the `item` doesn't have methods you expect it to
         | have, rather than pointing to the true cause of `get_stuff()`
         | not having error handled.
        
           | tialaramex wrote:
           | I can't say I've ever run into this, but it sounds like you
           | have. You might find there's a common pattern which
           | distinguishes this mistake from typical scenarios where you
           | did something else wrong and the compiler can use that to
           | provide more helpful errors in this case.
           | 
           | Seems like, we do for x in Result-or-Option<T> and T is
           | IntoIterator, and then we treat x as if it is the thing T was
           | an iterator for, rather than T. That's detectable.
           | 
           | So, worth taking some examples of code where you did this,
           | and writing up a report that the compilers team can look at.
           | If you've got some idea what you're doing you might well even
           | find Rust's error reporting code readable enough to dive in
           | and try some fixes yourself.
        
             | Arnavion wrote:
             | I've seen users come asking for help about it on IRC a
             | bunch of times, so it's probably a pretty common typo, at
             | least among beginners. In retrospect it might've been
             | better if there was an explicit conversion to the
             | (Into)Iterator type instead of being impled on
             | Option/Result itself.
             | 
             | https://www.arnavion.dev/blog/2020-04-07-rust-the-
             | unfortunat...
        
           | 3836293648 wrote:
           | I've never run into this because I always write out my
           | `.iter()`s and `.into_iter()`, but this is why I don't think
           | loops should accept `IntoIterator` types, just actual
           | `Iterator`s so you're forced to type it out
        
             | tialaramex wrote:
             | This just adds verbosity - there's a blanket implementation
             | of IntoIterator on Iterator so people would just learn to
             | write
             | 
             | for x in y.into_iter()
             | 
             | and presumably curse 3836293648 who made them do this extra
             | typing for no purpose.
             | 
             | It doesn't, as you seem to imply fix the parent's problem,
             | because both the container they thought they were getting,
             | and the Option or Result they were actually getting,
             | implement IntoIterator so they'll call into_iter() on it.
        
           | afdbcreid wrote:
           | There is a warn-by-default Clippy lint for that:
           | `clippy::for_loops_over_fallibles`. However, being an
           | `IntoIterator` is important for things like flattening and
           | other iterator adapters.
        
       | ridiculous_fish wrote:
       | A minor rant about std::ptr::NonNull: this wraps a *mut T, and
       | there is no equivalent for *const T. So if you want to use the
       | optimization for a pointer-to-const, you must cast away
       | constness, and just be careful to never mutate the pointee. In
       | practice that's exactly what the standard library does.
        
         | Rusky wrote:
         | There's a bit of a sentiment that the `*mut T`/`*const T`
         | distinction is more unhelpful noise than an actually useful
         | feature. They can be freely cast between each other - `*mut T`
         | to an immutable object is totally legal - and thus (much like C
         | and C++'s const) don't really mean anything about the thing
         | they point to. Further, writing through one or converting back
         | to a reference is already `unsafe`, so even using them as a
         | lint doesn't add much.
         | 
         | AIUI that is why NonNull only has one variant- it's just easier
         | to have one kind of pointer. But interestingly, even though
         | `NonNull`'s API is written in terms of `*mut T`, it is
         | _implemented_ in terms of `*const T`: https://github.com/rust-
         | lang/rust/blob/4ca19e09d302a4cbde14f.... This makes it
         | covariant (`NonNull<T>` for `T: 'long` is a subtype of
         | `NonNull<T>` for `T: 'short`), while `*mut T` is invariant,
         | since this is the more common mode, and also the more flexible
         | option that can be restricted back down to invariance when
         | necessary.
         | 
         | So NonNull is sort of a re-setting of the defaults: the unsafe
         | mutability of `*mut T` (since people wind up needing to cast
         | covariant `*const T`s to `*mut T` to write through them a lot
         | anyway), with the covariance of `*const T`.
        
       | joshfee wrote:
       | Have only just started reading the article, but its really
       | annoying that there are no hyperlinks to the referenced
       | types/methods.
       | 
       | > See the debug_* methods on it.
       | 
       | Sure... please point me to them
        
         | DonaldPShimoda wrote:
         | To be fair, these are all excerpts from a Twitter thread where
         | the tweet author was just trying to informally drop some
         | knowledge to people who were asking about the types in question
         | and therefore probably know where to go for the documentation.
         | 
         | Those links would be a great addition to the article, though.
        
         | tialaramex wrote:
         | For example:
         | 
         | https://doc.rust-lang.org/std/fmt/struct.Formatter.html#meth...
         | 
         | The entire Rust standard library documentation (like most
         | documentation for Rust software, it's built with the same
         | tools) has a Search UI at the top.
         | 
         | https://doc.rust-lang.org/std/
        
       | Robin_Message wrote:
       | Interesting that a language that targets embedded (amongst other
       | things) has a default vector allocator for space for 4 entries of
       | objects between 2 and 1024 bytes. I can imagine that being
       | wasteful for code that has a lot of very short lists of non-
       | trivial objects (e.g. something like configuration options).
       | 
       | Although I expect it is overridable in some way but I don't know
       | enough Rust to know how.
        
         | nynx wrote:
         | Vec and allocators aren't usually used for embedded rust since
         | the std library can be disabled. (Though the _core_ library is
         | still accessible)
        
         | gameswithgo wrote:
         | You can specify the capacity on creation:
         | Vec::with_capacity(foo)
         | 
         | There are also alternative vector like types for different use
         | cases, and you can roll your own.
        
           | __s wrote:
           | There's also crates to avoid heap allocations for usually
           | small vectors: tinyvec, smallvec, arrayvec
           | 
           | Note: gameswithgo you seem to be shadow banned. Your comment
           | history looks alright tho. You may want to email
           | hn@ycombinator.com asking to have that fixed _(edit: found
           | the culprithttps://news.ycombinator.com/item?id=30732886)_
        
       | rascul wrote:
       | There's some good information here but I find it slightly
       | annoying that each item starts with "Did you know". Also, some
       | examples would be nice. Descriptions are good but it can take a
       | few minutes for me to visualize in code what is explained.
        
         | sam_bristow wrote:
         | I think both of those issues are because this is a fairly
         | straight forward transcription of the original Twitter thread.
         | 
         | I agree that it would be nice if they'd flesh out some of the
         | items with example code though.
        
           | rascul wrote:
           | Ahh ok. I didn't realize it was a Twitter thread. I guess I
           | didn't actually look at the article title.
        
             | tialaramex wrote:
             | When Jon did the initial tweets, almost immediately non-
             | Twitter users were like "Please transcribe this somewhere
             | else" and there was a Reddit thread, now this is an actual
             | article.
             | 
             | I think Jon just intended it as a bit of fun, and so
             | Twitter felt appropriate. After all if there's important
             | stuff to be communicated about a type it should be in the
             | actual documentation. If you want to learn about types
             | entirely by reading Tweets that seems like a weird and
             | probably bad learning style. See also: learning ballet
             | entirely by reading translated poems about ballet.
        
       | tialaramex wrote:
       | ControlFlow https://doc.rust-
       | lang.org/core/ops/enum.ControlFlow.html is awesome.
       | 
       | Aside from its actual purpose (as part of making ? less magic,
       | described in this trivia list) it's good to agree how to talk
       | about control flow in the type system. If two unrelated pieces of
       | software both care about control flow, in a lot of languages
       | they'd both make up their own ways to signal "keep going" versus
       | "halt". But since core::ops::ControlFlow exists in Rust it makes
       | sense to use that, and so you spend less time writing adaptors.
       | This is one of the main purposes of a standard library (notice
       | this is in _core_ not just _std_ and so it 's available even in a
       | tiny embedded device with no allocator if you want it)
        
       ___________________________________________________________________
       (page generated 2022-06-07 23:02 UTC)