6 ms·
Rust for JavaScript Developers – Pattern Matching and Enums
- LibertyBeta 6y agoAnother great crib sheet for JS folks to think about Rust. What I like about this, rather than say the Rust book, is you've given examples that map closely to other language paradigms without ignoring the fact that rust handles them differently.
- pininja 6y agoI appreciate the author’s use of code style to make it easier to read the examples. While JS is usually camelCase they choose to use snake_case for functions like operate_drone. I also appreciate the lesson of converting discriminated unions in JS and to rust - that’s my favorite pattern from the TypeScript community.
- Dowwie 6y agoEnums are my favorite type in Rust. You can do so much with them, and even more using 3rd party crates such as strum [1], allowing you to do things such as iterate over variants and decorate each variant with custom properties. How is this useful? Here's one example: an enum's variants can map to the values of a reference table in your relational database. When you seed your db, iterate over the enum variants and load into the ref table the properties associated with each variant (the properties you associated using strum). When you query values from the reference table, resolve the value to the enum type. You'll never have values in your table/model that are out of sync with your enum in rust. [1] https://github.com/Peternator7/strum https://github.com/Peternator7/strum
- cle 6y agoHaving used enums with large teams across many distributed systems over the years, enums are my least favorite type in that setting, because it's very easy to write an enum that does not tolerate unknown/new values (they usually don't by default, which seems to be the case for Rust as well). This often makes the system brittle to new changes because subsystems have very strict expectations of data schema, even when they don't really need to care about it (e.g. when they're just passing data around). I've seen a lot of attempts on these teams to ensure these mistakes aren't made (special parsing libraries, using default fallback values, etc.), but someone always manages to sneak in an enum somewhere where they shouldn't and then something blows up 6 months later when a new value is introduced. Enthusiastic engineers will spend a lot of time trying to figure out how to add an over-strict enum, because hey, who doesn't want type safety?
- dwaltrip 6y agoRust prevents the program from compiling if a new enum variant was not handled, unless one uses a catch all in the match expression. Did the codebase have an over-usage of catch alls? Or were you using a different language that doesn’t catch enum errors during compilation?
- ori_b 6y agoWhat happens when you have a mix of new and old binaries being incrementally rolled out?
- dwaltrip 6y agoGood point, but isn’t that a more general code compatibility issue that applies to many things beyond just enums?
- Tyr42 6y agoI think the key words here are "distributed system". Just because HEAD has the new enum used everywhere doesn't mean all the deployed code everywhere can handle the new enum value. Imagine you have two servers communicating with enum Packet { Ping, Pong, } Where the semantics are that if you receive a ping, you say pong, and if you see pong, you say ping. Suppose we deploy this out to several servers, with code built on 2020-7-12. Now on 2020-07-13 you commit to head a new enum value `Stats`, which returns a new kind of reply. If you send outgoing requests in the same 2020-07-13 build, you're going to have a bad time, as your fleet is going to start at 100% 2020-07-12 and switch over to 100% 2020-07-13, but during that switch you'll have some 50% 50% split. If a new server sends a request to an old one, you'll have a crash. Instead you have to add receivers in 2020-07-13, but not use it yet. Roll it out, make sure it's past how far you might roll back, then you can deploy a 2020-07-20 build which makes calls using the new enum. If you skip that step, you're going to have a bad time. Maybe you want to instead have a default case handler instead of crashing, but that means each of your enums has to know about "UNKNOWN" and you're deserialization error has to be future proof.
- 6y ago
- jfkebwjsbx 6y agoJavaScript and Rust are so opposite in their goals and design that I wonder why something like this is written...
- oh_boy 6y agoBecause it is easier to learn programming language X when you can compare it to what you know from programming language Y. Articles like this are most likely the first thing I'm looking for to have a kick-start into a new field.
- kohtatsu 6y agoI think you just answered your own question.
- steveklabnik 6y agoWouldn’t that imply that it’s needed more? The more different two things are, the harder it is to get from A to B, and the more a map is useful.
- jfkebwjsbx 6y agoThe question is whether enough people need such a map.
- steveklabnik 6y agoIn that case: yes, we have a lot of JS people coming to Rust. The difference helps explain that too: if you’re going to learn something, learning something that’s very different from what you’re used to means you get more out of it than learning something similar. When I decided I wanted to move away from Ruby, I didn’t even consider getting deeper into Python. Why would I? They’re so similar that the cost/benefit ratio is off, at least for me back then. YMMV of course. Oh and finally, not every bit of writing has to be extremely useful or hit a wide audience. Sometimes people write things because they want to.
- 6y ago
- rat323 6y agoWow stop the Rust spam every day HN.
- styfle 6y agoQuestion for the author: Why does Rust destructuring require the struct name? The example uses: let Person { name, city } = person; This seems a little verbose since I would expect the type system to know that person is of type Person already. Is this because destructuring syntax is trying to be compatible with the pattern matching syntax?
- steveklabnik 6y agoNot the author, but yes, this is because the thing between "let" and "=" is a pattern, the exact same thing used in match, and in other places. For example, function arguments! fn takes_person(p: Person) { fn takes_person(Person {name, city}: Person) { But I wouldn't inherently say that's the reason, it's also because structs are nominally rather than structurally typed.
- estebank 6y agoYou will find pre-RFCs[1][2] in https://internals.rust-lang.org/ https://internals.rust-lang.org/ about this, as well as for anonymous structs[3]. It isn't in the language yet due to a combination of subtle issues that need to be considered before a final design can be reached, trying to keep the grammar clean, accepting that explicitness and verboseness now is better than hastily moving towards something that seems better but that can introduce technical, documentation or understandability issues. > Is this because destructuring syntax is trying to be compatible with the pattern matching syntax? It's not trying to be compatible, it is pattern matching syntax. If the language changed to, for example, allow the following let _ { name, city } = person; Would also allow match opt_person { Some(_ { name, city }) => {} None => {} } [1]: https://internals.rust-lang.org/t/pre-rfc-struct-constructor-name-inference/10960 https://internals.rust-lang.org/t/pre-rfc-struct-constructor... [2]: https://internals.rust-lang.org/t/pre-rfc-implied-type-for-patterns-in-argument-position/12391 https://internals.rust-lang.org/t/pre-rfc-implied-type-for-p... [3]: https://internals.rust-lang.org/t/pre-rfc-anonymous-struct-and-union-types/3894 https://internals.rust-lang.org/t/pre-rfc-anonymous-struct-a...
- jsf01 6y agoWhat is the reason for using different syntax for Move and TakePhoto from the Operation enum in the penultimate example? Is there a name for each of those enum member types?
- estebank 6y agoEnums have 3 different variant types: enum Foo { StructVariant { named: i32, fields: String, with: (), types: usize }, TupleVariant(i32, String, usize), // only types and position information UnitVariant, // just a tag, no other days being carried } The struct variant is useful when you are carrying enough different fields that you want to name them.
- nickx720 6y agoGreat stuff. Thank you :)