Python Rust Interop

I am wary of PyO3. The last time I seriously considered using it (a little under 2 years ago), it felt like basically every feature I looked at fell into one of two categories:

  • Either it already existed in rust_cpython (which PyO3 is a fork of)...
  • ...or I could easily come up with a way to invoke UB with it.

After reporting the issues I found, I even had to work to convince the author that the UB was a problem!

In the time since then, a major shitstorm happened with unsafe code in the actix crate by the same author. I would like to think he has learned his lesson, but for me, the damage has been done; I have difficulty bringing myself to use the crate without heavy review. (and there's simply too much of it for me to review!)

And even looking at it today, I see things that make sirens go off in my head:

fn try_from_mut<V: Into<&'v PyAny>>(
    value: V
) -> Result<&'v mut Self, PyDowncastError>

Surely this can be called twice on a &'v PyAny to get aliasing mutable references to Self?

impl<'a, T> std::convert::From<&'a T> for PyObject
where
    T: AsPyPointer;

Where did the lifetime go? Moreover, why on Earth does this even exist? I can only assume there's some way to get a &'a T back out of the PyObject, thereby allowing a use-after-free? (Edit: I just noticed the AsPyPointer bound. That might make this less bad)

self.inner
    .push_back(unsafe { mem::MaybeUninit::uninit().assume_init() });

mem::MaybeUninit::uninit().assume_init() is explicitly documented as UB even for types like u32 that have no invalid bit patterns.


I'm not certain that I have the time and energy to hunt down and report examples of all the ways that one can still invoke UB using this crate. I'd feel much more comfortable just contributing to rust_cpython to add any missing features.

6 Likes