6 ms·
Personally I tried to like Rust but I just found it endlessly frustrating to write code in. Yeah C++ has a lot of gotchas but they're gotchas I know. I feel l
by tedajax 12y ago
Personally I tried to like Rust but I just found it endlessly frustrating to write code in. Yeah C++ has a lot of gotchas but they're gotchas I know. I feel like the Rust compiler is just yelling at me constantly and a lot of patterns from C++ land don't really translate well into Rust code.
- adamnemecek 12y ago> I feel like the Rust compiler is just yelling at me constantly that's the point. if it's yelling at you you are probably doing something wrong. > and a lot of patterns from C++ land don't really translate well into Rust code that's the point, you can't really get rid of all the c++ warts without changing things
- kibwen 12y ago> if it's yelling at you you are probably doing something > wrong. That's not necessarily the case, any static type system must be inherently conservative and therefore must reject some otherwise valid programs. It also takes time to learn Rust's idioms for creating cyclic data structures, and surely we can still take steps to improve our documentation to that regard.
- Animats 12y agoOften, it's not at all obvious what the type inference system is doing. The borrow checker has better error messages than the type checker. As for cyclic data structures, one would often like to have a primitive which encapsulates a forward owning pointer and a backpointer tied to it, with the backpointer unable to outlive the forward pointer. That's enough to handle most tree-like structures, and doesn't require reference counts or reference-counted weak pointers. It's a compile-time concept checkable by the borrow checker. Something to think about for Rust 2. Also, for Rust 2, I'd suggest exceptions. The "new error handling model"[1] is excessively verbose. Repackaging low-level errors for the next level up requires considerable coding. I understand the big problem with exceptions is what to do about unsafe code. I'd suggest "just say no". Unsafe blocks cannot raise exceptions. If an exception in safe code unwinds into an "unsafe" block, it becomes a panic. Unsafe code in Rust is usually to interface with non-Rust code. If you have unsafe sections in pure Rust code, you're probably doing it wrong. The secret of exception sanity is a standard exception hierarchy, like Python's. Catching an exception class in Python also gets all its subclasses. "EnvironmentError" covers all I/O errors, network errors, and such, but doesn't cover internal program errors. New user-defined exceptions are added below the most appropriate part of the standard exception hierarchy. So Python usually doesn't have Java's exception hell. If exceptions go into Rust, do it that way. Require that all exception objects descend from some predefined exception object in the standard exception hierarchy. An additional advantage of a standard exception hierarchy is that it leads to straightforward internationalization. If all the standard exceptions have translations, users get semi-reasonable error messages in their own language. If you have too many string-type error messages in a program, it's hard to handle multiple languages. Go is starting to get exceptions through the back door. People are abusing the "panic"/"recover" mechanism in Go to get exceptions.[2] Don't go there. [1] http://lucumr.pocoo.org/2014/11/6/error-handling-in-rust/ http://lucumr.pocoo.org/2014/11/6/error-handling-in-rust/ [2] https://code.google.com/p/go-wiki/wiki/PanicAndRecover#Usage_in_a_Package https://code.google.com/p/go-wiki/wiki/PanicAndRecover#Usage...
- Jweb_Guru 12y ago> As for cyclic data structures, one would often like to have a primitive which encapsulates a forward owning pointer and a backpointer tied to it, with the backpointer unable to outlive the forward pointer. One can already do this with `&T` (for the backpointer) and `Box<T>` or `&mut T` (for the forward pointer). The hard part is letting you safely mutate something once the back reference has been added, without any runtime checks and with arbitrary data in the pointer. I don't believe what you've described addresses that issue, but maybe I'm missing something.
- Animats 12y agoMy point is that it's possible to extend the Rust model to check this at compile time. The borrow checker has to understand backpointers. The owned object cannot outlive the owner, so a simple backpointer is safe in that sense. But ownership can change, and when it does, the backpointer must also change. The Rust ownership model can almost do this now. A move-type assignment to the forward pointer which causes a change in ownership would have to do a mutable borrow on the backpointer to get exclusive use, then change the backpointer to the new owner. That prevents a change to the backpointer while it's being used for something. The borrow checker would have to understand that those two pointers are tied together by an invariant. There would need to be syntax for back references, understood by initialization as well as moves. It's such a common case that it's worth the special handling. It would let you do trees and doubly-linked lists without reference counts. The Rust ownership model is very powerful. I was fooling around with something like this a decade ago as an improvement to C++, but all I expected to get out of it was the elimination of reference count updates when you passed an object to a function without giving the function permission to delete or keep the object. Rust pushes that model much harder, and it works. It can be pushed even further to make more common use cases compile-time checkable. This would cover more common idioms from C++. Another possible extension is lifetimes on objects allocated with "new". If you need to add something to a tree, you want the new objects to have the same lifetime as the tree. If you could specify a lifetime in "new", resulting in an object being allocated in an outer scope, that would cover the idiom where you call some "add to data structure" function and it creates objects to be added to the data structure. Currently in Rust, you have to create new objects from within the scope that owns the data structure, then call some function to install them in the data structure. The idea of creating an object in an outer scope seems strange, but when you create a closure, you're doing just that.
- reubenmorais 12y agoMy understanding is that one of the basic assumptions you must have when choosing Rust over C++ for a new project is that you're looking for long term maintainability over short-term ease of prototyping. A lot of the yelling done by the compiler comes from real-world experience building gigantic piles of code that need to be maintained for years and years. And the bit about patterns that don't translate well, that's easily fixed with more experience coding in Rust. C++ programmers know C++ so well because they've been doing it for decades and the language forces you to be on the watch for all the gotchas.
- hendzen 12y agoThat's the first time I've ever heard C++ trumpeted for "short-term ease of prototyping" - and I write C++ fulltime.
- reubenmorais 12y agoHa! I felt awkward typing it, but the GP really read like he feels about Rust vs C++ the way someone would feel about Haskell vs Python.
- kibwen 12y agoWe would love to hear more about your experience, if you care to elaborate. Understanding where people are having trouble helps us direct our documentation efforts at making the language easier to learn for people from various backgrounds. :)
- cjcole 12y agoI've written about 10 thousand lines of Rust at this point and hope to write much more. After "suspending frustration" and internalizing the borrow checker and the idioms it just works for me now. That said, I still get minor jets of frustration when this works: let y = a.b.c; But this does not: let x = a.b; let y = x.c; And this works: let x = foo.bar(); let y = foo.baz(x); But this does not: let y = foo.baz(foo.bar()); due to scoping and borrow checker rules. Papercuts, yes, but annoying nonetheless. That said, Rust (effortlessly) caught what could have been a very dangerous bug for me last night (I'm using a Rust wrapper around Lua): when you call "tostring" in Lua it returns you a reference to a string on the Lua stack. If you then pop the Lua stack the reference becomes invalid. If you then use the reference to the string, well... (This is akin to iterator invalidation.) Rust's borrow checker flagged the error and its error reporting made the problem obvious. This will make up for a very large number of papercuts.
- sanderjd 12y agoYou've captured one of my somewhat late realizations about rust – some seemingly-simple refactors change semantics in ways you might not immediately expect, coming from most other languages. In addition to the ones you mentioned, I'm sometimes frustrated when trying to extract methods, sometimes because of moves and sometimes because of needing to write out a complex return value where the type was previously inferred. But I have personally found it to be true that (as with any other language) you get a gut sense for the semantics of the language and stop fighting against them over time.
- cjcole 12y agoThe core problem is that the set of safe programs is considerably larger than the set of programs which pass a tractable, maintainable, and (potentially) provably safe compiler (including borrow and type checkers). You want to enlarge the latter set, since that lessens programmer frustration and increases expressiveness, but in the context of a non-garbage-collected language that often requires adding complexity to the type and borrow checkers which may in turn end up introducing bugs. A discussion of various run-ins with the borrow checker and some details of attempts to reduce the friction: https://github.com/rust-lang/rust/issues/6393 https://github.com/rust-lang/rust/issues/6393 'Both @pcwalton and @zwarich spent some time trying to actually implement this work (with a possible RFC coming hand-in-hand). They ran into some unexpected complexity that means it would take much more work than hoped. I think everyone agrees with you that these limitations are important and can impact the first impression of the language, but it's hard to balance that against backwards-incompatible changes that are already scheduled.' [added on edit:] In particular, here is a very nice statement of the issue(s): https://github.com/rust-lang/rust/issues/6393#issuecomment-24307095 https://github.com/rust-lang/rust/issues/6393#issuecomment-2... nikomatsakis: 'I'd also like to have more progress on a soundness proof before we go about extending the system.' And here again is the tension between complexity in the compiler and the coverage of the space of safe programs.
- chongli 12y agoI feel like the Rust compiler is just yelling at me constantly This is all about your frame of mind. When I started learning Haskell I felt the same way. Now I treat it differently. I ask the compiler questions and it gives me the answers. What is the type of this partially-applied function? Oh, that. It helps to have an editor and workflow that supports this style of programming. Writing large amounts of code and submitting it in batch to the compiler is just going to give you a headache when you get dozens (or more) errors and warnings. With a tool like emacs flymake/flycheck, you can get continuous feedback and use hotkeys to jump to and quickly fix errors as they occur, rather than letting them pile up. a lot of patterns from C++ land don't really translate well into Rust code Well of course. Rust is a new programming language, not a rehash of C++. To take full advantage of Rust you need to learn its patterns and idioms.
- andrewflnr 12y agoIt gets better. It really does. Yeah, you have to learn the lifetime formalism and how to express the stuff you want in it, but once you get your head in the game, learn the Rust patterns, it's no more frustrating than your average statically typed language. Or maybe it's a bit more of a pain than C++, but the payoff is worth it.
- findjashua 12y ago"I feel like the Rust compiler is just yelling at me constantly" not fair to blame the compiler for being good at its job
- baddox 12y agoIf the job is to increase productivity, constantly yelling is probably not a good way to do the job.
- denis1 12y agoIs the job to increase productivity? Or is it to remove entire classes of bugs while not killing productivity at the same time?
- cjcole 12y agoIt's not the case that all wrestling with the borrow checker is about bugs: https://github.com/rust-lang/rust/issues/6393 https://github.com/rust-lang/rust/issues/6393
- rkangel 12y agoRust is described as the successor to C++ so often that it's easy to end up assuming that it's a drop in replacement. It's not. It's a very different language, but designed for the same use cases (mostly). There are many C++ patterns that become pointless, and new patterns you need.