Need help translating int and const * char C++ to Rust via CXX

I am almost there.. I have refactored the C++ project I was originally working in. Rust is now the goal and I am wrapping the C++ dependency via CXX.. (And ConanRS)

but I am getting an error for what seems like basic translation

main.rs

use std::ffi::c_char;

#[cxx::bridge(namespace = "com::enserio")]
mod ffi {

    unsafe extern "C++" {
        include!("twsapi_grpc_server/include/twsapi-client.h");
        include!("twsapi_grpc_server/include/AvailableAlgoParams.h");
        include!("twsapi_grpc_server/include/AccountSummaryTags.h");
        include!("twsapi_grpc_server/include/Utils.h");

        type TWSApiClient;

        fn new_twsapi_client() -> UniquePtr<TWSApiClient>;
        // C++ VERSION = int32_t connect(const char * host, int32_t port, int32_t client_id = 0);
        fn init_connect(&self, host: *const c_char , port: i32, client_id: i32) -> i32;
    }
}

fn main() {
    println!("Starting TWSApiClient connect()");
    let client = ffi::new_twsapi_client();
    client.connect("127.0.0.1", 4002, 10);
    println!("Finished TWSApiClient connect()");
}

client.cpp

...
    int32_t TWSApiClient::init_connect(const char *host, int32_t port, int32_t client_id)
    {
...

The Error

  error[cxxbridge]: pointer argument requires that the function be marked unsafe
     ┌─ src/main.rs:16:32
     │
  16 │         fn init_connect(&self, host: *const c_char , port: i32, client_id: i32) -> i32;
     │                                ^^^^^^^^^^^^^^^^^^^ pointer argument requires that the function be marked unsafe

If I take away the *const c_char and instead try &c_char` .. it gives

/src/main.rs.cc:55:150: error: cannot convert ‘int32_t (com::enserio::TWSApiClient::*)(const char*, int32_t, int32_t)’ {aka ‘int (com::enserio::TWSApiClient::*)(const char*, int, int)’} to ‘int32_t (com::enserio::TWSApiClient::*)(const char&, int32_t, int32_t) const’ {aka ‘int (com::enserio::TWSApiClient::*)(const char&, int, int) const’} in initialization
  cargo:warning=   55 |   ::std::int32_t (::com::enserio::TWSApiClient::*init_connect$)(const char &, ::std::int32_t, ::std::int32_t) const = &::com::enserio::TWSApiClient::init_connect;
  cargo:warning=      |                                                                                                                                                      ^~~~~~~~~~~~
  exit status: 1

are my integers looking okay? should I just change to a std::string ?

As the error says, your function should be unsafe:

unsafe fn init_connect(&self, host: *const c_char , port: i32, client_id: i32) -> i32;
2 Likes

If you control the C++ side, then probably yes, and stick to a safe function, unless you have a good reason not to. string is safer in C++ too than a raw char pointer, and (for this reason) it's probably easier to translate correctly in an automatic way.

Two caveats apply, though:

  1. You can't get a CxxString by-value on the Rust side
  2. This will require you to have an std::string instance, so if all you have a const char *, then this will incur an allocation. (Given that this seems to be networking code, that cost is probably negligible to however long you have to wait for the network anyway.)
1 Like

im actually quite lost now.. because I went ahead trying to just get it to work by removing ALL function parameters... and still not working... seems to indicate one of the two sides of my code has a const... but I see no const on either side

my latest take

main.rs

#[cxx::bridge(namespace = "com::enserio")]
mod ffi {

    unsafe extern "C++" {
        include!("twsapi_grpc_server/include/twsapi-client.h");
        include!("twsapi_grpc_server/include/AvailableAlgoParams.h");
        include!("twsapi_grpc_server/include/AccountSummaryTags.h");
        include!("twsapi_grpc_server/include/Utils.h");

        type TWSApiClient;

        fn new_twsapi_client() -> UniquePtr<TWSApiClient>;
        //int32_t connect(const char * host, int32_t port, int32_t client_id = 0);
        fn init_connect(&self) -> bool;
    }
}

client.cpp

    bool TWSApiClient::init_connect()
    {

and header

    public:
        bool init_connect();

The Error

 cargo:warning=/home/emcp/Dev/git/JRGEMCP_Bootstrapping/bootstrap-rust-cpp-conan/target/debug/build/twsapi_grpc_server-f1ae9ab28f1e650b/out/cxxbridge/sources/twsapi_grpc_server/src/main.rs.cc: In function ‘bool com::enserio::com$enserio$cxxbridge1$TWSApiClient$init_connect(const TWSApiClient&)’:
  
cargo:warning=/home/emcp/Dev/git/JRGEMCP_Bootstrapping/bootstrap-rust-cpp-conan/target/debug/build/twsapi_grpc_server-f1ae9ab28f1e650b/out/cxxbridge/sources/twsapi_grpc_server/src/main.rs.cc:54:96: error: cannot convert ‘bool (com::enserio::TWSApiClient::*)()’ to ‘bool (com::enserio::TWSApiClient::*)() const’ in initialization
 
cargo:warning=   54 |   bool (::com::enserio::TWSApiClient::*init_connect$)() const = &::com::enserio::TWSApiClient::init_connect;
  cargo:warning=      |                                                                                                ^~~~~~~~~~~~
  exit status: 1

Your init_connect() takes &self, which is a pointer-to-immutable Self. It's the equivalent of C++'s const member function. However, on the C++ side, you don't declare it as const. You need to either:

  • change the C++ declaration to bool init_connect() const if you don't need the function to mutate the object, or
  • change the Rust declaration to fn init_connect(&mut self) -> bool if you do need mutable access.
4 Likes

okay wow that was it.. I am learning so much of BOTH languages now.. thank you to you and @alice !

In general, C++ is mutable-by-default, whereas Rust is immutable-by-default.

1 Like

I successfully changed the reference.. but now seems another error has popped up. I ended up having to do this

  --- stderr

  error[cxxbridge]: needs a cxx::ExternType impl in order to be used as a non-pinned mutable reference in signature of `init_connect`
     ┌─ src/main.rs:10:9
     │
  10 │         type TWSApiClient;
     │         ^^^^^^^^^^^^^^^^^ needs a cxx::ExternType impl in order to be used as a non-pinned mutable reference in signature of `init_connect`


  error[cxxbridge]: mutable reference to opaque C++ type requires a pin -- use `self: Pin<&mut TWSApiClient>`
     ┌─ src/main.rs:14:25
     │
  14 │         fn init_connect(&mut self) -> bool;
     │                         ^^^^^^^^^ mutable reference to opaque C++ type requires a pin -- use `self: Pin<&mut TWSApiClient>`

Final working reference

        fn init_connect(self: Pin<&mut TWSApiClient>) -> bool;

Yeah, that's because C++ has move constructors, so in general when you transfer ownership of a C++ object, it might need to run arbitrary user-defined code in order to maintain internal invariants (this is how C++ can represent self-referential types, for example).

Mutable references can give away ownership in both directions (e.g. simple assignment or mem::replace()), so the Rust side needs to make sure it doesn't actually move the object by-value. This is what Pin is for. It's a marker type that restricts regular mutable references, so they can't be (safely) used for moving the referent value.

Great to know..

I successfully compile the CXX , but seeing the following

fn main() {

    println!("Starting TWSApiClient connect()");
    let client = ffi::new_twsapi_client();

    client.init_connect(4002, 333);
    println!("Finished TWSApiClient connect()");

}


error[E0599]: no method named `init_connect` found for struct `UniquePtr<TWSApiClient>` in the current scope
  --> src/main.rs:22:12
   |
22 |     client.init_connect(4002, 333);
   |            ^^^^^^^ method not found in `UniquePtr<TWSApiClient>`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `twsapi_grpc_server` due to previous error

I will recheck my work vs. the Demo cxx/main.rs at master · dtolnay/cxx · GitHub

Since i got the basic no-parameters working.. I re-added the integers successfully.