GithubHelp home page GithubHelp logo

dtolnay / cxx Goto Github PK

View Code? Open in Web Editor NEW
5.5K 5.5K 312.0 7.09 MB

Safe interop between Rust and C++

Home Page: https://cxx.rs

License: Apache License 2.0

Rust 85.43% C++ 12.16% Starlark 1.41% Dockerfile 0.15% Shell 0.02% JavaScript 0.44% CSS 0.09% Makefile 0.02% Handlebars 0.04% TeX 0.24%

cxx's People

Contributors

adamchalmers avatar adetaylor avatar benesch avatar capickett avatar dtolnay avatar geekbrother avatar hlopko avatar illicitonion avatar isg avatar jgalenson avatar joverwey avatar keith avatar leonmattheskdab avatar lopopolo avatar mforster avatar myronahn avatar nehliin avatar paandahl avatar permosegaard avatar phil-opp avatar philipcraig avatar rinon avatar rookboom avatar sayrer avatar snowp avatar trondhe avatar uebelandre avatar vi avatar wravery avatar yujincheng08 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cxx's Issues

Decide header include path

The current setup exposes our C++ header as:

#include "cxxbridge/cxxbridge.h"

This isn't great because of the repetition, and also because all the symbols in the header are namespaced under rust:: so an include path with rust in the name somewhere may make more sense.

Something like:

  • #include "cxxbridge/rust.h"
  • #include "rust/cxxbridge.h"
  • #include "rust/cxx.h"

Multiple FFI blocks referencing types from one another

I have two files requiring FFI, json.rs and basevalue.rs. I'd like to keep the FFI blocks local to those files, instead of having a central ffi.rs, but right now this is not possible.

My FFI block in basevalue.rs.

#[cxx::bridge(namespace = base)]
pub mod ffi {
    extern "C" {
        include!("base/values_rust.h");
        type RawOptionalBaseValue;
        fn RustOptionalBaseValueSetString(v: &mut RawOptionalBaseValue, value: &str);
        fn RustOptionalBaseValueSetBool(v: &mut RawOptionalBaseValue, val: bool);
        // etc.
    }
}

My FFI block in json.rs:

#[cxx::bridge(namespace = base)]
pub mod ffi {
    extern "Rust" {
        pub fn decode_json(
            bv: UniquePtr<RawOptionalBaseValue>,
            json: &str
        ) -> bool;
    }
}       

Note that the json.rs block references a type from the basevalue.rs block.

This gives

error[cxxbridge]: unsupported type

which is unsurprising, and I imagine is really hard to solve.

