rust-lang / rust-bindgen

Automatically generates Rust FFI bindings to C (and some C++) libraries.
https://rust-lang.github.io/rust-bindgen/
BSD 3-Clause "New" or "Revised" License
4.51k stars 702 forks source link

Support C++ exceptions without undefined behaviour #1208

Open Ekleog opened 6 years ago

Ekleog commented 6 years ago

Bindgen currently does not (cannot) support C++ exceptions, because C++ won't be able to unwind correctly into the rust code.

An idea to support this would be to emit a C++ wrapper for the C++ functions (see https://github.com/rust-lang-nursery/rust-bindgen/issues/1172#issuecomment-354831001 for the design idea). Emitting a C++ wrapper for functions would also allow to (by making it extern "C") remove all of bindgen's code for mangling C++ functions and make supporting new C++ compilers much easier.

A C++ wrapper function could look like this (syntax untested esp. the union one, and there are tricks with non-copyable return types, I can never remember the correct syntax for moving c++ stuff):

// Original function
ret_t fun(arg_t arg) {
    // ...
}

// Wrapper function
struct XXX_bindgen_result_ret_t {
    enum { OK, ERROR, UNKNOWN_ERROR } tag;
    union {
        ret_t ok;
        char * error_msg;
    } value;

    static XXX_bindgen_result_ret_t make_ok(ret_t r) {
        XXX_bindgen_result_ret_t ret;
        ret.tag = OK;
        ret.value.ok = r;
        return ret;
    }
    static XXX_bindgen_result_ret_t make_err(char * s) {
        XXX_bindgen_result_ret_t ret;
        ret.tag = ERROR;
        ret.value.error_msg = s;
        return ret;
    }
    static XXX_bindgen_result_ret_t make_unknown_error() {
        XXX_bindgen_result_ret_t ret;
        ret.tag = UNKNOWN_ERROR;
        return ret;
    }
};

extern "C"
XXX_bindgen_result_ret_t XXX_bindgen_function_fun(arg_t arg) {
    try {
        return XXX_bindgen_result_ret_t::make_ok(original_cpp_function(arg));
    } catch (exception e) {
        return XXX_bindgen_result_ret_t::make_err(e.message());
    } catch (...) {
        return XXX_bindgen_result_ret_t::make_unknown_error();
    }
}

Basically, with this C++ wrapper, bindgen takes control of the way FFI is performed, and can almost ignore the fact that it's a C++ compiler on which it's running.

The drawbacks are:

tmandry commented 6 years ago

+1. Even though much of the prevailing C++ wisdom says to avoid exceptions, the C++ Core Guidelines actually encourage their use. Especially from constructors, which have no other "standard" way of failing if they cannot establish their invariants.

On a tangential note, it seems like doing this would open the door to a whole host of possibilities, including calling methods on template classes. I know performance may not be ideal with these wrappers, but it would support "plug and play" integration with many existing APIs which use exceptions or templates. I can say as a lead for a decent-sized C++ codebase that this would be a game changer in terms of possible adoption of Rust, which is something I badly want.

tmandry commented 6 years ago

I assume this would leave it to the user to invoke the C++ compiler? Presumably, we are already using a C++ compiler in our build system somewhere, and many systems like Bazel already have a nice way of dealing with generated source (genfiles).

Doing some magic from build.rs sounds like a nice way to get started for some folks, but not a general solution.

Ekleog commented 6 years ago

Yes, I was thinking it would leave it to the user to compile and link the generated C++ source files, in the same way as they would be linking the “original” C++ source files. This means either compiling Rust as a library and then linking it into a C++ executable or compiling C++ as a library and linking it into the Rust executable, just like with the current bindgen.

alexreg commented 6 years ago

Any developments with this lately?