Hacker News new | past | comments | ask | show | jobs | submit login

(...)

> While I get what this does, it looks very noisy. Losing the ability to do this in a single construct is fine w/ me if this is the result.

FWIW, I agree, just demonstrating that replacing @ in patterns isn't the easy case as it is with the classic integer range example you gave. :)

> Mostly I just didn't think the (A)Rc pointers needed a sigil, and I thought it was weird that what most people would think of as a pointer used to use `~` and now uses `Box`, whereas the pointer you would (mostly) never use is the one with the most familiar sigil (` `). Feels like that one should be `Raw` and `Box` should be `* `. Library-defined or otherwise doesn't really matter to me; and if you're building something that can't allocate, the compiler will just tell you when you can't use `alloc`/`new` or whatever. EZ.*

The one I think of as a pointer is & and &mut. And, there's a whole pile of reasons that Box doesn't get used nearly as much as * and malloc in C:

- arrays and non-owning pointers use different syntax (Vec/[] and &/&mut respectively)

- proper generics means much fewer places where one needs to create a void* to pass data around (e.g. std::thread::spawn vs. pthread_create)

- enums means polymorphism and optional-ness can be done with "inline" types with little ceremony.

But yes, as I said, it is a little weird that the dangerous pointers gets relatively nice syntax.

> I would get rid of struct destructuring entirely. It's only real use is inside of match, and that's packing more things into match.

If you're including tuples as structs, I strongly disagree: it's great for `let` and multiple returns (and even true structs, not tuples, are sometimes nice there, although 'foo'/'bar' has less benefit versus a struct's 'x.foo'/'x.bar' than versus a tuple's 'fooAndBar.0'/'fooAndBar.1'). Also, .. works for struct enum variants: the Baz::Foo variant above is valid Rust syntax now.

> Oh I'll definitely cop to being 1000x better at C than I am at Rust, and the more I use it the more I'm fine with it. But moving from C to Rust (or any language to Rust) is so hard because of all of these things. I mostly work in C, Python, Java, and JavaScript and Rust is very different from all of those -- and it's hard for me to justify those differences. And as a C programmer, the borrow checker isn't responsible for the learning curve. Rather, it's all the "neat" things in Rust. All I really wanted was C with a borrow checker, or Java without GC and a 90s idea of OO (hand waving a lot here). I honestly don't see why we had to tack on all this extra stuff.

Yeah, it's definitely true that new things have a "change budget": how different they can be before it's too much. Rust has been designed with this in mind (things like {} and <>, as I mentioned above), but different people's threshold for it are different. There's lots of changes in Rust over C that make code more declarative and require less mental reconstruction (for me), which means I enjoy writing Rust more than if it didn't have them, but it's definitely true that they aren't literally necessary, or that they could maybe be phrased in more restricted ways that match closer to C.

I've personally found being exposed to many very different languages has helped inform the code I write in all others. Each new one definitely takes some getting used to, but my experience is that having touched several different paradigms has made me both more flexible (less attached to any particularly way of doing things) and a deeper understanding of even the "boring" languages: the trade-offs and "whys". There's value to things being the same, but there's also value in being able to break out of that mold and doing new things even if there's not an obvious benefit when focused on the old style.

I hope that you find Rust more and more enjoyable as you use it more, and if not, that's unfortunate: not everything works for everyone. Hopefully, at the very least, Rust inspires other languages that suit you better. :)




Just chiming in that I really like your absurdly long HN comments about Rust, and that while I have a script that gives me an RSS feed of them, others don't.

You should consider a blog where you just copy-paste long HN comment threads about Rust you find yourself writing.


Thanks for the kind words! I do have a blog with various writings about Rust (link in my profile), but I don't think publishing comments would be appropriate for me right now (at least, not without more work). :)


