[HN Gopher] What's in the Box?
       ___________________________________________________________________
        
       What's in the Box?
        
       Author : milliams
       Score  : 125 points
       Date   : 2021-04-19 12:15 UTC (10 hours ago)
        
 (HTM) web link (fasterthanli.me)
 (TXT) w3m dump (fasterthanli.me)
        
       | jpgleeson wrote:
       | Good read. Amos used to be the most visible/active developer on
       | itch and his twitter was interesting to read. He also has a lot
       | of other pieces like this that really dig into how and why you go
       | wrong when working with something that is unfamiliar to you.
       | 
       | I really enjoyed this one:
       | 
       | https://fasterthanli.me/articles/i-am-a-java-csharp-c-or-cpl...
        
       | [deleted]
        
       | HideousKojima wrote:
       | >As a Java developer, you may be wondering if we're trying to
       | turn numbers into objects (we are not).
       | 
       | Funny that he answered that so early on, my line of thought
       | coming from C# was "Are they trying to turn a value type into a
       | reference type? I thought Rust doesn't have quite the same
       | distinction so is this some weird way of working with the heap
       | instead of the stack?"
        
       | ivoras wrote:
       | Oh, why have they redefined the word "enum" to mean a "union"?
       | enum Result<T, E> {             Ok(T),             Err(E),
       | }
        
         | steveklabnik wrote:
         | They're not only unions, they're closer to discriminated
         | unions. Rust also has unions, with the "union" keyword.
        
         | munificent wrote:
         | It has a fixed enumerated list of cases (here `Ok` and `Err`).
         | Some of those cases may have additional payloads, but I don't
         | see how that makes them any less like an enumerated type.
        
         | Twisol wrote:
         | From my perspective, these really are more like enums than
         | unions. Rust enums cover C-style enums as a limiting case:
         | enum Result {           Ok,           Err,         }
         | 
         | If this was all you got, you'd need to return something like
         | `(status, value, err)` to model fallible functions. This is not
         | unlike Go's `value, err` convention, except that `status` is
         | additionally inlined into `value` as a sentinel `nil`. This
         | does nothing to prevent you from using the `value` if an error
         | has occurred, or vice versa.
         | 
         | Instead, we like to carry data _alongside_ the enum tags:
         | // using named fields instead of tuple structs, for clarity
         | enum Result<T, E> {             Ok {value: T},             Err
         | {err: E},         }
         | 
         | Now we can return a single `Result,` and you can only use
         | `value` if the function succeeded, and you can only use `err`
         | if the function failed.
        
         | yoneda wrote:
         | Logically speaking, Rust's "enums" are neither enums (in the
         | traditional sense) nor unions. They are tagged unions /
         | disjoint unions / variants / coproducts / algebraic data types.
         | We many names for this concept, but "union" is not one of them,
         | because a union allows for a non-empty intersection.
         | 
         | Under the hood, of course, they are implemented with C-style
         | unions with fields sharing memory. But to conflate Rust's enums
         | with how they are implemented is to disregard the extra safety
         | that they provide.
        
         | estebank wrote:
         | To extend on what Steve mentioned, `union` in Rust is a C-style
         | union, but because the type carries no information on what
         | variant the underlying bits represent, accessing them is
         | `unsafe`[1]. On the other hand, `enum`s in Rust are tagged
         | unions, they reserve some data for the typesystem to determine
         | at runtime which variant any given instance corresponds to.
         | Because of that you _can 't_ interpret the T as an E with an
         | enum, but you can with an union.
         | 
         | [1]: https://doc.rust-lang.org/reference/items/unions.html
        
           | ziml77 wrote:
           | And to add onto that, you can also use a Rust enum like a
           | traditional enum:                   enum Foo {
           | OptionA,             OptionB,             OptionC,         }
           | enum Bar {             OptionA = 1,             OptionB = 2,
           | OptionC = 4,         }
        
       | beders wrote:
       | While I like the idea of Rust, having to (re)-learn all these
       | subtleties seems daunting (just look at the table comparing dyn
       | vs. box vs. arc vs. references etc.)
       | 
       | When I moved to Java 1.0, coming from C/C++, I hated the
       | performance loss but happily traded that for a garbage collector.
       | Typically, when the code compiled, it ran.
       | 
       | Now with Rust I'm wondering how much time practitioners spend on
       | analyzing compiler errors (which is a good deal better than
       | analyzing heap dumps with gdb). And do you get to a place where
       | your coding naturally avoids gotchas around borrowing?
        
         | burntsushi wrote:
         | > Now with Rust I'm wondering how much time practitioners spend
         | on analyzing compiler errors
         | 
         | Almost zero. Seriously. Because the rules got internalized for
         | me pretty quickly.
         | 
         | If you asked me, "how much time did you spend when just
         | starting Rust," then it would be a lot more than zero. Enough
         | to noticeably slow me down. But it got down to near-zero pretty
         | quickly. I'd maybe a month or so with ~daily programming.
        
           | stouset wrote:
           | I'll caveat this by saying that to have this kind of
           | experience, it's incredibly important to understand why
           | you're getting these types of errors in the first place.
           | 
           | Rust programs require you to consider some things upfront in
           | your design that you don't have to think about in other
           | languages. If you internalize these requirements, designing
           | programs in Rust can quickly become just as easy and natural
           | as developing in other languages. But it can feel arbitrary
           | and impossible if you just try and force your way forward by
           | `Box`ing and `clone()`ing everything endlessly because it
           | seems to make the annoying compiler errors go away.
           | 
           | If you're the type of engineer who learns a new language and
           | just ends up writing programs in the style of your old
           | language (but with different syntax), Rust is going to feel a
           | lot harder and you may never "get" it. The difficulty curve
           | of Rust is--I think--much steeper than other languages for
           | this type of engineer. You can be productive writing C-style
           | programs in golang. You can be productive writing Java-style
           | programs in Ruby. But Rust is going to fight you much harder
           | than other languages if you try to approach things this way.
           | 
           | If you're the type of engineer who strives to build idiomatic
           | software in whatever language you're using, you'll have a
           | much faster ramp-up to proficiency.
        
         | wyager wrote:
         | Personally, I find the overhead of dealing with rust memory
         | management to be 100% worth it when writing embedded code with
         | no dynamic allocation. It can really help to prevent bad memory
         | management practices and, not so much catch, but rather
         | structurally prevent, bugs. If you're really experienced with
         | embedded C you were probably doing things mostly the same way
         | anyway.
         | 
         | For writing code on an operating system, I'm in the same boat
         | as you; I would rather have GC. Haskell and Rust are
         | spiritually actually pretty similar with the former simplifying
         | memory management and enriching the type system (at the cost of
         | needing to worry about things like memory leaks), and I tend to
         | go to Haskell for non-embedded applications most of the time.
        
         | zozbot234 wrote:
         | > While I like the idea of Rust, having to (re)-learn all these
         | subtleties seems daunting
         | 
         | It's not like Java is any simpler. Rust gives you the
         | equivalent of a GC, except at compile time. And the compiler
         | tells you when you're getting it wrong.
        
           | Twisol wrote:
           | > It's not like Java is any simpler.
           | 
           | Can attest. It's easier to learn the rules of Java the
           | language, but way harder to learn how to write _good Java
           | software_. To some extent, Rust forces you to begin learning
           | both at the same time, which is of course more difficult.
           | 
           | What always surprises me is how much "good Rust software"
           | actually coheres with "good software". I'm not saying that
           | you should write software in any language as though it were
           | Rust -- every language has its own effective praxis. Rather,
           | since Rust forces you to pick up some of the rules of good
           | design as you learn, those rules can _transfer_ to other
           | ecosystems, forming part of a language-agnostic basis of
           | engineering. I think that 's really cool.
           | 
           | A good example is handles over pointers [0]: recognizing that
           | pointers/reference embody two orthogonal concerns, _address_
           | and _capability_ , lets you see how to separate them when it
           | benefits the design. Rust's extremely strict single-ownership
           | model often forces you build separate addressing systems,
           | allowing disparate entities to address and relate to each
           | other in complex patterns, while consolidating all
           | _capability_ to affect those entities into a single ownership
           | root.
           | 
           | The mental model of single-ownership itself is valuable for
           | managing the complexity of a large network of entities, and
           | knowing when you can or should break it in other languages
           | has been really valuable to me.
           | 
           | [0] https://floooh.github.io/2018/06/17/handles-vs-
           | pointers.html
        
           | Kranar wrote:
           | I've heard before the idea that Rust has a "compile time GC"
           | or "static GC", and while I can sympathize with wanting to
           | leverage that term, it already has a fairly well understood
           | meaning and it's not what Rust provides. The only GC that
           | comes built-in to Rust is reference counting via Rc and Arc.
           | 
           | With an actual GC, there is no notion of getting it wrong;
           | the whole point of a GC is that it automates the handling of
           | memory. A useful metaphor for a GC is that it's a system that
           | simulates an infinite amount of memory. With a GC, at least
           | conceptually, you don't allocate and deallocate memory,
           | rather you declare what object you want and it stays alive
           | forever. The GC works behind the scenes to maintain this
           | illusion, although since it's only a simulation there are
           | certain imperfections.
           | 
           | There are some languages that can do this at compile time,
           | such as Mercury and I believe Rust took some inspiration from
           | Mercury... but Rust does not have a compile-time GC the same
           | way that Mercury does.
        
             | zozbot234 wrote:
             | > With an actual GC, there is no notion of getting it
             | wrong; the whole point of a GC is that it automates the
             | handling of memory.
             | 
             | Rust is memory safe, so Rust programs don't go "wrong"
             | either, barring use of unsafe{} or bugs in the underlying
             | implementation.
             | 
             | You only need Rc<> or Arc<> for objects that may have more
             | than a single "owner" controlling their extent. That's a
             | comparatively uncommon case.
        
               | Kranar wrote:
               | You're switching context here and doing so in a way
               | that's fairly pedantic and not really useful.
               | 
               | You mentioned that Rust informs you when you're "getting
               | it wrong" and most people who aren't being pedantic can
               | understand the meaning of that; that there something that
               | would otherwise go wrong if not for a compile time check
               | that prevents it.
               | 
               | In most GC'd languages, there is no notion of something
               | that would have otherwise gone wrong if not for a compile
               | time check (with respect to memory safety). In most GC'd
               | languages, that very concept doesn't exist.
               | 
               | Another way to put it is that there's nothing for a Java
               | compiler to tell a user about "getting it wrong" because
               | there's nothing to get wrong in the first place (with
               | respect to memory safety, since we're being overly
               | pedantic now).
        
       | g_delgado14 wrote:
       | > Because in JavaScript, if something goes wrong, we just throw!
       | 
       | Not if you're using `neverthrow` >:)
       | 
       | https://www.npmjs.com/package/neverthrow
        
       | kryptn wrote:
       | This was a great read! I'm starting to pick up rust and this is
       | helpful.
       | 
       | I've also been watching the Crust of Rust series from Jon
       | Gjengset and enjoying those as well.
       | 
       | https://www.youtube.com/playlist?list=PLqbS7AVVErFiWDOAVrPt7...
        
         | radicalriddler wrote:
         | As someone trying to move into systems programming with rust,
         | thanks for the resources guys!
        
         | iudqnolq wrote:
         | Jon is amazing. I've been watching his videos while doing the
         | dishes. Unfortunately I'm essentially caught up. Any other
         | recommendations?
        
           | yagizdegirmenci wrote:
           | David Pedersen's streams/videos are also great: https://www.y
           | outube.com/channel/UCDmSWx6SK0zCU2NqPJ0VmDQ/vid...
        
             | iudqnolq wrote:
             | Thanks, I'm vaguely familiar with them from the Rustacean
             | Station discord. Somehow their style doesn't quite work for
             | me.
        
           | runevault wrote:
           | Have you looked at Ryan Levick's videos on youtube? He also
           | does a pretty good job covering the language.
           | 
           | edit for link:
           | https://www.youtube.com/channel/UCpeX4D-ArTrsqvhLapAHprQ
        
             | kryptn wrote:
             | Thanks for the recc!
        
             | iudqnolq wrote:
             | Thanks, I'm vaguely familiar with them from the Rustacean
             | Station discord. Somehow their style doesn't quite work for
             | me.
        
       | justinsaccount wrote:
       | This managed to capture a common occurrence when I've tried to
       | learn rust. The compiler spits out a short but seemingly helpful
       | error like this:                 warning: trait objects without
       | an explicit `dyn` are deprecated         help: use `dyn`: `dyn
       | Error`
       | 
       | You make the code change it suggests and then you get a longer
       | error message that says what it just told you to do is invalid.
        
         | estebank wrote:
         | The longer error message would have been emitted regardless of
         | the warning:                   warning: trait objects without
         | an explicit `dyn` are deprecated          --> file.rs:3:13
         | |         3 | fn foo() -> Trait {           |             ^^^^^
         | help: use `dyn`: `dyn Trait`           |           = note:
         | `#[warn(bare_trait_objects)]` on by default
         | error[E0746]: return type cannot have an unboxed trait object
         | --> file.rs:3:13           |         3 | fn foo() -> Trait {
         | |             ^^^^^ doesn't have a size known at compile-time
         | |         help: use some type `T` that is `T: Sized` as the
         | return type if all return paths have the same type           |
         | 3 | fn foo() -> T {           |             ^         help: use
         | `impl Trait` as the return type if all return paths have the
         | same type but you want to expose only the trait in the
         | signature           |         3 | fn foo() -> impl Trait {
         | |             ^^^^^^^^^^         help: use a boxed trait object
         | if all return paths implement trait `Trait`           |
         | 3 | fn foo() -> Box<dyn Trait> {           |
         | ^^^^^^^^^^^^^^
         | 
         | https://play.rust-lang.org/?version=nightly&mode=debug&editi...
         | 
         | Edit: and for some more realistic cases where the compiler can
         | actually look at what you wrote, instead of just giving up
         | because you used `todo!()`:                   warning: trait
         | objects without an explicit `dyn` are deprecated          -->
         | file.rs:7:20           |         7 | fn foo(x: bool) -> Trait {
         | |                    ^^^^^ help: use `dyn`: `dyn Trait`
         | |           = note: `#[warn(bare_trait_objects)]` on by default
         | error[E0308]: `if` and `else` have incompatible types
         | --> file.rs:11:9            |         8  | /     if x {
         | 9  | |         S            | |         - expected because of
         | this         10 | |     } else {         11 | |         D
         | | |         ^ expected struct `S`, found struct `D`         12
         | | |     }            | |_____- `if` and `else` have
         | incompatible types                  error[E0746]: return type
         | cannot have an unboxed trait object          --> file.rs:7:20
         | |         7 | fn foo(x: bool) -> Trait {           |
         | ^^^^^ doesn't have a size known at compile-time           |
         | = note: for information on trait objects, see
         | <https://doc.rust-lang.org/book/ch17-02-trait-
         | objects.html#using-trait-objects-that-allow-for-values-of-
         | different-types>           = note: if all the returned values
         | were of the same type you could use `impl Trait` as the return
         | type           = note: for information on `impl Trait`, see
         | <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-
         | types-that-implement-traits>           = note: you can create a
         | new `enum` with a variant for each returned type         help:
         | return a boxed trait object instead           |         7 | fn
         | foo(x: bool) -> Box<dyn Trait> {         8 |     if x {
         | 9 |         Box::new(S)         10|     } else {         11|
         | Box::new(D)           |
         | 
         | and                   warning: trait objects without an
         | explicit `dyn` are deprecated          --> file.rs:7:20
         | |         7 | fn foo(x: bool) -> Trait {           |
         | ^^^^^ help: use `dyn`: `dyn Trait`           |           =
         | note: `#[warn(bare_trait_objects)]` on by default
         | error[E0746]: return type cannot have an unboxed trait object
         | --> file.rs:7:20           |         7 | fn foo(x: bool) ->
         | Trait {           |                    ^^^^^ doesn't have a
         | size known at compile-time           |           = note: for
         | information on `impl Trait`, see <https://doc.rust-
         | lang.org/book/ch10-02-traits.html#returning-types-that-
         | implement-traits>         help: use `impl Trait` as the return
         | type, as all return paths are of type `S`, which implements
         | `Trait`           |         7 | fn foo(x: bool) -> impl Trait {
         | |                    ^^^^^^^^^^
        
           | justinsaccount wrote:
           | ah yeah I've gotten that                 error[E0308]: `if`
           | and `else` have incompatible types
           | 
           | before.
           | 
           | I was trying to write a simple little program that would
           | output data and optionally reversed (like sort -r). Nothing I
           | could do with iterators would work because it kept trying to
           | tell me that a reverse iterator was not compatible with an
           | iterator. It would work if I hardcoded it, but any code like
           | output = if (reverse) { result.rev() } else { result };
           | 
           | would fail to compile with a completely nonsense error
           | message. I think I eventually got it to work by collecting it
           | into a vector first and reversing that. Haven't really
           | touched rust since. Honestly the compiler might as well just
           | tell me to go fuck myself.
        
             | estebank wrote:
             | > would fail to compile with a completely nonsense error
             | message.
             | 
             | How long ago was this? We spend _a lot_ of time trying to
             | make the error messages easy to follow and informative. If
             | it wasn 't that long ago, I would love to see the exact
             | cases you had trouble with in order to improve them.
             | 
             | > I think I eventually got it to work by collecting it into
             | a vector first and reversing that.
             | 
             | Collecting and reversing is indeed what I would do if I
             | couldn't procure a reversible iterator
             | (DoubleEndedIterator), but if you have one, you can write
             | the code you wanted by spending some cost on a fat pointer
             | by boxing the alternatives[1].
             | 
             | > Haven't really touched rust since. Honestly the compiler
             | might as well just tell me to go fuck myself.
             | 
             | I'm sad to hear that, both because you bounced off (which
             | is understandable) and because that experience goes counter
             | to what we aim for. We dedicate a lot of effort on making
             | errors not only readable, pedagogic and actionable (with
             | varying levels of success). We _really_ don 't want the
             | compiler to come across as antagonistic or patronizing.
             | 
             | [1]: https://play.rust-
             | lang.org/?version=stable&mode=debug&editio...
             | 
             | Edit: For what is worth, type mismatch errors do try to
             | give you appropriate suggestions, but in the case of two
             | types that both implement the same trait (like the one you
             | mention), the compiler _does not_ look for traits that are
             | implemented for both:                   error[E0308]: `if`
             | and `else` have incompatible types           -->
             | file.rs:10:9            |         7  |       let x = if
             | true {            |  _____________-         8  | |
             | A            | |         - expected because of this
             | 9  | |     } else {         10 | |         B            | |
             | ^ expected struct `A`, found struct `B`         11 | |
             | };            | |_____- `if` and `else` have incompatible
             | types
             | 
             | This could be done, but that has the potential to give you
             | _a lot_ of output for all possible traits that could
             | satisfy this code, and in general we would be sending you
             | in the wrong direction. When we can 't be 90% sure that the
             | suggestion is not harmful, we just "say nothing", like in
             | this case. On a real case of the example above, you'd be
             | more likely to want to create a new enum.
        
               | Twisol wrote:
               | Per the sibling conversations: instead of having the
               | compiler tell users about traits that are implemented by
               | both arm types, maybe it would be more productive to tell
               | users how the issue arises from static dispatch
               | considerations?
               | 
               | Maybe if there's an attempt to invoke a method on the
               | result later, like in this case, the compiler could point
               | to it in a "note" and say "Would not be able to determine
               | statically which `impl` of this method to invoke", or
               | something.
               | 
               | Users with experience in languages like Java and Python
               | will have a reasonable expectation that code like this
               | should work, because "they both implement iteration" [0].
               | It's definitely not obvious that dynamic dispatch is
               | _why_ that can work, and how Rust 's static dispatching
               | default impacts idioms like this.
               | 
               | It's singularly frustrating to try to express yourself in
               | a language where familiar idioms just don't carry anymore
               | -- as anyone who's gone from Haskell to Java can attest.
               | I think it's valuable to recognize the idiom and gently
               | teach users why Rust differs.
               | 
               | [0] https://news.ycombinator.com/item?id=26867490
        
               | estebank wrote:
               | The problem for the `if/else` case is that we need to
               | identify the trait that both arms should be coerced to,
               | and that set could potentially be huge, and any
               | suggestion should skip things like std::fmt::Display.
               | That's why the suggestion I showed earlier only works on
               | tail expressions, we can get the type from the return
               | type and work from there, and even account for someone
               | writing `-> Trait` and suggest all the appropriate
               | changes.
               | 
               | I just filed https://github.com/rust-
               | lang/rust/issues/84346 to account for _some_ cases
               | brought up and I completely agree with your last
               | sentence. It is something that others and I have been
               | doing piecemeal for a while now and would encourage you
               | (and anyone else reading this) to file tickets for things
               | like these at https://github.com/rust-lang/rust/issues/
        
               | Twisol wrote:
               | Sure, that makes sense.
               | 
               | In some of the cases in this discussion, isn't the
               | problem the compiler has to solve a little bit simpler?
               | From (a) the indeterminate type of the result and (b) the
               | invocation of a method on that value within the same
               | scope, we should be able to infer the trait that the user
               | is relying on. (Assuming the trait itself is in scope,
               | which, if it isn't, is already an error that hunts for
               | appropriate traits to suggest, I think?)
               | 
               | In some other cases we've discussed here, the actual
               | trait we want is named in the return type, which also
               | fills the role of (b) above. I think this is the case you
               | outlined.
               | 
               | I guess my point is, it seems like we already have enough
               | local information to avoid doing a type-driven trait
               | search. In one case, we have a method and two non-
               | unifiable types, and in the other, we have a trait and
               | two non-unifiable types. I can see how the more general
               | case of "just two non-unifiable types" would be hard, but
               | I'm not sure we have to solve that to cover a meaningful
               | chunk of the problem space.
        
               | athrowaway3z wrote:
               | Boxing isn't necessary. It's just not very pretty.
               | 
               | https://play.rust-
               | lang.org/?version=stable&mode=debug&editio...
        
               | Twisol wrote:
               | That snippet gives a warning: "trait objects without an
               | explicit `dyn` are deprecated". Adding the `dyn` in the
               | right place (`&mut dyn Iterator<Item=i32>`) makes it a
               | little more clear that you're still paying the costs of a
               | fat pointer (half for the trait object pointer, half for
               | the instance pointer), even if the instance is indeed
               | stack-allocated and not heap-allocated ("Box").
               | 
               | If you're returning the `dyn Iterator` from this
               | function, you'd likely need to Box it anyway, since it
               | will go out of scope on return. (Of course, you inlined
               | the function to account for this ;) )
               | 
               | None of which is to say you're wrong; only that different
               | solutions will be appropriate for different cases. "Box"
               | will _probably_ work more consistently in more cases, but
               | it 's definitely valuable to have the stack-allocated
               | approach when it's applicable.
        
               | justinsaccount wrote:
               | yeah.. that's basically what i was trying to do:
               | 
               | https://play.rust-
               | lang.org/?version=stable&mode=debug&editio...
               | 
               | so where it says                  = note: expected type
               | `Rev<std::vec::IntoIter<_>>`                 found struct
               | `std::vec::IntoIter<_>`
               | 
               | it could not be more unhelpful. I know those things are
               | different, however the following "for i in output" works
               | for both of those things individually, so why does it
               | matter that the types are different since they both
               | implement iteration?
        
               | Twisol wrote:
               | I think the key idea here is that Rust consistently uses
               | static dispatch by default. When you invoke some method
               | defined by a trait, it needs to look up _at compile-time_
               | which actual implementation it should dispatch to. Since
               | the if-else expression isn 't returning the same type, it
               | doesn't matter if they both implement Iterator -- Rust
               | still doesn't know which actual type will be produced, so
               | it won't be able to tell which method implementations
               | should actually be dispatched to.
               | 
               | Dynamic dispatch, which solves the issue you faced, needs
               | to be explicitly opted into using the `dyn Trait` syntax,
               | since it introduces a hidden indirection through a fat
               | pointer.
               | 
               | This is definitely a difference from languages like Java
               | or Python, where dynamic dispatch is the default (and
               | sometimes there's no way to explicitly use static
               | dispatch). On the other hand, languages like C and C++
               | also use static dispatch by default, with mechanisms like
               | function pointers or `virtual` to provide dynamic
               | dispatch as needed.
               | 
               | You would very likely have faced a similar problem in
               | C++, had you used `auto x = (..) ? ... : ...`; (If you
               | used `T x = ...; if (...) { x = ... } ...`, you'd have
               | been faced immediately with the issue of "what type
               | should T be" anyway, I think.)
        
               | justinsaccount wrote:
               | That actually makes perfect sense to me.
               | 
               | I ran into that issue trying to port something else to
               | rust, just didn't realize it was this same issue. In go
               | you just define an interface and then make a slice of
               | that interface and put things in it. In rust I ended up
               | having to do                 Vec<Box<dyn Checker>>
               | 
               | I think initially I tried just doing a
               | Vec<Checker>
               | 
               | and when that failed I ended up putting something like
               | "How do I make a vec of an impl in rust" and found a code
               | sample.
               | 
               | That's where the compiler just saying "The types don't
               | match" is not very helpful.
        
               | estebank wrote:
               | If I'm following correctly, you had a situation like the
               | following, right?
               | 
               | https://play.rust-
               | lang.org/?version=stable&mode=debug&editio...
               | error[E0277]: the size for values of type `dyn
               | std::fmt::Display` cannot be known at compilation time
               | --> src/main.rs:5:12             |         5   |     let
               | y: Vec<dyn Display> = x.into_iter().collect();
               | |            ^^^^^^^^^^^^^^^^ doesn't have a size known
               | at compile-time             |             = help: the
               | trait `Sized` is not implemented for `dyn
               | std::fmt::Display`                  error[E0277]: a value
               | of type `Vec<dyn std::fmt::Display>` cannot be built from
               | an iterator over elements of type `{integer}`
               | --> src/main.rs:5:45           |         5 |     let y:
               | Vec<dyn Display> = x.into_iter().collect();           |
               | ^^^^^^^ value of type `Vec<dyn std::fmt::Display>` cannot
               | be built from `std::iter::Iterator<Item={integer}>`
               | |           = help: the trait `FromIterator<{integer}>`
               | is not implemented for `Vec<dyn std::fmt::Display>`
               | error[E0277]: the size for values of type `dyn
               | std::fmt::Display` cannot be known at compilation time
               | --> src/main.rs:5:31             |         5   |     let
               | y: Vec<dyn Display> = x.into_iter().collect();
               | |                               ^^^^^^^^^^^^^^^^^^^^^^^
               | doesn't have a size known at compile-time             |
               | = help: the trait `Sized` is not implemented for `dyn
               | std::fmt::Display`
               | 
               | I can see a bunch of places where we could improve the
               | output that we haven't gotten to yet:
               | 
               | - The third error shouldn't have been emitted in the
               | first place, the first two are more than enough
               | 
               | - The first error has a note, but that note with a little
               | bit of work could be turned into a structured suggestion
               | for boxing or borrowing
               | 
               | - For the second suggestion we could detect _this case in
               | particular_ where the result would be !Sized and also
               | suggest Boxing.
               | 
               | Edit: filed https://github.com/rust-
               | lang/rust/issues/84346
               | 
               | It is also somehow unfortunate that `impl Trait` in
               | locals isn't yet in stable, but once it is it would let
               | you write `let z: Vec<impl Display> =
               | x.into_iter().collect();`, but as you can see here, that
               | doesn't currently work even on nightly:
               | https://play.rust-
               | lang.org/?version=nightly&mode=debug&editi...
        
       | estebank wrote:
       | Preemptive comment: the following output towards the second half:
       | help: function arguments must have a statically known size,
       | borrowed types always have a known size           |         3 |
       | fn f<T>(&t: T)           |         ^
       | 
       | is a bug that already has a PR to fix. It's supposed to point at
       | the T and suggest the following instead:                   help:
       | function arguments must have a statically known size, borrowed
       | types always have a known size           |         3 | fn f<T>(t:
       | &T)           |            ^
        
         | kzrdude wrote:
         | You changed the function signature between your two code
         | snippets, that makes it hard to understand what you mean.
        
           | CBLT wrote:
           | Check out the context from TFA: this is a help message
           | prescribing a change you could make (inserting a &). The
           | signature is different because it's prescribing a different
           | signature to fix the issue.
        
         | fasterthanlime wrote:
         | I've added a link to the PR directly in the article to clear up
         | the confusion!
        
       | munk-a wrote:
       | I'm saying this informatively rather than as a nit-pick but
       | Because Result<T, E> is an enum, that can represent two things
       | 
       | is an incorrect use of that terminology - Result here is a tuple
       | (it might possibly be a union but I hope not - those are really
       | weird). Tuples are a really good data structure to get more
       | familiar with since they do a lot of stuff that may be inobvious
       | from the outside. A lot of folks will equate them to arrays and
       | arrays can usually be used to represent them but a type-safe
       | tuple (or n-ple pronounced EN-pull) is, essentially, the useful
       | part of structs that aren't classes.
       | 
       | If you're not yet familiar with tuples I'd suggest spending some
       | time reading up on them since they're a very strong tool in the
       | developer toolbox.
        
         | steveklabnik wrote:
         | Sorry, but you're not right, at least in the way that Rust uses
         | the terms "enum" and "tuple." In Rust, an enum is a sum type
         | (also called a "discriminated union" in some other languages;
         | we don't use this term for Reasons), and a tuple is a product
         | type (like arrays). Result is an enum.
        
           | munk-a wrote:
           | Oh interesting - I had no idea that Rust called such a thing
           | enum. Wanting to avoid the term discriminated union makes a
           | lot of sense - the untagged union (what you'd get in C for
           | instance) is a train wreck in that it is not a full statement
           | of data. It's pretty hard to talk about tagged unions without
           | people shortening it to unions - there are some alternatives
           | out there like sum type, but I haven't seen them gain much
           | traction linguistically. Within untagged unions the type is
           | generally constrained to a certain extent by compiler checks
           | but the true type of the value is always unknown unless
           | communicated by a separate piece of data. Usually good uses
           | of unions occur in places where that second piece of data is
           | carried along side the first piece (tuple style) in a struct
           | - generally the type should be inferrable from some piece of
           | related data unless you want some serious headaches.
        
             | steveklabnik wrote:
             | It's all good! Yeah, Rust has C-style unions as well,
             | though they're unsafe, and largely for C interop.
             | 
             | Rust's enums give you the full power of putting other data
             | structures inside of them; the variants can hold data of
             | any kind; single values, structs, tuples, and even other
             | enums.
        
               | jkarneges wrote:
               | Coming from C/C++, "enums with data" sound more
               | interesting to me than "unions with a type", even though
               | they describe the same thing. I used C/C++ enums far more
               | often than unions, and often wished I could add extra
               | data to them.
               | 
               | Another thing is that adding data to Rust enums is
               | optional, and so you can have an enum of variants with no
               | data. The union equivalent to that would be a type-only
               | union which sounds kind of odd.
        
       | xbar wrote:
       | That is just a lot of fun.
        
         | hardwaregeek wrote:
         | Agreed. It's a wonderful post that goes into a lot of depth
         | about some fascinating language internals.
        
       ___________________________________________________________________
       (page generated 2021-04-19 23:00 UTC)