Hacker News new | ask | show | jobs
by missinglugnut 359 days ago
Steve, I know you're an authority on the language but you've dismissed the point being made here without engaging with it.

Return is a statement in the minds of most programmers, but an expression in the language. That was a very pragmatic decision that required an unintuitive implementation. As a result, we've got this post full of code that is valid to the compiler but doesn't make a lick of sense to most programmers reading it.

2 comments

> Return is a statement in the minds of most programmers

I would take issue with this, sure, for a lot of people, they may be bringing assumptions over from languages where assignment is a statement. That doesn't make them correct.

> required an unintuitive implementation

To some people, sure. To others, it is not unintuitive. It's very regular, and people who get used to "everything is an expression" languages tend to prefer it, I've found.

> ...people who get used to "everything is an expression" languages tend to prefer it, I've found.

This feels awkward as my mental model is that in "everything is an expression" languages you simply DO NOT offer "return" (and, if you do, it must be mapped to bottom and do something insane like throw an exception... but, like, if you are really used to using such a language, you'd never let yourself type a "return", as the entire concept feels icky and wrong in such a language).

Ruby is an "everything is an expression" language and it has return, and idiomatically it's used identically to Rust: for early returns.

It is of course not statically typed.

Ruby is complex as it does the thing I said: 1) return is bottom, and so you can't have return "statement"--most people call it a statement even though it is a "void value expression"... but, like, once the type of an expression becomes sufficiently encoded in the syntax, how is it not a statement?--in a context where an expression would go; and 2) it isn't even the same thing as what Rust is doing, as it is a non-local construct that is effectively throwing an internal form of exception, allowing you to "return" from a function far up the call stack, and so it's rules are a bit more regular and useful: Ruby is coming at the notion of almost everything is an expression so as to allow expressions that look like statements, and that's a very different motivation than the syntax we are seeing here in Rust.
> people who get used to "everything is an expression" languages tend to prefer it, I've found

I.e., if we bias our sample to the data points proving our point then our point is proven. It's like that quip about how every car insurance company can simultaneously claim "people who switched saved hundreds of dollars in average."

I also like "everything is an expression" languages, but I don't think that's a fantastic argument.

The original claim that the was responding to in this thread was that `return` as an expression didn't fit in well with Rust, and he said that it did. He also cited how far more things in Rust are expressions than statements, so it stands to reason that people who program in Rust are familiar with those styles of language. It sounds like you're arguing that it makes more sense to judge whether return makes sense as an expression in Rust based on the expectations of people who aren't as familiar with expression-based languages (and therefore aren't super familiar in Rust), which doesn't make a ton of sense to me.
We've been going down this road for a long time now. E.g. "throw" is a (void-typed) expression in C++ already for similar reasons, although it doesn't go far enough without a proper bottom type. C# took it further and added the type so that you can write things like e.g. `x = y ?? throw new Error(...)`. There's no obvious reason why "return" should be conceptually different.

A better question at this point, arguably, is why there should even be an expression/statement distinction in the first place. All imperative statements can be reasonably and sensibly represented as expressions that produce either () or "never". Semicolon then is just a sequencing operator, like comma in C++.

I considered whether to mention C# in this thread but initially decided against it because it doesn't actually have a bottom type. You can't assign a throw expression to an implicitly-typed variable, or anywhere else where it is needed to infer a type. You can only use it in places where a type is already known so the throw expression can be coerced to it.

In fact, I recently ran into the finding that you can't use it with logical operators either: `return myBool && throw...` doesn't work. I assume that's because && can be used with many types even if the first operand is a bool, but the compiler error message doesn't explain that, it just says throw is an invalid token here, and if you parenthesize it, it says a throw expression can't be used in this context. I was very surprised by this seemingly arbitrary limitation.

Yes, this is a good example of how not having the bottom type actually makes things messier overall. Without it you have to make those case-by-case hacks. With it, all the stuff that people actually want to write and that makes sense "just works", and sure, there's more stuff that you could write that doesn't make sense as well, but it's not something that people might end up writing accidentally by mistake and get wrong behavior.