> However, I'm not sure it's entirely like that in this case: at the very least, this complexity here is revealed to the programmer (they have to do the same parsing switch in their head, even if it's usually fairly obvious). Additionally, this complexity applies, somewhat, to tools that work with code too, not just the compiler (e.g. limited editors trying to do syntax highlighting without running more detailed semantic analysis). This seems like such a minor thing to introduce such a heavy penalty, but maybe there's something more annoying you're finding with 'as'.

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.

> I really do think this is just a familiarity thing: coming from languages with a strong statement vs expression distinction it's weird, coming from mathematics/languages with out the distinction, it isn't. The language isn't particularly more complex because of it, it is just slightly different.

I disagree; I think the language is significantly more complex as a result. "Everything is an expression" leads to a lot of assigning out of `if` and `match` expressions, consequently there's a ton of pressure to dump a lot of things into them. `match` in particular is really just a regex for variables, except 100x bigger. In most ways I consider that a regression.

> It's true that Rust's target market is mostly the former set of languages, so one could argue maintaining familiarity is critical (something Rust acknowledges: {} for scope and <> for generics driven by that), but it's also an argument that would have kept us writing slightly improved assembly languages forever.

I don't think that C/C++ are being held back because they have statements though. My whole point is that Rust fixes the main issues with C and C++ (weird tricky behavior, super dangerous memory management, data races, concurrency, no/bad standard library), but then for considerably less gain tacks on a lot of functional programming ideas.

To be clear I'm not at all against functional programming. I just think most systems programmers aren't functional programmers because there really haven't been (and still aren't, as Rust isn't really functional) functional systems languages. And because Rust doesn't have the benefits of a lot of functional languages (super cool Lisp macros, etc.) I struggle to see the point beyond a style preference.

> I personally hate the C/C++ pattern of having to declare things and then initialize them later.

Like this for example. Do you hate it enough to pull all the statements out of a language? Feels like time that could be better spent somewhere else.

> Yes, what cost? I genuinely don't see a cost other requiring some people to get used to it, and I do see costs to the other approach.

The vast, vast majority of programmers come from languages with statements, and one of the main complaints about Rust is its learning curve. I think that's a significant cost.

Like, when I build an application in Java, Python, or C, "I had to initialize a variable using a function instead of an if/match statement" is nowhere on the pitfall list. That's not what our industry struggles with. It struggles with program complexity, logic errors, concurrency, and architectural confusion. "Everything is an expression" does nothing to address those issues. Lifetimes and the borrow checker do, and I think those departures are great investments for systems programming. Assigning from a match solves a problem I never had.

> I personally find the following to be so much nicer:

  let name = match someEnum {
    X => "...",
    Y => "...",
    // ...
  };
Really the only reason I use `switch` so much in C is that there's not a lot of polymorphism. Otherwise I greatly prefer to have this logic internal to the thing I'm matching on:

  let name = someEnum.get_name();
But again, because `match` is the way Rust does everything, look it's another match expression.

> (I also forgot to mention ternary ?: in C: it's also partly an acknowledgement that if-as-a-expression is useful.)

Aha well, as you might imagine I really dislike the ternary -- in really any language. They're hard to read, hard to edit, too easy to make a mess... basically all my arguments about `match`. Honestly just use an if.

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.

> I don't see much upside to emulating C-style manual tagged unions here.

Well I guess my point is mostly that I want to differentiate between matching on a value and implementing inside-out polymorphism. I don't like that they're smashed together in match because I think they're very different, and it leads to a lot of mixed up logic inside match expressions. The point isn't to emulate tagged unions so much as it is to separate concerns.

> [1]: This is a point where other people would complain about breaking with other practice for no good reason too (anyone who had used Haskell or OCaml or similar would find this system unnecessarily clunky).

And I would totally agree with them if Rust were a language in the vein of Haskell or OCaml, but it's not; it's a mainstream systems language. If Rust's pitch were, "Hey, do you like Haskell? You'll _LOVE_ Rust!" then they'd have a valid complaint. But Rust's pitch is, more or less, "Hey are you tired of C/C++ pitfalls or slow Python/Ruby/JS code? Give Rust a whirl!" When optimizing for familiarity and shallow learning curve you gotta pick an audience; you can't have it both ways.

> Focusing on a single value also doesn't generalize/scale: for instance, if one is making a decision that depends on more than one value:

  fn maybeAdd(x: Option<i32>, y: Option<i32>) -> Option<i32> {

    match (x, y) {
      (Some(left), Some(right)) => Some(left + right),
      (Some(left), None)        => Some(left),
      (None, Some(right))       => Some(right),
      (None, None)              => None
    }

  }
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:

  def maybe_add(self):
      if self.left and self.right:
          return self.left + self.right
      if self.left:
          return self.left
      if self.right:
          return self.right
I mean, I don't want to pick apart a clear hypothetical example. My point is that we already have a tool that can handle those scaling concerns: if and encapsulation/polymorphism. 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've personally found being exposed to many very different languages has helped inform the code I write in all others. Each new one definitely takes some getting used to, but my experience is that having touched several different paradigms has made me both more flexible (less attached to any particularly way of doing things) and a deeper understanding of even the "boring" languages: the trade-offs and "whys". There's value to things being the same, but there's also value in being able to break out of that mold and doing new things even if there's not an obvious benefit when focused on the old style.

Hah, tell me about it! You should've seen me the day I discovered there were different "types" of numbers in C (coming from Python)! There's a lot to learn, no question.

But there's a reason behind C's proliferation of numeric types, one that aligns with its purpose. I don't dislike `match`, etc. because I'd never worked with it before. I dislike it because it encourages so many bad practices in order to solve a problem I never had.

> I hope that you find Rust more and more enjoyable as you use it more

I do actually. Maybe you're getting this, but I identify more as a grump when it comes to programming so I gravitate towards grumpier languages (C, Go, etc.) so Rust's eagerness about some of the ML stuff is a little off-putting. But really, compared to integer promotion in C like, `match` is nothing :)


> 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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: