5 ms·
> Rust is not the most beginner-friendly language out there — the compiler is not as lenient and forgiving as that of most other languages […], and will regular
by loup-vaillant 10y ago
> Rust is not the most beginner-friendly language out there — the compiler is not as lenient and forgiving as that of most other languages […], and will regularly reject your code […]. This creates a relatively high barrier to entry […]. In particular, Rust’s “catch bugs at compile time” mentality means that you often do not see partial progress — either your program doesn’t compile, or it runs and does the right thing. […] it can make it harder to learn by doing than in other, less strict languages.
I don't see how making the type system stricter makes the language harder to learn. Maybe that's because I know another relatively paranoid type system (Ocaml), but still.
A type system that rejects your code is like a teacher looking at a proof you just wrote, and tells you "this doesn't even make sense, and here's why". It may be frustrating, but this kind of feedback loop is tighter than what you would get from a REPL.
And you do see partial progress: the type errors change and occur further in the source code as you correct your program. Each error is an opportunity to fix a typo or a misconception. The distinction between a broken prototype that doesn't even compile and a working program isn't binary: when you correct a type error, your program is less broken, even though it doesn't compile yet.
- bjz_ 10y agoThis is a perspective that I think we could do better at advocating for when it comes to static typing. It's not really just about safety, it's also about being able to rapidly figure out a good model for your problem through a conversation with the type system. It will not hesitate to let you know when you don't make sense, or you haven't explained things correctly!
- IshKebab 10y agoBecause a lot of the time it does make sense and the borrow checker just isn't clever enough to know. For example: https://github.com/rust-lang/rfcs/issues/811 https://github.com/rust-lang/rfcs/issues/811
- loup-vaillant 10y agoThen the borrow checker is just flawed. I know, I know, no non-trivial checker can be both sound and complete. But if too many useful programs end up being rejected because of that gap, the type system must be changed.
- ekidd 10y agoMy experience with Rust's borrow checker is that it can be pretty frustrating for about a week, and then it mostly stays out of the way, only popping up to report dumb bugs. After that, maybe once per 2,500 lines of code written, I still run into a situation that requires some finessing to pass the borrow checker. It's definitely not perfect, and there are known places where the checker needs to be enhanced (and that work is underway in some cases). Overall, I'm happy to accept the tradeoffs of the borrow checker. I have to deal with a learning curve and the occasional workaround. But I get fast, correct and expressive code. And even though I like to think of myself as being extremely careful about pointer ownership, the borrow checker still catches real problems in my code that would be a nightmare to debug, especially once threads get involved.
- jandrese 10y agoMostly, it just trains you to avoid the patterns that cause it problems. As long as this doesn't result in horribly convoluted code it's not really a big deal, it just increases the ramp up time for the language.
- robohamburger 10y agoAgreed (though for me it was a bit longer than a week)! Knowing when to use borrowing over owned copies I think is vital as well. Once I started planning out what was going to own what my code became a lot simpler and I spent less time fighting the borrow checker in general. Hopefully as rust evolves so will the tooling and the borrow checker. It seems like you could build some cool visual aids and/or debugging info into IDEs for novices who are new. --explain already sort of does this.
- oconnor663 10y agoI lot of these cases end up with a workaround like "you need to bind this temp value to a local variable before you use it" or "you need to put this section in curly braces". These things are annoying, and there's lots of room for the compiler to infer more of them, but they don't really get in the way of writing useful programs. In the cases where I've seen the compiler really get in the way, it's because the program wants to do something actually unsound (by Rust's standards), like taking a mutable reference to a list while something else is iterating over it.
- pYQAJ6Zm 10y ago> A type system that rejects your code is like a teacher looking at a proof you just wrote, and tells you "this doesn't even make sense, and here's why". Not only that: the Rust compiler, for many cases, will suggest calling it with a ‘--explain’ argument to get an explanation of what’s causing the error and, usually, how to solve it. A very friendly feature.
- fizzbatter 10y agoI'm not trying to bash Rust, i love it, so for random viewers looking to pick a fight.. please don't bother. I'm only citing what my experience thus far is. It's confusing, as hell. Let me try to explain a little bit. I'm a average dev, and have been for ~7 years or so. My languages have typically been dynamic, but the past 2 years has been heavily Go. I picked up the majority of Rust, as well as basic borrow checker usage, in a matter of hours. But that's where the fun stopped. Generic syntax beat me up (mostly related to Static dispatch and Type Assertions). Lifetime syntaxes are currently kicking my ass as well. The problem as i see it, seems to be that i am a more.. "dig in" developer. And frankly, that does not seem to fly here. I've read a lot of the rust docs, but rather than start to finish, it's been in response to questions i have. This seems to be problematic for rust, as important concepts can be left in the dust. A good example of this is Static vs Dynamic Dispatch is rust. Heavy trait usage combined with using Static Dispatch incorrectly resulted in code not designed for what i wanted, and lots of seemingly random bugs. Ie, `foo = Foo::new()` would fail, but `foo = Foo::new(); foo.bad(baz);` would succeed. Currently i'm trying to figure out how to either move a variable out of a block, or, more importantly define a lifetime on `Ok(r) => r,` so that the result can escape the block. Anyway. None of this is the fault of rust exactly, and again, i love this language. I'm switching because Go hasn't offered the full safety i want. But man, it has been quite the hellish experience to get started.
- steveklabnik 10y agoHave you dropped by IRC? Always happy to help answer questions in real-time. > how to to either move a variable out of a block, Blocks evaluate to a value, so if it's a movable type, it will move. let x = { let s = String::new(); // stuff happens with s s }; Here, s will move out of the block, and into x. > more importantly define a lifetime on `Ok(r) => r,` so that the result can escape the block. Lifetimes cannot extend the life of something, they are descriptive, not prescriptive. So I think you might be trying something that's impossible...
- bluejekyll 10y agoI agree with the statement that Rust has a steeper learning curve, and I love Rust and would choose it for every programming challenge I have from now on. A strategy that I have reverted to in Rust is to use #[test]'s to mark my progress... I might only write one short function, verify it works with a #[test] then move on. It's almost TDD, but not quite. I started this practice back in my Java days, but have found it particularly beneficial with Rust.
- scottlamb 10y ago> A type system that rejects your code is like a teacher looking at a proof you just wrote, and tells you "this doesn't even make sense, and here's why". It may be frustrating, but this kind of feedback loop is tighter than what you would get from a REPL. I've been trying to port a personal project to Rust recently. Sometimes Rust's type system is too limited to understand why something is safe. Here are a couple examples: * in C++ code, you can have a class in which one field has a reference/pointer into another which came earlier in the declaration order (and thus will be constructed first and destructed last). This is often a useful thing to do (one example: https://users.rust-lang.org/t/struct-containing-reference-to-own-field/1894 https://users.rust-lang.org/t/struct-containing-reference-to...) You can't do that in Rust. They have to be separate instance variables on some thread. If there's no thread running to own it, you have to use referencing counting (Arc or Rc) or unsafe blocks. Or maybe instead of keeping a reference, have all calls take the outer struct as a context argument and use some sort of struct/lambda which knows how to find the thing you're referencing given that (this is what I'm trying in my code). Someone proposed a language change for "self-borrowing structs" (https://mail.mozilla.org/pipermail/rust-dev/2014-February/008658.html https://mail.mozilla.org/pipermail/rust-dev/2014-February/00...) but it didn't go anywhere as far as I can tell. * in C++ code, you can loop over one instance variable and then call a private method on self which mutates a different instance variable. In rust, you'll get errors about self being partially borrowed. I think you have to restructure the other method to not take self, which probably means grouping things into child structs. Basically Rust doesn't look across functions boundaries to decide if something is safe so it has to consider this an error even if it isn't for the particular method you're calling. Fundamentally, I think the choice is between these three options: * use a garbage-collected language and not have to be explicit about these details. There's no possibility of buffer overflows or use-after-free errors but you have to pay the runtime overhead of the garbage collector. Go is clear about the costs involved: pauses up to 10 ms, 25% of all CPU cycles, and 50% of RAM (see http://golang.org/s/go14gc http://golang.org/s/go14gc). Other GCed languages likely have similar costs even if they aren't stated as clearly. * use an unsafe language like C/C++, enforce these things manually in your head and with comments, and occasionally have security problems when you screw up. * use a safe-but-explicit language like Rust/Swift and have to "show your work" to the compiler quite a bit more, finding a different way if safety is too hard to prove.