> On Apr 10, 2020, at 19:46, Chris Angelico <[email protected]> wrote:
>
> On Sat, Apr 11, 2020 at 12:26 PM Soni L. <[email protected]> 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.
An even more relevant illustration is JavaScript async code. Before there was a
consistent convention it was next to impossible to use different libraries
together without frequent bugs of the “callback never got called because we got
lost somewhere in the chain and there’s no way to tell where” variety, and that
couldn’t be fixed by just the language, it had to be fixed by every single
popular library being rewritten or replaced by a new library.
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.
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/AFMUGWRN7RK2DFI5QCY4V5TTXSGWSHGJ/
Code of Conduct: http://python.org/psf/codeofconduct/