5 ms·
I've been writing a parser generator in rust as a way to learn the language, and it's been a mixed journey of being very impressed, and very frustrated. A large
by aethertap 12y ago
I've been writing a parser generator in rust as a way to learn the language, and it's been a mixed journey of being very impressed, and very frustrated. A large part of my frustration comes from what seems like a negative feedback between algebraic data types and the borrow checker. I often find myself with a piece of code that looks clean and simple, using the Option and Result monads to deal with errors in an elegant way, but then I can't compile it because of issues with borrowing. Ultimately, I usually have to break apart the nice structure that feels natural and reimplement it as either a sequence of match or "if x.is_ok()" statements, which just feels... wrong somehow. It becomes tempting to just punt on the error handling and call unwrap() on everything just to get it working for now, which leads to problems later on. It's so close to being incredibly expressive and great for systems programming, but it isn't quite all the way there (at least for me, at this point).
I think that higher-kinded types will probably help a lot with this if they make it into the language, and it's likely that there's a way to get around the errors and get the full benefit from the algebraic data types and monadic functions that I just haven't found yet. The language is enough of a level-up from my C++ days that I'm sure I'll stick to it, even if those things aren't true.
I thought this quote from the article really nailed it regarding my recent experience:
"Expressiveness or elegance is not a goal of Rust. It’s certainly not bad in this regard, just not as wonderful as you may wish if you care it a lot."
- falcolas 12y agoThis is my experience as well. I was experimenting with a Rust implementation of the Whisper storage system, and I got completely stymied while trying to wire up the unit tests. While I could freely pass a file handle into multiple functions, I could not do the same with any standard implementatoins of mock read/write object due to the underlying Vector objects implementing the `drop` method, which disallowed re-use of the object (including simply reading the contents after coming out of the function).
- pcwalton 12y ago> I could not do the same with any standard implementatoins of mock read/write object due to the underlying Vector objects implementing the `drop` method, which disallowed re-use of the object (including simply reading the contents after coming out of the function). That was simply the compiler preventing you from using memory after it was freed. It sounds like a case of not calling "clone" to copy the vector, instead of anything relating to nonlexical borrow scopes.
- jganetsk 12y agohttps://github.com/rust-lang/rust/issues/6393- https://github.com/rust-lang/rust/issues/6393- They want to make this better.
- kibwen 12y agoI don't think that full-fledged higher-kinded types will be necessary to alleviate the specific frustrations you're hitting there. There are plans in the works to make borrow scopes smarter (so-called "non-lexical borrows"), which should hopefully free us from having to split out a nice chain into temporaries merely to appease the borrow checker. This is enough of a priority that I foresee it landing in the language sometime in 2015.
- pcwalton 12y ago> I think that higher-kinded types will probably help a lot with this if they make it into the language, and it's likely that there's a way to get around the errors and get the full benefit from the algebraic data types and monadic functions that I just haven't found yet. How would HKT help with this? It sounds like a nonlexical borrow scope issue. I don't find nonlexical borrow scopes to be a big pain anymore now that I'm used to how borrowing works (and I write thousands of lines of Rust code a week), but they can be annoying when getting started. They're definitely something that can be added backwards compatibly post 1.0; the semantics are not as trivial as they may seem though (you have to do union and intersection of arbitrary regions of a control-flow graph, or loop-nesting tree). > It becomes tempting to just punt on the error handling and call unwrap() on everything just to get it working for now, which leads to problems later on. If unwrap() works for you, why not use the try! macro to make your error handling correct? If it's truly borrow check errors you're having, calling try! is treated exactly the same way as the compiler as unwrap() is—but with try!, you will handle errors correctly.
- smosher_ 12y ago> How would HKT help with this? > If unwrap() works for you, why not use the try! macro [...] I was under the impression that the whole `try!` situation would become nicer with HKT. It wouldn't solve the problem you've identified, but wouldn't it make the code nicer (in some peoples eyes at least)? (Edit: formatting.)
- pcwalton 12y agoWhat HKT would let you do is to write a generic "try" function that works for all "error-like things", instead of having to have a separate try! macro for every error type. Except that that isn't the case any longer, because we now have the FromError [1] trait, which means that in practice try! works for custom error types as well. Instead, what HKTs would actually be useful for is something else entirely: generic algorithms that need to (for example) work with different kinds of collections' iterators and can be specialized for them at runtime, without paying the price of allocating the iterators on the heap. For example, consider a graph algorithm that's parameterized over the type of graph. (Note that I've never actually needed to write one of these algorithms in a generic way myself, but I can see it being useful in some circumstances.) What HKT is not there for is Haskell-like monadic "do" notation. That simply isn't idiomatic Rust, and I don't foresee it becoming so in the future. The try! macro is the way you do this kind of thing, and it's already shipping today without HKT as a type system feature. [1]: http://doc.rust-lang.org/std/error/ http://doc.rust-lang.org/std/error/
- yazaddaruvala 12y agoDefinitely one of the things that frustrates me about Rust, is that I feel like I can't write "pretty" code. Multiple times it was even the reason I gave up learning Rust. Clarification: I think my definition of pretty has come from a certain feel/comfort you have when reading Ruby code (which I have written/read for only a couple months). Meanwhile, to continue learning Rust, I've done myself a favor. I just told myself: "Rust is worth writing but it isn't for pretty code. Sorry!". The other, related, mental block I haven't been able to overcome yet: In Java/Python/Ruby/(even C++), the runtime "seems so heavy" I don't care when I "waste" allocations or memory. However, the fact that it says on Rust's home page "featuring zero cost abstractions" makes me feel guilty/incompetent every time I introduce a cost-full abstraction. Again a reason I stopped learning Rust a few times. Sadly this feeling of inadequacy/guilt extends even to applications. Which is ridiculous since if your application isn't doing expensive things, you probably have a very simple application. This definitely plays a part in why I can't write code as elegant as Rust will allow it. For the record, sorry, I don't usually like critiquing something I don't have a solution for. However, I thought this was worth bringing up. Mostly I needed to vent, but does anyone else feel similarly? Am I really the only one? If other people are feeling the same way, I hope it doesn't become a habit for us to feel the only way to write good Rust is to allow it to be inelegant. It would be disheartening if the next systems programming language was memory safe but still noone enjoyed reading it. I'm curious what peoples thoughts are? Mostly joking, but maybe just a line on the website or guide saying: "We take care of the performance so you can write expensive applications"?
- ufo 12y agoVery interesting point and I also feel similar a need to "overoptimize" my code when I program something in C. This is specially notable when working with strings: In high level languages I use strings concatenation and splitting willy nilly but in C all those strcats and for loops "feel" suboptimal so I have a tendency to write hairy code full of pointers and strtok.
- toolz 12y agoDevelopers are a strange bunch. I've never heard someone say, "Wow, look at that optimized code! I can't wait to work with this dude!". However, I have heard many times, "Working with this person is great. They do everything from expressive code, to small logical commits to make my life easier." It's almost as if many of us would rather be seen as an elite, rather than seen as a pragmatic developer.
- simias 12y agoI guess it depends where you come from, coming from C Rust feels very elegant. I miss Option a lot when I go back to writing C, for instance. Maybe if you write your code first and then modify it to get through the borrow checker it just means you're not yet familiar enough with the language's pattern (and given how young and unstable it is at the moment, I doubt anybody can pretend to know idiomatic rust at that point). Maybe if you took ownership constraints while designing your application you would end up with more elegant code? It definitely adds a cognitive load though, that's for sure. As for the "match", "is_ok" and "unwrap" noise, it used to be a problem in my code as well but since refutable lets have been added to the language it's not really a problem anymore. For instance in my code I used to have: match map::in_range(addr, map::ZERO_PAGE) { Some(off) => { // Do stuff with off } _ => (), } Or: let off = map::in_range(addr, map::ZERO_PAGE); if off.is_some() { let off = off.unwrap(); // Do stuff with off } None of which is elegant or nice looking. With refutable lets I can write that: if let Some(off) = map::in_range(addr, map::ZERO_PAGE) { // Do stuff with off } For is_ok I use the try! macro which is admittedly a bit hackish but works well in practice. In the end I never use unwrap()/is_ok() outside of test code where I don't want to do proper error handling and let the runtime panic if something goes wrong.
- aethertap 12y agoOkay, this is a revelation. I hadn't seen refutable lets, and this seems great!
- masklinn 12y agoRefutable lets also work in `while` (`while let pattern = expression { … }`), which is pretty great.
- aethertap 12y agoI just rewrote one of my parser functions using that, and it literally eliminated 90% of the code and improved error reporting at the same time. Very cool, thanks for the tip.