12 ms·
What's in the Box?
- xbar 5y agoThat is just a lot of fun.
- hardwaregeek 5y agoAgreed. It's a wonderful post that goes into a lot of depth about some fascinating language internals.
- jpgleeson 5y agoGood 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-cplusplus-dev-time-to-do-some-rust https://fasterthanli.me/articles/i-am-a-java-csharp-c-or-cpl...
- estebank 5y agoPreemptive 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) | ^
- fasterthanlime 5y agoI've added a link to the PR directly in the article to clear up the confusion!
- kzrdude 5y agoYou changed the function signature between your two code snippets, that makes it hard to understand what you mean.
- CBLT 5y agoCheck 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.
- kryptn 5y agoThis 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=PLqbS7AVVErFiWDOAVrPt7aYmnuuOLYvOa https://www.youtube.com/playlist?list=PLqbS7AVVErFiWDOAVrPt7...
- iudqnolq 5y agoJon is amazing. I've been watching his videos while doing the dishes. Unfortunately I'm essentially caught up. Any other recommendations?
- runevault 5y agoHave 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 https://www.youtube.com/channel/UCpeX4D-ArTrsqvhLapAHprQ
- yagizdegirmenci 5y agoDavid Pedersen's streams/videos are also great: https://www.youtube.com/channel/UCDmSWx6SK0zCU2NqPJ0VmDQ/videos https://www.youtube.com/channel/UCDmSWx6SK0zCU2NqPJ0VmDQ/vid...
- iudqnolq 5y agoThanks, I'm vaguely familiar with them from the Rustacean Station discord. Somehow their style doesn't quite work for me.
- HideousKojima 5y ago>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?"
- brundolf 5y agoYou're basically correct. Everything in Rust is value-typed by default. A Box<> puts a value type on the heap (makes it a reference type). The difference from a normal reference type is that a Box is owned (in Rust ownership terms), where other references aren't. Both normal references and boxes can be typed as dyn Trait which means they dynamic-dispatch method calls based on some trait, instead of statically dispatching them to the concrete type.
- deleted 5y ago[deleted]
- beders 5y agoWhile 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?
- wyager 5y agoPersonally, 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.
- burntsushi 5y ago> 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 5y agoI'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.
- justinsaccount 5y agoThis 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 5y agoThe 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&edition=2018&gist=304a1a1495275024b63676e605c197de 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 { | ^^^^^^^^^^
- munk-a 5y agoI'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 5y agoSorry, 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 5y agoOh 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 5y agoIt'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.
- g_delgado14 5y ago> Because in JavaScript, if something goes wrong, we just throw! Not if you're using `neverthrow` >:) https://www.npmjs.com/package/neverthrow https://www.npmjs.com/package/neverthrow
- valand 5y agohttps://github.com/gcanti/fp-ts https://github.com/gcanti/fp-ts https://github.com/gcanti/io-ts https://github.com/gcanti/io-ts If you're using TS these are pretty much footgun eliminator.
- ivoras 5y agoOh, why have they redefined the word "enum" to mean a "union"? enum Result<T, E> { Ok(T), Err(E), }
- steveklabnik 5y agoThey're not only unions, they're closer to discriminated unions. Rust also has unions, with the "union" keyword.
- estebank 5y agoTo 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 https://doc.rust-lang.org/reference/items/unions.html
- ziml77 5y agoAnd 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, }
- scoutt 5y ago> to determine at runtime I didn't know this. How is determined? Is there any code inserted by the compiler to do this? What's the overhead of using Result<T, E>?
- estebank 5y agoTL;DR: the overhead for enums that don't have an inordinate amount of variants is about 8 bytes, but can be zero in special cases. > What's the overhead of using Result<T, E>? Enough bits (+ padding) to discriminate between all the variants, as any tagged union would, modulo some tricks for niche optimization. For example, Result<i64, u64> is 16 bytes in memory because it needs 8 bytes to represent either a i64 or an u64, plus another 8 bytes to determine whether it is an Ok or an Err. You can think of it as a struct Enum { variant: usize, bytes: [u8; N], } The niche optimization I mentioned applies for things like Option<NonZeroI64>, where NonZeroI64 fits in 8 bytes but the compiler can ensure that the bitpattern corresponding to the 0 value will never be used, so it can use that to differentiate between Some and None. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6c4de0addbabb43ff279af2b8333be56 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- cryptonector 5y agoThis blog post is really difficult to navigate. It's about Box, so... ^Fbox, right? No, because the author uses a directory called "whatbox" and so their examples are littered with "whatbox", so... ok, ^Fbox and match word, but now the section titled "What the heck is a Box?"... says nothing about Box.
- scubbo 5y agoThe moment I read "As a Java developer, you may be wondering if we're trying to turn numbers into objects (we are not)" without an explanation of what we _were_ trying to do, I came to the comments to see whether it was worth persevering. Is there a common name - ideally (since I love them) an eponymous law? - for the precept that, when writing, you shouldn't introduce a question in your reader's mind without at least _acknowledging_ it? Ideally, of course, it should be answered immediately, but sometimes that's impractical. To prevent your reader from having too many "open loops" in their brain, though (shout out to [GTD](https://gettingthingsdone.com/ https://gettingthingsdone.com/)), if the answer isn't essential to what follows (which is not the case here), you should at least reassure the reader that they don't need to know the answer to that question yet.
- cryptonector 5y agoThis. TFA is too long and doesn't show even a glimpse of getting to the point.
- valand 5y ago> Because in JavaScript, if something goes wrong, we just throw! A bit out of topic: somewhat ironic that the JS/TS-based projects in my workplace we ended up using return and Result<T, E>-like struct, backed by TypeScript's union type. Throws is only for never-scenarios and unhandled cases. Rust-like approach has been very successful to squash runtime bugs, especially in big, complex products.
- rattray 5y agoI really like this pattern in TS: const x: Data | Error = await fetchData().catch(e => e); if (x instanceof Error) { return 'too bad' } // proceed with x as Data Basically you add `.catch(e => e)` to any I/O which may fail, and handle the error in an `if` instead of a `try`. Many of the benefits of Go's error handling, but without the awkward extra variable and without a chance of using your result without ensuring it isn't an error.