Skip to main content

Rust, meet Python

Out of mere curiousity, I wanted to try out bindgen to generate a Rust interface to a C library. So I ran it against libpython, not really expecting that it would work, but you don't know until you tried, right? The fun part is: It does work, after defeating a few errors.

At first, I tried the obvious:

# bindgen /usr/include/python2.7/Python.h  -o src/ffi.rs

This fails because it cannot find limits.h:

/usr/include/limits.h:123:16: fatal error: 'limits.h' file not found
/usr/include/limits.h:123:16: fatal error: 'limits.h' file not found, err: true
thread 'main' panicked at 'Unable to generate bindings: ()', /checkout/src/libcore/result.rs:906:4
note: Run with `RUST_BACKTRACE=1` for a backtrace.

But the internet knows how to fix that:

# bindgen /usr/include/python2.7/Python.h  -o src/ffi.rs -- -isystem /usr/lib/llvm-3.8/lib/clang/3.8.1/include/
Error { repr: Custom(Custom { kind: Other, error: StringError("Cannot find binary path") }) }

Right, so there's still an error, but now src/ffi.rs exists, so I won't complain. Unfortunately rustc will, but here's the Rust code first:

use std::ptr;
use std::ffi::CString;

#[allow(warnings,dead_code)]
mod ffi;

use ffi::{Py_Initialize,Py_Finalize,PyRun_SimpleStringFlags};

fn main() {
    let script = CString::new("import this").unwrap();

    unsafe {
        Py_Initialize();
        PyRun_SimpleStringFlags(
            script.as_ptr(),
            ptr::null_mut()
        );
        Py_Finalize();
    }
}

This code calls Py_Initialize to boot the python interpreter, then runs a simple script consisting of "import this" to demonstrate that it works, and then shuts down the interpreter using Py_Finalize.

If we compile it using rustc -lpython2.7 src/main.rs (I haven't yet managed to get Cargo to pass the -l flag correctly, that's why I have to use rustc directly), we get an empty screen plus a complaint about FP_NORMAL being redefined:

--------------------------------------------------------- previous definition of the value `FP_NORMAL` here
...
1857 | } pub const FP_NAN : _bindgen_ty_4 = 0 ; pub const FP_INFINITE : _bindgen_ty_4 = 1 ; pub const FP_ZERO : _bindgen_ty_4 = 2 ; pub const FP_SUBNORMAL : _bindgen_ty_4 = 3 ; pub const FP_NORMAL : _bindgen_ty_4 = 4 ; pub type _bindgen_ty_4 = :: std :: os :: raw :: c_uint ; pub const _LIB_VERSION_TYPE__IEEE_ : _LIB_VERSION_TYPE = -1 ; pub const _LIB_VERSION_TYPE__SVID_ : _LIB_VERSION_TYPE = 0 ; pub const _LIB_VERSION_TYPE__XOPEN_ : _LIB_VERSION_TYPE = 1 ; pub const _LIB_VERSION_TYPE__POSIX_ : _LIB_VERSION_TYPE = 2 ; pub const _LIB_VERSION_TYPE__ISOC_ : _LIB_VERSION_TYPE = 3 ; pub type _LIB_VERSION_TYPE = :: std :: os :: raw :: c_int ; extern "C" {
     |                                                                                                                                                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `FP_NORMAL` redefined here
     |
     = note: `FP_NORMAL` must be defined only once in the value namespace of this module

error: aborting due to 5 previous errors

error: Could not compile `bindgentest`.

To learn more, run the command again with --verbose.

Seems like we're hitting an issue where enums collide with macros that have the same name, which is exactly the case here. So let's try and brute-force our way out, simply by blacklisting those types in bindgen:

# bindgen /usr/include/python2.7/Python.h -l python2.7 -o src/ffi.rs \
    --blacklist-type FP_INFINITE  \
    --blacklist-type FP_NAN       \
    --blacklist-type FP_ZERO      \
    --blacklist-type FP_SUBNORMAL \
    --blacklist-type FP_NORMAL    \
    -- -isystem /usr/lib/llvm-3.8/lib/clang/3.8.1/include/
Error { repr: Custom(Custom { kind: Other, error: StringError("Cannot find binary path") }) }

rustc should now work, and if we run the resulting main binary, we'll get:

# ./main
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Hooray!