15 ms·
> To be honest, I really think you're blowing the complexity of this way out of proportion. Plus like, it's not like Rust's grammar is this incredible thing of
by dbaupp 9y ago
> To be honest, I really think you're blowing the complexity of this way out of proportion. Plus like, it's not like Rust's grammar is this incredible thing of beauty: look at `where` clauses or lifetime syntax. If Rust were really optimizing for human readability, it's hard for me to imagine this is the result. It's purely a style thing, just like `let` instead of `var` and so on, and I don't think sacrificing programmer familiarity for a designer's idea of style is a good tradeoff.
Having a nice grammar in this respect is more of a technical beauty and elegance than an aesthetic one, but it translates into simpler tooling and so on, which can be an aesthetic one. To be honest, I also think you're blowing the value of having "(Type)value" syntax way out of proportion. It's not like Python or JavaScript use it, and everyone seems to cope fine, and even C++ (theoretically) prefers the far more clunky static_cast<T>(...).
> Otherwise I greatly prefer to have this logic internal to the thing I'm matching on:
Oh, yeah, obviously abstracting out into functions for common functionality is better than ad-hoc matches, but there's lots of cases where that's just overhead and fiddly and less clear. People already complain a lot about Rust requiring needless ceremony and being unergonomic, and encouraging people to go through the ceremony of defining functions for little things seems to be going against that.
> Do you hate it enough to pull all the statements out of a language?
Rust has statements: match, if and the loops can be used as statements, and you're actually free to write your assignments C/C++ style (type inference even works for it):
let name;
match someEnum {
X => name = "...",
Y => name = "...",
...
}
doSomething(name)
Moving the "name =" out of the match seems like a tiny step that makes the code less noisy and nicer. But, it is a style preference.
Rust has not pulled statements out of the language, just upgraded things from "always a statement" to "can be an expression".
> I don't want to keep repeating myself, but my main beef isn't readability or whatever. It's that I don't understand what it gets me beyond using if. If ternaries somehow (really) solved a NULL problem, or helped me with error handling, or let me avoid use-after-free, then those are all great and welcome tradeoffs. But all it does is save a couple of lines, and because my problem has never been that I had to use a couple extra lines I just don't care about features where that's the sole virtue. I need more.
The value of 'match'es for things where 'switch' or 'if' are reasonable is being declarative and uniformity with the cases where 'switch' and 'if' are not reasonable. It's fair that it's unfortunate that things can be misused to create confusing code, but I don't think that's a reason to remove something in and of itself, or else a language would have to be extremely small.
> Oooooh, I think this is a very good point, but honestly this is just half-hearted polymorphism. These two enums should be in a struct, and this logic should be in its implementation. Then I think it's fine to do something like this:
What do you mean by polymorphism?! Just being able to have "None" or a value in the same variable as in Python?
Contesting every example by saying that it should just be wrapped up into a struct/function is... kinda missing the point. You still end up with that code somewhere (although it's true for the name example being in a function means 'return' works), and, you end up with a ridiculous number of near-pointless functions and types. In any case, there's not nearly enough context to say that that example should be wrapped up into a type: what's the connection between the two values other than that they should be added? There's a reason no-one proposes writing `a + b * c` as
(Add {
left: a,
right: (Multiply { left: b, right: c }).doIt()
}).doIt()
And this random example is only one step above plain arithmetic in terms of abstraction.
In any case, your proposed variant is... not a good version of my code. There's nothing stopping `if self.left: return self.right` (oops!) and it fundamentally doesn't fit with Rust's approach to conversions between types. As I said earlier, adding the type system features to defend against the first thing ends up, in the limit, being more complex than (but pretty similar to) having 'match' and using the existing type infrastructure. This is relevant to Rust's goals, both in being a more reliable systems programming language in general, and also for safety: with move-only types and stronger references, guaranteeing safety but still being useable I think would mean having a lot of that complicated infrastructure.
> And they're much, much better than match because they scale to more than 3-4 variables; past that and match is just terrifying.
I strongly disagree that 'if' scales to more than 3-4 variables, and that 'match' scales worse.
'match'-without-'if' is more restricted than 'if' and so is easier to understand (understand at a high-level): if I see a match on some variables, I know that it's going to be looking at those variables immutably, and structurally. The state of the variables at the start of the match completely determines which code runs. (And, but I guess you probably disagree with this, even with 'if's on arms, there's still not less structure than a plain sequence of 'if's. And, this lack of structure is clearly flagged, whereas with an 'if' chain, everything looks the same.)
But, with an arbitrary sequence of "if"s, it's a free-for-all, anything could happen up to and including mutation of things queried later, there's no static checking that I'm interacting with the variables I want to, and there's no single source of truth for what those variables even are (like the ... in match ... {), and there's no checking for things like handling all the cases. "if" having more power and so being worse is exactly the same reason that "goto" is frowned upon: it is too flexible and so too hard to understand. The same reasoning can be seen in C's 'switch' versus "if (x == Value) ... else if (x == OtherValue) ...".
It's true that an sequence of 'if's with lots of variables doesn't look particularly different to one with only a few variables (unlike Rust's match), but this is deceptive: it's going to at least as hard to understand what that if/else if chain is actually doing, not what it seems like it is doing, even in idealised cases (and, for fairness, if you're thinking the worst case of 'match' with @s and ifs, one really should be thinking of the worst cases of ifs).
---
Anyway, wrapping up this thread, you've convinced me that Rust could be a little more minimal ('match' doesn't need @ or 'if', and the convenience of everything-is-an-expression isn't needed), but I don't think there's even close to "50x" space for simplification.
There's a lot of consistency between various parts without many clunky interactions, which, I find, is where the most annoying complexity in programming languages appears. A lot of different to a major component of the audience, but a lot of that difference is bringing in conveniences from the last few decades of programming language research/experimentation.
It might be an interesting experiment for you to take a moderately large Rust program and convert it into "MISRust" (ala MISRA C, or maybe "misfit Rust" :P ), that doesn't use the C statements as expressions anywhere and just does single level 'match's without ifs or @s everywhere (etc.) just to see what it looks like. I suspect it wouldn't even be too hard to write a clippy-style lint that enforces all those rules.