Overview

Build Status Latest Version Rust Documentation

PyO3 is a Rust bindings for the Python interpreter.

Supported Python versions:

  • Python2.7, Python 3.5 and up

Supported Rust version:

  • Rust 1.20.0-nightly or later
  • On Windows, we require rustc 1.20.0-nightly

Usage

To use pyo3, add this to your Cargo.toml:

[dependencies]
pyo3 = "0.2"

Example program displaying the value of sys.version:

extern crate pyo3;

use pyo3::{Python, PyDict, PyResult};

fn main() {
    let gil = Python::acquire_gil();
    hello(gil.python()).unwrap();
}

fn hello(py: Python) -> PyResult<()> {
    let sys = py.import("sys")?;
    let version: String = sys.get("version")?.extract()?;

    let locals = PyDict::new(py);
    locals.set_item("os", py.import("os")?)?;
    let user: String = py.eval("os.getenv('USER') or os.getenv('USERNAME')", None, Some(&locals))?.extract()?;

    println!("Hello {}, I'm Python {}", user, version);
    Ok(())
}

Example library with python bindings:

The following two files will build with cargo build, and will generate a python-compatible library. For MacOS, "-C link-arg=-undefined -C link-arg=dynamic_lookup" is required to build the library. setuptools-rustincludes this by default. See examples/word-count. Also on macOS, you will need to rename the output from *.dylib to *.so. On Windows, you will need to rename the output from *.dll to *.pyd.

Cargo.toml:

[lib]
name = "rust2py"
crate-type = ["cdylib"]

[dependencies.pyo3]
version = "0.2"
features = ["extension-module"]

src/lib.rs

#![feature(proc_macro, specialization)]

extern crate pyo3;
use pyo3::{py, PyResult, Python, PyModule};

use pyo3::py::modinit as pymodinit;

// add bindings to the generated python module
// N.B: names: "librust2py" must be the name of the `.so` or `.pyd` file
/// This module is implemented in Rust.
#[pymodinit(rust2py)]
fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {

    #[pyfn(m, "sum_as_string")]
    // pyo3 aware function. All of our python interface could be declared in a separate module.
    // Note that the `#[pyfn()]` annotation automatically converts the arguments from
    // Python objects to Rust values; and the Rust return value back into a Python object.
    fn sum_as_string_py(_: Python, a:i64, b:i64) -> PyResult<String> {
       let out = sum_as_string(a, b);
       Ok(out)
    }

    Ok(())
}

// logic implemented as a normal Rust function
fn sum_as_string(a:i64, b:i64) -> String {
    format!("{}", a + b).to_string()
}

# fn main() {}

For setup.py integration, see setuptools-rust

Ownership and Lifetimes

In Python, all objects are implicitly reference counted. In Rust, we will use the PyObject type to represent a reference to a Python object.

Because all Python objects potentially have multiple owners, the concept of Rust mutability does not apply to Python objects. As a result, this API will allow mutating Python objects even if they are not stored in a mutable Rust variable.

The Python interpreter uses a global interpreter lock (GIL) to ensure thread-safety. This API uses a zero-sized struct Python<'p> as a token to indicate that a function can assume that the GIL is held.

You obtain a Python instance by acquiring the GIL, and have to pass it into some operations that call into the Python runtime.

PyO3 library provides wrappers for python native objects. Ownership of python objects are disallowed because any access to python runtime has to be protected by GIL. All APIs are available through references. Lifetimes of python object's references are bound to GIL lifetime.

There are two types of pointers that could be stored on Rust structs. Both implements Send and Sync traits and maintain python object's reference count.

  • PyObject is general purpose type. It does not maintain type of the referenced object. It provides helper methods for extracting Rust values and casting to specific python object type.

  • Py<T> represents a reference to a concrete python object T.

To upgrade to a reference AsPyRef trait can be used.