On Apr 10, 2020, at 19:46, Chris Angelico firstname.lastname@example.org wrote:
On Sat, Apr 11, 2020 at 12:26 PM Soni L. email@example.com wrote:
They used to say that about Rust.
Rust prides itself on not having exception handling, forcing everyone to do explicit return value checking. I'm not sure how this factors into the current discussion, since it forces all these functions to return two values all the time (an actual return value and an OK/Error status),
That’s not quite true. A Result is effectively a ragged union of a value or an error; you don’t explicitly specify a return value plus an OK or an Error, you specify either an Ok(return value) or an Err(error value). Sure, technically that’s the same amount of information, but the way it’s done, together with the syntactic sugar provided by the language, makes a big difference, which I’ll get to below.
which you can quite happily do in Python if you so choose,
Now, this is the key point. You can do this in Python, but it won’t work like it does in Rust. It’s not just that you won’t get the compiler verification that you covered all code paths (it might be possible to come up with types that mypy can verify here; I haven’t tried…); it’s that your code will look like 2 lines of boilerplate for every line of code, and still won’t interoperate well with the stdlib or the vast majority of third-party libraries unless you explicitly wrap the hell out of everything.
There are multiple error handling schemes that can be made to work well—but as far as I know, nobody has discovered one that can be made to work well if it isn’t used ubiquitously. Python, Rust, Scala, Swift, C#, Haskell, etc. are all good languages to deal with errors in—they all picked one, implemented it well, and strongly encourage everyone to use it everywhere. Meanwhile, C++ has three different error handling schemes, two of which are implemented very nicely, but it’s a nightmare to use, because every complex C++ program has to deal with all three of them and map back and forth all over the place.
And that’s the problem with Soni’s proposal. It’s a good system, and designing a new language around it and letting an ecosystem build up around that language might well give you something better than Python. But that doesn’t mean changing the language now will give us something better. That only worked in JS because the status quo ante was so terrible and the benefits so big (not to mention Node coming along and completely changing the target surface for the language toward the end of the transition). But here, the status quo ante is fine for most people most of the time, and the benefits small, so we’re never going to get the whole ecosystem updated or replaced, so we’re never going to get the advantages.
and in fact has already been mentioned here. It's not an improvement over exception handling - it's just a different way of spelling it (and one that forces you to handle exceptions immediately and reraise them every level explicitly).
No it doesn’t. That _is_ true in Go, which gives us a perfect opportunity to compare. Let’s take something non-trivial but pretty simple and compare Pythonesque pseudocode for how you’d write it in different languages:
Python: val = func() val = func2(val) try: val = func3(val) except Error3: return defval return func4(val)
Rust: val = func()? val = func2(val)? val = match func3(val): Ok(v): v Err(v): return Ok(defval) return func4(val)
Go: val, err = func() if err: return None, err val, err = func2(val) if err: return None, err val, err = func3(val) if err: return default, None val, err = func4(val) if err: return None, err return val, None
Go does force you to explicitly handle and reraise every error, which not only drowns your normal flow in boilerplate, it makes it hard to notice your actual local error handling because it looks almost the same as the boilerplate. But Rust lets you bubble up errors unobtrusively, and your local error handling is immediately visible, just like Python. (I know this is a silly toy example, but read actual published code in all three languages, and it really does pan out like this.)
The Rust argument is that this isn’t just as good as Python, it’s better, ultimately because it’s easier for a compiler to verify and to optimize—but those are considerations that aren’t normally top priority in Python, and arguably wouldn’t be achievable in any realistic Python implementation anyway. On the other hand, Rust has less runtime information and dynamic control in the rare cases where you need to do tricky stuff, which are considerations that aren’t normally top priority in Rust, and arguably wouldn’t be achievable in any realistic Rust implementation anyway. But for the vast majority of code, they’re both great.