(The exact code above is significantly simplified so obviously don't try to build it; if this issue isn't as "known" as I expect, let me know and I'll make a minimal test case).

Nonthrowing constructors for C++ binding of Rust std types

In some constructors, cxx uses exceptions to indicate failure. Many codebases specifically disable exception support for C++ for a variety of reasons (Firefox and Chromium amongst them).

In the long term, I would hope we could design a system that would allow these errors to be caught in both exception-based C++ and -fno-exceptions code, but for now even just printing the error message and abort()ing would at least be a start.

In summary:

  • Do you have plans to support non-exception C++, or would you be open to such plans?
  • In the short term, would you be open to a configuration switch that would generate fatal errors rather than throwing an exception?

How would commctrl objects be used in CXX?

I am currently writing some Rust code that will be called by external C code. This C code will call a Rust function and pass in a void * that represents a commctrl window. After some processing, this Rust function will call a C++ function that displays a message box (note: I plan to use wxWidgets to display this message box, among other things. Because of the way wxWidgets is programmed (inheritance), I cannot import the required function into Rust.)

Essentially, my use case is this, in terms of code.

gui/about.h:

void ShowMessageBox(void *parent, int otherData);

gui/about.cpp:

#include <wx/wx.h>

void ShowMessageBox(void *parent, int otherData) {
  wxDialog *dummy = new wxDialog();
  dummy->SetHWND(parent);

  wxString result;
  result.Printf( wxT("The result is %d."), otherData); 
  wxMessageBox(result, "Result", wxOK | wxICON_INFORMATION, dummy);
}

src/lib.rs:

use std::ffi::c_void;

#[cxx::bridge]
mod ffi {
  extern "C" {
    include!("gui/about.h");
    fn ShowMessageBox(parent: *const c_void, other_data: i32);
  }
}

#[no_mangle]
pub unsafe extern fn function_that_is_called_from_c(parent: *const c_void, other_data: i32) {
  // processing and such
  ffi::ShowMessageBox(parent, other_data);
}

Considering that raw pointers probably won't be added to this crate, how would a window handle be communicated across the Rust/C++ boundary?

Support passing function pointers across the FFI

i.e. function parameters with the type fn(...) -> ....

#[cxx::bridge]
mod ffi {
    extern "Rust" {
        fn f(arg: u8, callback: fn(u8));
    }
}

fn f(arg: u8, callback: fn(u8)) {
    callback(256 - arg);
}

C support

It would be great to provide a feature or something for this crate so that it could generate safe C bindings rather than C++. I would be willing to at least take a look at this; advice on how to proceed would be welcome.

-std=c++11 is not required on MSVC

Building on x86_64-pc-windows-msvc currently emits this warning:

warning: cl : Command line warning D9002 : ignoring unknown option '-std=c++11'

According to https://docs.microsoft.com/en-us/cpp/build/reference/std-specify-language-standard-version?view=vs-2019, C++11 is the default with flags /std:c++14 and /std:c++17 for the newer standards.

We should find a way to pass -std=c++11 only when it's required, such as on x86_64-apple-darwin where C++98 is the default. https://opensource.apple.com/source/clang/clang-800.0.42.1/src/tools/clang/www/cxx_status.html

running: "cc" "-O0" "-ffunction-sections" "-fdata-sections" "-fPIC" "-g" "-fno-omit-frame-pointer" "-m64" "-Wall" "-Wextra" "-o" "/Users/travis/build/dtolnay/cxx/target/debug/build/cxx-f198c1b742072022/out/src/cxxbridge.o" "-c" "src/cxxbridge.cc"

In file included from src/cxxbridge.cc:1:
src/../include/cxxbridge.h:9:18: warning: 'final' keyword is a C++11 extension [-Wc++11-extensions]
class RustString final {
                 ^

UniquePtr of pure virtual opaque extern C type doesn't compile

Repro:

// lib.rs

#[cxx::bridge]
mod ffi {
    extern "C" {
        include!("repro.h");

        type Virtual;
        fn make() -> UniquePtr<Virtual>;
    }
}
// repro.h

#pragma once

struct Virtual {
  virtual void function() = 0;
};

std::unique_ptr<Virtual> make();

This fails to compile with:

repro.rs.cc: In function ‘void cxxbridge02$unique_ptr$Virtual$new(std::unique_ptr<Virtual>*, Virtual*)’:
repro.rs.cc:599:85: error: invalid new-expression of abstract class type ‘Virtual’
  |   new (ptr) ::std::unique_ptr<Virtual>(new Virtual(::std::move(*value)));
  |                                                                       ^
repro.h:11:8: note:   because the following virtual functions are pure within ‘Virtual’:
  | struct Virtual {
  |        ^~~~~~~
repro.h:12:16: note: 	‘virtual void Virtual::function()’
  |   virtual void function() = 0;
  |                ^~~~~~~~

The error is inside of the shim we emit for UniquePtr::new:

void cxxbridge02$unique_ptr$Virtual$new(::std::unique_ptr<Virtual> *ptr, Virtual *value) noexcept {
  new (ptr) ::std::unique_ptr<Virtual>(new Virtual(::std::move(*value)));
}

But we should just not emit that shim for opaque C types. The signature of UniquePtr::new is fn(T) -> UniquePtr<T> which is uncallable when T is an opaque C type because those can never exist by value on the Rust side of the bridge.

error: custom attributes cannot be applied to modules

➜  demo-rs git:(master) cargo run  
   Compiling proc-macro2 v1.0.8
   Compiling unicode-xid v0.2.0
   Compiling syn v1.0.14
   Compiling cc v1.0.50
   Compiling anyhow v1.0.26
   Compiling unicode-segmentation v1.6.0
   Compiling unicode-width v0.1.7
   Compiling termcolor v1.1.0
   Compiling codespan v0.7.0
   Compiling codespan-reporting v0.7.0
   Compiling quote v1.0.2
   Compiling link-cplusplus v1.0.1
   Compiling cxx v0.1.2 (/home/eh/repos/other/cxx)
   Compiling thiserror-impl v1.0.11
   Compiling cxxbridge-macro v0.1.2 (/home/eh/repos/other/cxx/macro)
   Compiling thiserror v1.0.11
   Compiling cxxbridge-demo v0.0.0 (/home/eh/repos/other/cxx/demo-rs)
error[E0658]: custom attributes cannot be applied to modules
 --> demo-rs/src/main.rs:1:1
  |
1 | #[cxx::bridge(namespace = org::rust)]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: for more information, see https://github.com/rust-lang/rust/issues/54727

error: aborting due to previous error

For more information about this error, try `rustc --explain E0658`.
error: could not compile `cxxbridge-demo`.

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

'cargo test' doesn't link on macOS

error: linking with `cc` failed: exit code: 1
  |
  = note: "cc" "-m64" "-L
...
  = note: ld: library not found for -lstdc++
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

I think it's trying to link libstdc++ (GNU), but macOS doesn't ship it anymore (it ships libc++, the Clang one). So, it should be trying to link with -lc++instead.

Consider taking by-value structs by rvalue reference

Currently for something like:

mod ffi {
    struct Struct {
        s: String,
        p: UniquePtr<CxxString>,
    }

    extern "Rust" {
        fn f(s: Struct);
    }
}

the public function signature we emit on the C++ side would be:

void f(Struct s);

But in some varieties of calling code, the above signature might require doing an unnecessary deep copy of the fields of Struct. It may be better to emit instead:

void f(Struct &&s);

Consider emitting cargo:rerun-if-changed line for argument of cxx::Build::bridge

https://docs.rs/cxx/0.1.2/cxx/struct.Build.html#method.bridge could automatically do:

println!("cargo:rerun-if-changed={}", rust_source_file);

According to https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script:

rerun-if-changed=PATH is a path to a file or directory which indicates that the build script should be re-run if it changes (detected by a more-recent last-modified timestamp on the file). Normally build scripts are re-run if any file inside the crate root changes, but this can be used to scope changes to just a small set of files.

Prevent passing &mut Box<T> from Rust to C++

We should detect and reject the following:

mod ffi {
    struct Struct {
        t: Box<T>,
    }

    extern "C" {
        fn f(x: &mut Struct);
    }
}

Rust's Box is not nullable, and the above makes it too easy for innocuous-looking C++ code to std::move out of the Box and hit UB when it's later dropped by Rust.

Instead, for data structures containing Box that ever pass from Rust to C++ by mutable reference, we should enforce that the Box is held in Option so it can be nulled out during moves. Option<Box<T>> has a guaranteed representation for Sized T.

mod ffi {
    struct Struct {
        t: Option<Box<T>>,
    }

    ...
}

An option for cxxbridge to output the full cxxbridge.h header

A typical Bazel setup will have something like this generated configuration:

alias(
    name = "cargo_bin_cxxbridge",
    actual = "//rust_bindings/cargo/vendor/cxxbridge-cmd-0.1.2:cargo_bin_cxxbridge",
)

This works fine for the two outputs that cxxbridge makes right now, but it would be nice for it to have an option to output the full cxxbridge.h header as well. Otherwise, it will require fishing around in vendor directories and adding extra Bazel rules to make the file visible. By using the alias for the command to produce the file, the reference will stay stable across upgrades, and cxxbridge can make internal changes without breaking builds.

I added this function to include.rs:

pub fn get_full_cxxbridge() -> &'static str {
	return HEADER
} 

and that seemed fine, but I wasn't sure what to do about the required input argument (I also couldn't test anything, because of #19). Here's a strawman:

     /// Emit header with declarations only
     #[structopt(long)]
     header: bool,
+
+    /// Emit full cxxbridge header
+    #[structopt(long)]
+    cxxbridge: bool,
+
 }
 
 fn main() {
     let opt = Opt::from_args();
+    if opt.cxxbridge {
+        let _ = io::stdout().lock().write_all(gen::include::get_full_cxxbridge().as_ref());
+        return;
+    }
     let gen = if opt.header {
         gen::do_generate_header
     } else {

which works if I call it with a dummy input file:

cargo run -- foo.rs --cxxbridge

Support associated methods

Currently only freestanding functions are supported by this crate, not methods.

I like the approach of using self in the foreign function parameter list to signify a method receiver:

#[cxx::bridge]
mod ffi {
    extern "C" {
        type C;
        fn f(self: &C);
    }

    extern "Rust" {
        type R;
        fn g(self: &R);
    }
}

struct R {...}

impl R {
    fn g(&self) {...}
}

This would expose the methods r.g() to C++ and c.f() to Rust.

The above syntax would require rustc 1.43 or newer for rust-lang/rust#68728.

How to solve relocatable semantics between Rust and C++?

Semantically, C++ types cannot be relocated because they may contain pointers to themselves or chains of objects that eventually point back to themselves. Rust assumes that all types can be relocated (e.g. by memcpy). Pin<T> can help with this problem when working with such FFI, but is there any way in which this crate can assist bridging these semantic differences?

An example using structs from outside the "ffi" module

I can't use this crate yet, because of its compiler requirements. But the examples left me wondering how to refer to structs that are not in the ffi module.

Is there a way to make a struct defined in another module, like this example:

pub struct NamedPoint {
  name: String,
  pub x: i32,
  pub y: i32,
}

visible to both languages, in the way that "SharedThing" is used in the example? Maybe a new annotation is needed for that?

Design C++ namespace scheme

#5 and #8 call out that prefixed names like RustString and RustBox are not idiomatic and we should be placing these types in a namespace instead.

Thinking only of the code that I would want downstream to be writing, the style I find most readable is something like:

void f(rust::Str s, rust::Box<Thing> b);

Some questions for @pravic and @slurps-mad-rips or anyone else:

  • I have the feeling it isn't appropriate for me to declare a top-level rust namespace with familiar type names like Box in it. That namespace "belongs" to the official Rust project. Is that feeling correct or is it not so bad?

  • I can define our types in cxxbridge::rust and recommend an import downstream to bring rust in scope when there isn't a collision with something else. Are C++ style guides generally okay with that or would people end up forced to write out cxxbridge::rust::... all over the place?

  • For the import, as far as I can tell it would be spelled using rust = cxxbridge::rust;. Is that right? There doesn't seem to be a way to avoid repeating "rust" such as using cxxbridge::rust;.

    main.cc:11:18: error: using declaration cannot refer to a namespace
    using cxxbridge::rust;
          ~~~~~~~~~~~^
    main.cc:11:18: error: namespace ‘cxxbridge01::rust’ not allowed in using-declaration
       11 | using cxxbridge::rust;
          |                  ^~~~
  • Any other thoughts on the namespace scheme? Thanks!

Support rust `Option` and C++ `std::optional`

It would greatly simplify API development if automatic Option<T> conversion was supported.

Since C++17 there is std::optional<T>, which would allow a more or less direct translation of supported types.

I had a look at #67 and the ideas and work done by @myronahn for Vec seems to be similar to what would be needed for supporting Option so maybe one could build on that...

Wreturn-type-c-linkage warning from Clang

The demo works but emits a warning.

% cargo run --manifest-path demo-rs/Cargo.toml
   Compiling proc-macro2 v1.0.8
   Compiling unicode-xid v0.2.0
   Compiling cc v1.0.50
   Compiling syn v1.0.14
   Compiling unicode-segmentation v1.6.0
   Compiling anyhow v1.0.26
   Compiling unicode-width v0.1.7
   Compiling termcolor v1.1.0
   Compiling codespan v0.7.0
   Compiling codespan-reporting v0.7.0
   Compiling link-cplusplus v1.0.1
   Compiling cxx v0.1.2
   Compiling quote v1.0.2
   Compiling thiserror-impl v1.0.9
   Compiling cxxbridge-macro v0.1.2
   Compiling thiserror v1.0.9
   Compiling cxxbridge-demo v0.0.0
warning: target/debug/build/cxxbridge-demo-5144ad694551d3cf/out/demo-rs/src/main.rs.cc:94:20: warning: 'org$rust$cxxbridge01$get_name' has C-linkage specified, but returns user-defined type 'const std::string &' (aka 'const basic_string<char, char_traits<char>, allocator<char> > &') which is incompatible with C [-Wreturn-type-c-linkage]
warning: const std::string &org$rust$cxxbridge01$get_name(const ThingC &thing) noexcept {
warning:                    ^
warning: 1 warning generated.
    Finished dev [unoptimized + debuginfo] target(s) in 16.65s
     Running `target/debug/cxxbridge-demo`
this is a demo of cxx::bridge
called back with r=333
done with ThingC

Deconflict top level build directory and bazel BUILD file

Having both causes silly trouble on case insensitive filesystems. 🤦‍♀

% git status
HEAD detached at origin/master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	deleted:    BUILD

no changes added to commit (use "git add" and/or "git commit -a")

% git checkout -- BUILD

% git status
HEAD detached at origin/master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	deleted:    build/rust.bzl

no changes added to commit (use "git add" and/or "git commit -a")

% git checkout -- build/rust.bzl

% git status
HEAD detached at origin/master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	deleted:    BUILD

no changes added to commit (use "git add" and/or "git commit -a")

Consider allowing one cxxbridge cli invocation to emit both header and cc

As currently implemented, two invocations of the cli command are required and the output goes to stdout.

$ cxxbridge path/to/lib.rs --header > generated.h
$ cxxbridge path/to/lib.rs > generated.cc

In some Starlark-based environments (or others) it turns out to be desirable to emit both outputs from a single invocation.

genrule(
    name = "gen",
    src = "...",
    cmd = "$(exe //path/to:cxxbridge-cmd) ${SRC}",
    outs = {
        "header": ["generated.h"],
        "source": ["generated.cc"],
    },
)

Don't rely on the target directory being named 'target'

Currently, paths::target_dir() tries to find the target directory by looking at the name.
This has two problems:

  1. The user might have specified a different target directory by using the Cargo config option target-dir (https://doc.rust-lang.org/cargo/reference/config.html)
  2. The target directory might be called target, but actually be a symlink to something else that isn't called target - and canonicalize resolves symlinks.

In both cases, paths::target_dir() fails.

What is the rationale for not putting all the generated code etc. in the OUT_DIR or a subdirectory thereof directly? If the latter is acceptable, I'm happy to submit a pull request changing the behavior...

Support f32↔️float, f64↔️double

This should be a simple extension, just hasn't been implemented yet.

If C++ allows float and double to mean something other than f32 and f64, we'll want to emit some static assertions to ensure we're in the case where the meaning is the same as in Rust.

Shared structs with type parameters

We should support structs that have type parameters and translate them to an ABI-compatible class template in C++.

#[cxx::bridge]
mod ffi {
    struct Generic<T> {
        wow: T,
    }

    extern "C" {
        fn f(g: Generic<String>);
    }
}

C++ std::vector<T> and Rust std::vec::Vec<T> support

I've hacked in some preliminary std::vector<T> support to allow passing C++ vectors to Rust.

https://github.com/myronahn/cxx/tree/master

Right now it works for std::vector<uint8_t> since that was my primary need but it would be very easy to add in support for all the basic types.

Adding support for general types is a bit harder as the orphan rule is keeping me from defining macro-expanded trait impls for both the Vector class and the UniquePtr class. I'm currently wondering what the best workaround would be for this.

It would be great to get a once-over from you if you have any spare time to take a look. Is it worth continuing work on this, or are you in the middle of implementing something bigger/better?

Thanks in advance.

I wonder how to use "cxx" with 3rd C++ interface?

Let me make it clear. I got a project which need to use a 3rd C++ library(with lib and header).
To use it , I have to do so in C++:

#include <"ThirdPartyInterface.h">
class CMyClass: public ThirdPartyInterface{
virtual callbackFrom3rdPartyInterface();// I need to implement it. 
};

Support opaque C++ types that are not structs

For example we might have:

// c++

using Obj = void*;
// rust

#[cxx::bridge]
mod ffi {
    extern "C" {
        type Obj;
        fn f(obj: &Obj);
    }
}

Currently this fails to compile because we forward declare opaque C types assuming they are structs.

target/debug/build/cxxtest/out/src/main.rs.cc:5:8: error: using typedef-name ‘using Obj = void*’ after ‘struct’
    5 | struct Obj;
      |        ^~~

How to include the generated header?

Sorry if it's a duplicate, but I didn't find anything helpful.

I use cargo build script. I included the generated header using this path: target/cxxbridge/src/lib.rs.h. But during a clean build this header isn't there when a file with this include compiles.

In the example I see there's #include "demo-rs/src/main.rs.h" and it's magically works with cargo build. So I guess I skipped something important, which does this generating of header. But what?

Can't use raw pointers

I'm guessing this is just a case of things not being implemented yet, but what is required to let C functions return raw pointers?

For example this code

use std::os::raw::c_char;

#[cxx::bridge]
mod ffi {
    extern "C" {
        include!("vendor/bzip2/bzlib.h");

        fn BZ2_bzlibVersion() -> *const c_char;
    }
}

Fails to compile with

   Compiling cxx-experiment v0.1.0 (/home/michael/Documents/cxx-experiment)
error: unsupported type
 --> src/lib.rs:8:34
  |
8 |         fn BZ2_bzlibVersion() -> *const c_char;
  |                                  ^^^^^^^^^^^^^

warning: unused import: `std::os::raw::c_char`
 --> src/lib.rs:1:5
  |
1 | use std::os::raw::c_char;
  |     ^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

error: aborting due to previous error
error: could not compile `cxx-experiment`.
To learn more, run the command again with --verbose.

Expose snake case type aliases

As suggested in #46 (comment).

some people may want idiomatic C++ naming for the types as well. It wouldn't hurt to create type aliases that are all snake case. (i.e., template <class T> using box = Box<T>;)

Initializing values inside of `RustBox` needs to avoid ADL

Because RustBox uses new (this->deref_mut()) T(val), anyone on the C++ side of things could inject custom code.

To fix this, use ::new instead of new.

(There are other API issues on the C++ side of things, but I'll file that as a separate issue)

C++ ergonomic usage suggestion

As mentioned in #5, there is need for better namespace usage. Additionally, I understand the need for ABI compatibility, and quickly hacked up an improved API suggestion on the C++ side. This will of course required additional work on the rust side of things, but wanted to get feedback first.

Below, is a C++17 (though backportable to C++11 with minimal effort) API mockup for an improved API. Among the changes are

  • Renaming types to use the C++ standard style
  • Placing everything inside an inline namespace named v1
  • Made the internal representation of str actually private. If its needed in generated code, I recommend using the friend keyword.
  • Left a note that the from_raw and into_raw could be turned into construction and explicit casting to a more opaque class (this class could be made into a friend of box, or vice versa. This reduces the need for unnecessary encapsulation, and also allows the type system to take care of some unsafety for us). For now I've just used a unique_ptr, which will cause a compiler error if an opaque type would be destructed.
  • Improved unnecessary code generation by using iosfwd instead of iostream. I recommend looking at what happens per translation unit when including iostream. It's not pretty, but iosfwd allows us to forward declare the ostream insertion operators without incurring this overhead.
  • Changed all cast operators to explicit. This saves more headaches in the long run, I assure you.
  • Box now has an explicit operator bool, so it can be used in if() statements, and is more in line with unique_ptr, which is the closest thing on the C++ side.
  • box also can now do in-place construction, saving unnecessary copies. We also construct correctly, via std::addressof and ::new. Adopting this API would allow #6 to be completed.

I also know quite a bit about build systems and the C++ language (and API design) in general, so feel free to ask any questions :)

#ifndef CXXBRIDGE_H
#define CXXBRIDGE_H

#include <cstdint>
#include <cstddef>

#include <type_traits>
#include <utility>
#include <iosfwd>
#include <string>
#include <array>

namespace cxxbridge {
inline namespace v1 {
namespace rust {

struct string final {
  string (string const&) noexcept;
  string (string&&) noexcept
  string () noexcept;
  ~string () noexcept;

  string (std::string const&);
  string (std::string&&);
  string (char const*);

  string& operator = (string const&) noexcept;
  string& operator = (string&&) noexcept;

  void swap (string&) noexcept;

  explicit operator std::string () const;

  char const* data () const noexcept; // no null terminator
  std::size_t size () const noexcept;
private:
  std::array<intptr_t, 3> repr;
};

class str final {
  // internal is private and must not be used other than by generated code.
  // Not really ABI compatible with &str, codegen will translate to
  // cxx::rust_str::RustStr
  struct internal {
    char const* ptr;
    std::size_t len;
  };

public:
  str (str const&) noexcept;
  str (str&&) = delete;
  str () noexcept;

  str (std::string&&) = delete;
  str (std::string const&) noexcept;
  str (char const*) noexcept;
  str (internal) noexcept;

  str& operator = (str const&) noexcept;

  void swap (str&) noexcept;

  explicit operator std::string () const;

private:
  internal repr;
};

template <class T>
struct box final {
  using value_type = T;
  using const_pointer = std::add_pointer_t<std::add_const_t<value_type>>;
  using pointer = std::add_pointer_t<value_type>;

  box (box const& that) : box(*that) { }
  box (box&& that) : repr(std::exchange(that.repr, 0)) { }

  template <class... Args>
  box (std::in_place_t, Args&&... args) {
    ::new (std::addressof(**this)) value_type(std::forward<Args>(args)...);
  }
  box (T const& value) : box(std::in_place, value) { }

  // replace this with a raw<T> wrapper so that we KNOW a type came from box
  box (std::unique_ptr<value_type> handle) {
    this->raw(handle.release());
  }

  box& operator = (box const& that) {
    box(that).swap(*this);
    return *this;
  }

  box& operator = (box&& that) {
    if (*this) { this->drop(); }
    this->repr = std::exchange(that.repr, 0);
    return *this;
  }

  void swap (box& that) {
    using std::swap;
    swap(this.repr, that.repr);
  }

  explicit operator bool () const noexcept { return this->repr; }

  // This should be replaced with a raw<T> wrapper so that we KNOW a type came
  // from a box.
  explicit operator std::unique_ptr<value_type> () noexcept {
    auto ptr = std::unique_ptr { this->get() };
    this->repr = 0;
    return ptr;
  }

  const_pointer operator -> () const noexcept { return this->get(); }
  pointer operator -> () noexcept { return this->get(); }

  decltype(auto) operator * () const noexcept { return *this->get(); }
  decltype(auto) operator * () noexcept { return *this->get(); }

private:
  void destroy () noexcept;
  void uninit() noexcept;

  void raw (pointer) noexcept;
  pointer raw () noexcept;
  
  const_pointer get () const noexcept;
  pointer get () noexcept;

  std::uintptr_t repr { };
};

std::ostream& operator << (std::ostream&, string const&);
std::ostream& operator << (std::ostream&, str const&);

}}} /* cxxbridge::v1::rust */

#endif /* CXXBRIDGE_H */

Windows support?

Can I use this bridge with -msvc targets?

An outstanding idea for the interop, thank you for it, by the way.

Use namespaces instead of prefixes

For example, in C++ it would be better to use something like cxxbridge::rust::String, and the same for other Rust types, like Str, Vec, Map, etc.

Correspondingly in Rust: cxx::cpp::String, Vector, Map, UniquePtr, SharedPtr.

Of course, the namespace names could be better - it's just an example against the RustString and CxxString type names.

Translating between Result ⟷ exceptions

Currently we catch and abort on unwinding across the FFI boundary in either direction; it's left to the user to communicate all failures via ordinary return values or out-parameters.

It would be nice if C++ exceptions were exposed to Rust as an idiomatic Result error value. For C++ functions that are declared in the bridge as returning a Result, we would not abort on exceptions but instead marshal them across as some kind of cxx::Exception object.

#[cxx::bridge]
mod ffi {
    extern "C" {
        fn f() -> Result<()>;
    }
}

Conversely, Rust functions that are declared as returning a Result could transmit failures as an exception on the C++ side.

#[cxx::bridge]
mod ffi {
    extern "Rust" {
        fn g() -> Result<()>;
    }
}

In both cases, long term we would want the translation to be customizable because not all codebases use exceptions; some would prefer a mapping of Rust Result to Leaf or Outcome. Some thoughts in #16.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.