I don't get at all how being forced to do anything could ever be a good thing. Smells like cognitive dissonance from here. I'm all for powerful tools that enables me to write better code faster; but being forced, really? That's the best thing about Rust? Ew.
Jokes aside, I see rust borrow checker is just another dimension of type checking, it is type checking for access behavior and that's all about it.
Just as you can wrap your data in memory into static type system and leverage it to make a class of mistake impossible (passing wrong data to a function), you can wrap your access behavior into lifetime type system (converting raw pointer access behavior to reference with correct lifetime/access using unsafe) and make a class of mistake impossible.
In my definition, that's a powerful tools that help me to write better code faster.
If you don't prefer tools that can automatically check if you were a good developer or not, it not only does not scale (to trust the software you build), it's that I wouldn't want to work with you as a teammate.
You are pretending there are no trade offs.
> it not only does not scale (to trust the software you build)
I think we should all be able to agree that empirically that is nonsense.
There are very few rules in C; same in Forth, Common Lisp and Perl among others; they provide tools, not religions. Python was always borderline.
These days it's like they're in some kind of competition to stuff as many rules as possible down peoples throats and get away with it. And that's not even the weird thing, the weird thing is that users are begging for more.
Or, for an alternative view, someone makes an extreme statement like "I don't get at all how being forced to do anything could ever be a good thing" and when called on to explain it in common contexts you note how you are harshly being attacked because of someone's ideology.
> There are very few rules in C
There are tons of rules in C. You have to type your variables. You often have to cast between variables to change type. You've likely just internalized them and accepted them as common so you don't think of them as cumbersome.
> same in Forth, Common Lisp and Perl among others
Even Perl is opinionated in spots. Have you ever wondered why postconditionals only work on statements, and not blocks, while regular conditionals only work on blocks, and not statements? e.g.
do_something() if $var_as_bool; # Valid
{ do_something(); } if $var_as_bool; # Invalid
if ( $var_as_bool ) { do_something(); } # Valid
if ( $var_as_bool ) do_something(); # Invalid
A choice was made to enhance the positive and suppress the negative aspects and possible uses of each.The thing is, what Rust is doing with the borrow checker isn't even as subjective as that. It's enforcing a constraint which, like type constraints, is based in a mathematical understanding of how to entirely prevent certain classes of errors. Like most type systems, there are escape hatches to allow you to do what you need as long as you take responsibility. So, since it's in some aspects in concept and execution to type checking, it's natural to ask someone that is critical of it what they think of type checking, as it leads to a natural explanation of how it works and the benefits.
> And that's not even the weird thing, the weird thing is that users are begging for more.
People like street signs as well. That doesn't mean they are always followed, but it is useful to see how to work well within the system most the time.
It's not like Rust has any rules just to make your code look funny.
> These days it's like they're in some kind of competition to stuff as many rules as possible down peoples throats and get away with it.
Could you expand on this please?
> And that's not even the weird thing, the weird thing is that users are begging for more.
... which users? And if the majority of them, then this perfectly explains the competition, but why are you surprised about the users' need for rules?
So anarchy it is, then?
But they're not.
> I don't get at all how being forced to do anything could ever be a good thing.
You specifically say that being forced to do anything couldn't be a good thing. The only other option is for nothing to be forced.
This is the result of writing hopelessly long lines of code. Python doesn't force you to write long lines.
I've done it myself, many times, but gradually made efforts to avoid this. I'll move deeply indented code into a new function or split up a long expression with a temporary variable. If I run into a particularly hard-to-format portion of code, it's usually a sign of that my code could be better.
ys = [x + 1
for x in xs
if x > 0]
or: ys = [
x + 1
for x in xs
if x > 0
]When compared to a set of functional style mapping and filtering functions, it quickly becomes much less readable as your needs become anything more than trivial. My main issue is that focus for the important aspect of what is being accomplished swings back and forth from the front to the back of the statement multiple times, especially if nested and with conditionals. e.g.
[(x,y) for x in range(10) for y in range(10) if y > 5 if x < 6]
or, in a similar formatting to what you show: [
(x,y)
for x in range(10)
for y in range(10)
if y > 5
if x < 6
]
In both cases, the accurate reading requires scanning back and forth from beginning to end of statement (whether vertically or horizontally) because the conditionals always postfix the rest of it (and this example is not as complex as it could be). For loops would likely have the conditionals preceding everything, making it obvious, and a set of filtering statements. The functional style also allows for a fairly straightforward reading of what's going on: toTen.filter(y=>y>5).flatMap(y=> toTen.filter(x=>x<6).map(x=>[x,y]) );
or: toTen.filter(y=>y>5).flatMap(y =>
toTen.filter(x=>x<6).map(x=>
[x,y]
)
);
Of course the functional style does require at least some minimal knowledge of some concepts often extraneous to novice programmers, so I understand why that wasn't chosen in Python's case. I just wish they had put conditionals in the same positional flow as the rest of the statement. >>> [(x,y) for x in range(2) for y in range(2) if print(x, y) is None if print(x, y) is None]
0 0
0 0
0 1
0 1
1 0
1 0
1 1
1 1
[(0, 0), (0, 1), (1, 0), (1, 1)]
Both conditionals have access to x and y.So the list comprehension is equivalent to this:
[(x,y) for x in range(10) for y in range(10) if y > 5 and x < 6]
And could more clearly be formatted like this: [
(x,y)
for x in range(10)
for y in range(10)
if y > 5
if x < 6
]
And would look something like this in a functional style, perhaps: toTen.flatMap(x =>
toTen.filter(x => x<6 && y>5).map(y=>
[x,y]
)
);I actually see what's going on a bit clearer now, as I looked closer and found that the correct way to write what I was originally trying to express is actually:
[(x,y) for x in range(10) if x < 6 for y in range(10) if y > 5]
which could be formatted as: [
(x,y)
for x in range(10)
if x < 6
for y in range(10)
if y > 5
]
Which is actually much closer to the functional style's flow, and is correctly eliminating iterations earlier in the loop (which is an important consideration).I was confused because I had seen examples where multiple if clauses where added to the right side, one per loop level (as I showed), and that makes it look like they are operating on the different levels, when in reality they are working on the innermost loop, like you showed.
I'll retract most my complaints then. There's still some question in my mind as to how you would usefully mutate items of the loop and use them in other levels of the loop without recomputing them again, but that might just be my unfamiliarity with the construct.
I find the split comprehension more readable than the equivalent for loop. That's mostly because I'm used to that style, of course, but it's also because it's more constrained. Comprehensions have a very limited grammar, but there are multiple ways to write a for loop that builds a list.
That said, they are almost always harder to understand than an equivalent for loop with an if statement. A list comprehension by its very nature groups a number of actions into a single expression, it’s harder to break up the parts.
Even an experienced developer who sees them all the time and can understand one in a second, would probably take a half second to understand the equivalent for/if statements.
List comprehensions are basically a watered down gateway drug to functional programming. Everyone who has ever taken a functional programming course loves it (rightly so), and often tries to find places to use it. Comprehensions do quite a bit in a single expression, it's easy to see that inch towards functional programming.
However, junior developers haven't taken a functional programming courses. They learn to program instructions or statements line-by-line. They are told (not entirely accurate) that every clock tick, the processor moves forward one unit at a time. Your mind starts to imagine the processor in that way, executing a line and going on to the next. Do this, then move forward, then do that. This is procedural programming.
A list comprehension doesn't quite fit that model, because it does quite a bit in a single line (generally map + filter, sometimes reduce). They teach you that units of complexity can generally be broken down line-by-line.
Of course list comprehensions can be formatted to multiple lines, but it is intrinsically something quite different. A list comprehension is not a statement (e.g., var foo = b + 5), it's an expression (['b' if x < 1 for x in y]) and a pretty complex expression at that.
Junior software developers are taught about statements, going line-by-line. They aren't taught about functional programming or complex expressions. I love python comprehensions, but I wish they were presented in a way that was as easy to understand as a for loop with an if statement.
Senior developers wouldn't care, but it would open up a giant world to junior devs.
def txform_item(x):
<maybe many lines, as complex as needed, + can see vars in outer scope>
# right below, for optimal locality of ref for human reader
new_list = [ txform_item(item) for item in old_list ]But a condition can be easily split if it is assigned to a variable, and that variable then tested. And naming said variable well can make a comment explaining the condition redundant.
x[1][4] = a[2] + b[4]
Black will format like either of these: x[1][
4] = a[2] + b[4]
x[1][4] = a[
2] + b[4]
But not like either of these, unless you insert the brackets yourself: x[1][4] = (
a[2] + b[4])
x[1][4] = (a[2]
+ b[4])
Maybe this sounds like just one little problem but I think it's a fundamental flaw. I have seen the result of a Python formatter (not Black but had the same problem) applied to a couple of files and it's a total mess. I'd take inconsistent column widths over that any day. I asked the creator of black about it and he was pretty dismissive. x[1][4] =\
a[2] + b[4]I think a more proper tooling analogy is safety tools, like the Saw Stop. They don't guarantee that you make a good construction, but they do either prevent or significantly reduce the chance of you losing a thumb.
Edit: This place is disappointingly childish... It does not reflect well on Rust, either.
You're getting downvoted not because of the alleged childishness of this place, but because you're trying to win a discussion on a complex issue by scoring a simplistic "gotcha".
Anyone trying to sell you a tool by claiming that "it will make you a good whatever" is just trying it on. Yes, a good tool can help achieve better results but that's not the same at all.
Then, all the replies I received were seemingly defiant but all in fact agreed to the point while trying to sound smart.
Childish, because I feel that some commenters see posting as a pissing contest.
I'm genuinely surprised by the reactions.
And where exactly is the problem? You've received exactly the type of answer you wanted. steveklabnik repeatedly agreed with you by saying that a programming language cannot make you a good programmer.
Remember, your "simple truth" is just a small part of the whole story. The other part is that less strict languages allow you get away with mistakes and be lazy but later on fail catastrophically (e. g. by "losing a thumb"). You're dismissing this very important part as "trying to sound smart" or by calling it childish.
> [if] you like it or not.
This is probably the best in-a-nutshell statement that describes what a good programming language is for me.
I had similar moments in the past. Before Python I cared about indention to some degree. But once I got used to the way Python forces you to indent your code, I came to the realization that this is pretty much the way I should format my code anyway and it influenced my coding style in all other block-based languages for the better.
Clojure was another language that showed me how functional programming should be done. My code in all languages changed and I used a lot less variables and a lot more constants and parameters. My code and the systems I created became a lot simpler and much more robust as a consequence. I had used Haskell before but it never really transformed my mind the way Clojure did. I also got a really good grasp on destructuring and list comprehension.
Rust offered me several relevations. I finally understood test-driven-development because the language has excellent support for it. I also understood RAII more deeply. Traits became clear to me (Clojure protocols and Haskell type classes felt strange to me). I finally understood why the Maybe monad in Haskell/ML is one of the simplest and yet bests ideas in programming.
And there is the borrow checker. I already had a functional programming mindset when I picked up Rust. But having a clear owner of data takes the mindset of functional programming even further[1], even when mutability is involved. It is a lot easier to reason about your code when you know exactly which operations can (and should) change a variable.
An aspect that the Python, Clojure and Rust communities have in common is that they emphasize idiomatic code a lot. Therefore it is much easier for newcomers to find examples of professional-level code and learn things the right way. And it is much easier to dive into a new codebase. Clojure is actually a very extreme case of this, because Clojure code tends to use a lot of 1-letter parameter names. In most programming languages you are allowed to have the variable "i" for looping over numbers. But not much more. In contrast, Clojure has a list of well-established 1-letter variables that are recognized by everyone in the community.
[1] This does not adhere to the classical definition of functional programming but I feel that it is a natural extension to it.