DEV Community

Cover image for Rust's Option type... in Python

Rust's Option type... in Python

Tai Kedzierski on October 13, 2023

Cover Image (C) Tai Kedzierski How many times have you written/seen code like this: data = get_data() print(data.strip()) Enter fullscre...
Collapse
 
xtofl profile image
xtofl

My penny?

Mypy has caught many forgotten None-checks in my code. The Pythonic way seems to be to not bother at runtime, but type-hint, and check statically:

from typing import Optional
def maybe_fruit(i: int) -> Optional[str]:
  return "5" if i == 5 else None

len(maybe_fruit(5))
Enter fullscreen mode Exit fullscreen mode

This will fail to type check:

$ mypy m.py 
m.py:6: error: Argument 1 to "len" has incompatible
   type "str | None"; expected "Sized"  [arg-type]
Found 1 error in 1 file (checked 1 source file)
Enter fullscreen mode Exit fullscreen mode

Note: it does seem that mypy is not fool proof, though.

Another, run-time checked, approach is to implement the Maybe monad. Some articles exist, and some libraries do. E.g. skeptric.com/python-maybe-monad/.

Collapse
 
taikedz profile image
Tai Kedzierski

Static analysis is one of the ways to try and ferret things out ; it probably does rely on it identifying correctly that a function can in fact return None, which I presume is not fully trivial...

The technique I explored is more "forceful"...

It's an interesting Mybe-monad article you link ; it also links through to a Wikipedia section which... essentially describes the Rust Option itself....

en.wikipedia.org/wiki/Monad_(funct...

Collapse
 
michalmazurek profile image
Michal Mazurek

I like the or_default()

But you could do:

value = some_func() or "default value"
Enter fullscreen mode Exit fullscreen mode

The other option I think would be more pythonic, by just raising an exception instead of returning None.

def some_func(...):
      # ... something produces value
      if value is None:
            raise DoesNotExists("Some meaningful message")
      return value
Enter fullscreen mode Exit fullscreen mode

This whole Result handling in rust comes from lack of exceptions, python do have them, so why not use them?

Collapse
 
taikedz profile image
Tai Kedzierski • Edited

Indeed, this is completely unpythonic, but for a reason:

In most languages (python being one of a plethora), it is an extremely common mistake to just assume we have a value, until it blows up at runtime with a rogue None.

Hence the "billion dollar mistake," where so many developers forget to handle the None case.

By making it a type, and gating it inside an Option, the idea is to enforce "you cannot do anything with this unless you fully acknowledge that you could have nullity and you have explicitly chosen how to take action"

So instead of null-ness being maybe-or-maybe-not a possibility:

# oops, might be None, dunno, I thought we guaranteed something?
# whatevs, should be fine, didn't see it ...
data = get_data()
Enter fullscreen mode Exit fullscreen mode

the function explicitly says "hey, handle the case, or else!"

# I explicitly acknowledge that I am not handling None
#       because FML YOLO
data = get_data().unwrap()
Enter fullscreen mode Exit fullscreen mode

So really, yes, there are idiomatic pythonic wasy to handle None. Adding the type enforces "You cannot mistakenly fail to handle it"

Collapse
 
michalmazurek profile image
Michal Mazurek

Yeah, I agree I saw many cases where None was not handled properly. But if we require a specific thing to be returned only from a function, then we as well can ask for handling of None or writing a function in a way that None will never be returned.

On the other hand what floats your boat, if it works it's good!