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