Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Trivia About Rust Types (thecodedmessage.com)
104 points by todsacerdoti on June 7, 2022 | hide | past | favorite | 26 comments


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)


It's neat that there's work to make ? less magic and more general. The postfix ? for working with Option and Result is a really nice syntactic touch in Rust that makes it easy to work with those types in imperative-ish code, without requiring a bunch of map() or and_then() calls.


> 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.


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.


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


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.


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.


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...


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.


Could you file a ticket? rustc is not above inspecting the types at play and special casing Option and Result handling to check for the consequences of iterating on them should be doable.


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.


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.


Ahh ok. I didn't realize it was a Twitter thread. I guess I didn't actually look at the article title.


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.


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.


Vec and allocators aren’t usually used for embedded rust since the std library can be disabled. (Though the _core_ library is still accessible)


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.


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 culprit https://news.ycombinator.com/item?id=30732886)


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.


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`.


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.


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.


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


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.


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/


In my fork of the article, I added documentation links and ordered the sections more sensibly: https://github.com/andrewarchi/compiler-notes/blob/main/rust...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: