dtolnay/cxx

Provide builtin handling of exposing a C++ constructor

Open

#280 opened on Sep 4, 2020

View on GitHub
 (5 comments) (0 reactions) (0 assignees)Rust (4,472 stars) (253 forks)batch import
help wanted

Description

To the extent that constructors "return" a C++ type by value, they're not translatable to Rust because Rust moves (memcpy) are incompatible with C++ moves (which can require a move constructor to be called). Translating an arbitrary constructor to fn new() -> Self would not be correct.

The current workarounds are:

  • You can bind them unsafely with bindgen which assumes moving without a constructor call is okay;
  • You can use the "shared struct" approach when possible, which is safely movable in either language; or
  • You can include! a shim which does the construction behind a unique_ptr or similar.

That last approach would look something like:

// suppose we have a struct with constructor `ZeusClient(std::string)`

// in a C++ header:
std::unique_ptr<ZeusClient> zeus_client_new(rust::Str arg);

// in the corresponding C++ source file:
std::unique_ptr<ZeusClient> zeus_client_new(rust::Str arg) {
  return make_unique<ZeusClient>(std::string(arg));
}
// in the Rust cxx bridge:
extern "C++" {
    include!("path/to/zeus/client.h");
    include!("path/to/constructorshim.h");

    type ZeusClient;

    fn zeus_client_new(arg: &str) -> UniquePtr<ZeusClient>;
}

However, it would be very possible for us to handle at least the special case of types without a move constructor. In the following snippet, CXX would need to: recognize the new function name since that isn't otherwise a legal C++ function name, identify the Self type based on the extern type in return position of the function signature, statically verify there is no move constructor, and emit the constructor as an associated function ZeusClient::new to Rust.

extern "C++" {
    type ZeusClient = path::to::ZeusClient;  // the local def with a cxx::ExternType impl, via bindgen or handwritten

    fn new(arg: &str) -> ZeusClient;  // will verify there is no move constructor
}

Additionally we can consider supporting implicit UniquePtr construction of types which may or may not have a move constructor, with or without an ExternType impl.

extern "C++" {
    type ZeusClient;

    fn new(arg: &str) -> UniquePtr<ZeusClient>;  // okay even with move constructor
}

Contributor guide