8 ms·
Fighting Rust's Expressive Type System
- doonesbury 6y agoAuthor writes: ----- match item_option { Some(item) => { //Do something with resource println!("{}", item); } None => {} --------- Wouldn't this be better with design by contract by having first() enforce length>0 then turning off assertions if desired for release code? Also OP should have mentioned not matching none is an error otherwise there's no point
- ezekiel68 6y agoWouldn't this be better with design by contract... I don't believe so. No plans survive contact with "the enemy", and the enemy here is input that your system may not have control over (such as processing streaming data from an external source). If we simply turn off the check for release builds, this case would halt the system rather than provide an option to log the offending input contextual information to an error channel and perhaps ignore or recover from the situation (as appropriate).
- masklinn 6y ago> Wouldn't this be better with design by contract by having first() enforce length>0 then turning off assertions if desired for release code? I would say very much not, but maybe I'm misunderstanding what you mean, or maybe you're misunderstanding the language? What I'm getting from your suggestion is pub fn first(&self) -> &T { debug_assert!(self.len() > 0); unsafe { &* self.as_ptr() } } which is a straight UB in "safe Rust" as soon as you invoke it on an empty slice (or vec), which is completely verboten. What you could do is have a separate `NonEmpty` vector either by construction or by assertion, for which `first` wouldn't be failible (but other operations would be). > Also OP should have mentioned not matching none is an error otherwise there's no point That actually is irrelevant to the story OP is telling. They're talking about null-safety, match-completeness is an orthogonal concern (and one they don't really care for here, they just want to operate on a present value).
- aliceryhl 6y agoMaybe I misunderstand what you meant, but by having `first()` return an `Option`, you can verify at compile-time that the caller handles the empty case, whereas an assertion would not detect the issue until runtime. If the caller wants to kill the program when its empty, they can just call `.unwrap()` on the option to explicitly opt-in to a runtime check.
- centimeter 6y ago> FP's conceit is trading performance for correctness. This isn't even remotely the case. The most popular functional languages are substantially faster than the most popular imperative languages. Haskell or OCaml both smoke the top 3 imperative languages: Python, Java, JavaScript. The "conceit" is trading the conceptual model most people learn in high school/trade programming classes for correctness, with little to no bearing on performance. > Rust is, as far as I'm aware, the first language that allows you to deploy functional concepts as zero-cost abstractions. This is definitely not the case. You can deploy zero-overhead functional concepts in C++ using template programming, constexprs, etc. Also, "zero-overhead" compared to what? Plenty of functional language have abstractions that can reasonably be described as zero-overhead, like newtypes in Haskell or functorial modules in OCaml. You can even translate the non-monomorphically-recursive subset of Haskell to pure hardware, which is pretty much the definition of "zero overhead". This article reads more like a regurgitation of taglines than original thinking.
- bigdubs 6y agoYou're mixing compiled versus interpreted languages here.
- centimeter 6y agoI'm responding to the article, which made the claim that the tradeoff when using a functional language is performance vs correctness. This is obviously not true in any meaningful capacity, when the vast majority of imperative language deployments are using languages slower than the vast majority of functional language deployments. The fact that the most popular imperative languages are usually JIT compiled (not interpreted, except python) instead of statically compiled is not relevant to the argument.
- mcqueenjordan 6y agoThe claim wasn't "most popular vs. most popular". Why don't you choose "highest performance `FP language` vs highest performance `imperative language`"? Also, `FP language` and `imperative language` aren't well-defined concepts (nor can they be, frankly). These are paradigms that languages have varying degrees of support for. So the entire foundation of the argument is meaningless.
- ezekiel68 6y agoI feel this blog post will be very helpful to people getting into rust for the first time. (or the second, etc time, if it turned them off the first time) I enjoy rust and I write as many of my personal projects in it as I can. Here's a quote that really resonated with my early experience with the language: "However many of these [common rust patterns] are by convention, rather than by language design. Their prevalence depends in part on the authors' stylistic choice. This meant I was more reliant on examples and documentation than I am perhaps accustomed to." I find, even today, it's impossible to check documentation or read "the book", alone, and produce idiomatic rust code. Finally one breaks down and asks on Stack Overflow or one of chat or forum outlets and receives an answer which (almost always accidentally) comes across as, "Oh, didn't you know it?? We always express those ideas in this format here [example code]". I've come to accept and appreciate this way. But it can be baffling and off-putting to newbies who wonder how they should have known.
- dochtman 6y agoCan you give an example of this? I'm very curious. As an experienced Rustacean, I kind of realize that there is a lot to idiomatic style, but at the same time I've assimilated it enough that it's often no longer obvious to me.
- capitol_ 6y agoOne thing of roughly this nature is that a lot of rust code that I have read have their own types for iterators of objects. For example, in the git2 crate there is both: https://docs.rs/git2/0.13.12/git2/struct.Branch.html https://docs.rs/git2/0.13.12/git2/struct.Branch.html and https://docs.rs/git2/0.13.12/git2/struct.Branches.html https://docs.rs/git2/0.13.12/git2/struct.Branches.html Where Branches implements Iterator.
- masklinn 6y agoThat seems pretty natural? The only reason why you don't have this in e.g. python is that iterators are often implemented as generators, so the "own type of iterator" is implicit. But I remember pretty much the same thing from my Java days, if you need to iterate you have to create an iterator type. It's a bit less visible due to type erasure but it's there nonetheless.
- svnpenn 6y agoThis site has something weird going on. If you try to select text, you can only select it in reverse. If you try to select forward, it just selects the entire page. Why do people do stuff like this?
- ziml77 6y agoI'm getting it too, but it's not happening everywhere. It seems like your selection has to include code and the cursor has to sit over whitespace in a limited horizonal range. Nothing looks off about the spans used to color the code so I'm guessing it's something strange about the sidebar.
- jeff-davis 6y ago"Fixing the Billion Dollar Mistake" Option/Maybe types and exhaustive pattern matching was an amazing contribution of ML and similar languages. I would like to be able to do that in SQL, as well. NULL is arguably in much worse shape in SQL.
- tsimionescu 6y agoI think NULL in SQL is very different from NULL in most PLs, and is much better behaved. It is on fact much closer to an Optional than to a 0 pointer. First of all, in SQL NULL is entirely your choice - no column is forced to accept NULL values. Secondly, most SQL functions treat NULL just like mapping over an optional - a < b works just like a.map(|a| {b.map(|b| {a < b})}). The only exception are aggregation functions, that also do the more sa e thing in my opinion, at least for most use cases - AVERAGE() over (a, NULL, b) is a+b/2.
- jeff-davis 6y ago"First of all, in SQL NULL is entirely your choice - no column is forced to accept NULL values." That's not true. Aggregate functions and outer joins produce NULLs even without NULLs in your data. This absolutely can cause weirdness. "The only exception are aggregation functions..." It's more weird and subtle than that. See: http://thoughts.davisjeff.com/2009/08/02/what-is-the-deal-with-nulls/ http://thoughts.davisjeff.com/2009/08/02/what-is-the-deal-wi...
- zodiac 6y agoWell, I think that's analogous to the max function having signature (say) Array<Int> -> Optional<Int>
- jeff-davis 6y agoBut why is the SUM of no elements NULL, but the COUNT-star of no elements zero? Sure, for any given case you can come up with some explanation. But there is no general explanation that covers many cases. And that leads to weirdness and surprises and bugs. Option types are way better and always do what you expect.
- bluk 6y agoFWIW, for the process_item function, I've found writing it like: struct Item { value: u32, } fn process_item(input: Option<Item>) -> Option<Item> { input.map(|mut item| { item.value += 3; item }) } to be more "idiomatic" instead of doing the match on the Option. The methods like "unwrap_or...()", "map...()", "ok...()", "and_then()", etc. on Result/Option are very useful, if not a bit difficult to find the right one to use sometimes. Deeply nested match, "if let Some", etc. code becomes like a straight chain of method calls. In the end, I find that the Result/Option methods shorten code considerably and improve readability. Also, instead of doing an unwrap() on optional values for assertion, sometimes I like: let my_list = vec![1,2,3]; assert_eq!(Some(&my_list[0]), my_list.first()); Mostly just depends though since I don't care too much in tests.
- pirocks 6y agoIMO the issue here is with the question mark operator. If the Rust question mark operator was more like Kotlin's this wouldn't be an issue.
- fanf2 6y agoIt looks like Kotlin uses ? for nullable types, which isn’t really anything to do with Rust’s ? for short-cut error handling. https://kotlinlang.org/docs/reference/keyword-reference.html https://kotlinlang.org/docs/reference/keyword-reference.html You can also use ? with Option: fn process_item(input: Option<Item>) -> Option<Item> { let item = input?; Some(item + 3) }
- masklinn 6y agoI'm guessing they're referring the `?.` "safe calls" operator[0-] which essentially acts as a mix of `map` and `andThen` except only for property accesses and method calls, and can be combined with `let` for a more general map-like behaviour. [0] where many conceive of the operation as `?` being a modifying operator to `.`
- pirocks 6y ago
- davidhyde 6y agoI found the following two resources to be excellent for rust teething issues: Rustlings: A set of exercises to "fix" to get you used to compiler error messages. https://github.com/rust-lang/rustlings https://github.com/rust-lang/rustlings Rust by example: A collection of short runnable examples to get you familiar with the standard library. https://doc.rust-lang.org/stable/rust-by-example/ https://doc.rust-lang.org/stable/rust-by-example/
- deleted 6y ago[deleted]
- 02020202 6y agorust, what a joke. still on a life support, not giving up :D
- MauranKilom 6y agoI really enjoyed this writing! The only thing that I find weird is the description of the leaky abstractions for the pin interface in "traditional" OOP. I understand the general sentiment, but this particular case is a prime example where polymorphy expresses the exact same thing just as well (here in C++): class Pin { public: Pin(std::uint8_t pin) : _pin(pin) {} virtual ~Pin() = default; // base class could also be made abstract here private: std::uint8_t _pin; }; class ReadPin : public Pin { public: ReadPin(std::uint8_t pin) : Pin(pin) {} double read(); }; class WritePin : public Pin { public: WritePin(std::uint8_t pin) : Pin(pin) {} void write(double level); }; You can then end up either with ReadPin sensor_pin = gpio.getReadPin(SENSOR); or, if you for some reason want to keep the chaining, nothing prevents you from having a ReadPin sensor_pin = gpio.getPin(SENSOR).asReadPin(); Maybe it's just not the right kind of example, but I don't really understand how OOP's failure is that you can call write on a ReadPin. The author says: > This kind of compromise happens often in the "kitchen sink" approach of OOP, where a child class might have access to inappropriate data or behaviour. Perhaps that data is relevant to a sibling class, but this is a sign of the abstraction leaking. ...which boils down to "if you violate the Liskov Substitution Principle, you are not using OOP correctly". I concede that this can and occasionally does happen in practice, but it's not like other languages are incapable of following the SOLID. It might just be easier to not do so.
- Jare 6y agoI'm improvising here, but if pins can be switched from read to write (but can only be in one of those at a time), then in Rust you should be able to use the borrow checker to provide additional guarantees. As long as a pin is borrowed by a ReadPin, you can't create a WritePin, and viceversa. As you say, you can use the same approach in C++, but this is one reason why the approach is extra interesting in Rust.