Safe FFI between Rust and C++

This commit is contained in:
David Tolnay 2019-10-20 14:51:12 -04:00
commit 7db7369797
No known key found for this signature in database
GPG Key ID: F9BA143B95FF6D82
56 changed files with 4614 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/Cargo.lock
/expand.cc
/expand.rs
/target

3
.travis.yml Normal file
View File

@ -0,0 +1,3 @@
language: rust
rust: nightly
script: cargo run --manifest-path demo-rs/Cargo.toml

35
Cargo.toml Normal file
View File

@ -0,0 +1,35 @@
[package]
name = "cxx"
version = "0.0.0"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
links = "cxxbridge00"
license = "MIT OR Apache-2.0"
description = "Safe interop between Rust and C++"
repository = "https://github.com/dtolnay/cxx"
documentation = "https://docs.rs/cxx"
readme = "README.md"
[badges]
travis-ci = { repository = "dtolnay/cxx" }
[dependencies]
anyhow = "1.0"
cc = "1.0.49"
codespan = "0.7"
codespan-reporting = "0.7"
cxxbridge-macro = { version = "0.0", path = "macro" }
proc-macro2 = { version = "1.0", features = ["span-locations"] }
quote = "1.0"
syn = { version = "1.0", features = ["full"] }
thiserror = "1.0"
[build-dependencies]
cc = "1.0.49"
[dev-dependencies]
rustversion = "1.0"
trybuild = "1.0"
[workspace]
members = ["cmd", "demo-rs", "macro"]

1
FUNDING.yml Normal file
View File

@ -0,0 +1 @@
github: dtolnay

201
LICENSE-APACHE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

23
LICENSE-MIT Normal file
View File

@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

361
README.md Normal file
View File

@ -0,0 +1,361 @@
CXX &mdash; safe FFI between Rust and C++
=========================================
[![Build Status](https://api.travis-ci.com/dtolnay/cxx.svg?branch=master)](https://travis-ci.com/dtolnay/cxx)
[![Latest Version](https://img.shields.io/crates/v/cxx.svg)](https://crates.io/crates/cxx)
[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/cxx)
This library provides a **safe** mechanism for calling C++ code from Rust and
Rust code from C++, not subject to the many ways that things can go wrong when
using bindgen or cbindgen to generate unsafe C-style bindings.
```toml
[dependencies]
cxx = "0.0"
```
*Compiler support: requires rustc 1.42+ (beta on January 30, stable on March
12)*
<br>
## Overview
The idea is that we define the signatures of both sides of our FFI boundary
embedded together in one Rust module (the next section shows an example). From
this, CXX receives a complete picture of the boundary to perform static analyses
against the types and function signatures to uphold both Rust's and C++'s
invariants and requirements.
If everything checks out statically, then CXX uses a pair of code generators to
emit the relevant `extern "C"` signatures on both sides together with any
necessary static assertions for later in the build process to verify
correctness. On the Rust side this code generator is simply an attribute
procedural macro. On the C++ side it can be a small Cargo build script if your
build is managed by Cargo, or for other build systems like Bazel or Buck we
provide a command line tool which generates the header and source file and
should be easy to integrate.
The resulting FFI bridge operates at zero or negligible overhead, i.e. no
copying, no serialization, no memory allocation, no runtime checks needed.
The FFI signatures are able to use native types from whichever side they please,
such as Rust's `String` or C++'s `std::string`, Rust's `Box` or C++'s
`std::unique_ptr`, Rust's `Vec` or C++'s `std::vector`, etc in any combination.
CXX guarantees an ABI-compatible signature that both sides understand, based on
builtin bindings for key standard library types to expose an idiomatic API on
those types to the other language. For example when manipulating a C++ string
from Rust, its `len()` method becomes a call of the `size()` member function
defined by C++; when manipulation a Rust string from C++, its `size()` member
function calls Rust's `len()`.
<br>
## Example
A runnable version of this example is provided under the *demo-rs* directory of
this repo (with the C++ side of the implementation in the *demo-cxx* directory).
To try it out, jump into demo-rs and run `cargo run`.
```rust
#[cxx::bridge]
mod ffi {
// Any shared structs, whose fields will be visible to both languages.
struct SharedThing {
z: i32,
y: Box<ThingR>,
x: UniquePtr<ThingC>,
}
extern "C" {
// One or more headers with the matching C++ declarations. Our code
// generators don't read it but it gets #include'd and used in static
// assertions to ensure our picture of the FFI boundary is accurate.
include!("demo-cxx/demo.h");
// Zero or more opaque types which both languages can pass around but
// only C++ can see the fields.
type ThingC;
// Functions implemented in C++.
fn make_demo(appname: &str) -> UniquePtr<ThingC>;
fn get_name(thing: &ThingC) -> &CxxString;
fn do_thing(state: SharedThing);
}
extern "Rust" {
// Zero or more opaque types which both languages can pass around but
// only Rust can see the fields.
type ThingR;
// Functions implemented in Rust.
fn print_r(r: &ThingR);
}
}
```
Now we simply provide C++ definitions of all the things in the `extern "C"`
block and Rust definitions of all the things in the `extern "Rust"` block, and
get to call back and forth safely.
Here are links to the complete set of source files involved in the demo:
- [demo-rs/src/main.rs](demo-rs/src/main.rs)
- [demo-rs/build.rs](demo-rs/build.rs)
- [demo-cxx/demo.h](demo-cxx/demo.h)
- [demo-cxx/demo.cc](demo-cxx/demo.cc)
To look at the code generated in both languages for the example by the CXX code
generators:
```console
# run Rust code generator and print to stdout
# (requires https://github.com/dtolnay/cargo-expand)
$ cargo expand --manifest-path demo-rs/Cargo.toml
# run C++ code generator and print to stdout
$ cargo run --manifest-path cmd/Cargo.toml -- demo-rs/src/main.rs
```
<br>
## Details
As seen in the example, the language of the FFI boundary involves 3 kinds of
items:
- **Shared structs** &mdash; their fields are made visible to both languages.
The definition written within cxx::bridge is the single source of truth.
- **Opaque types** &mdash; their fields are secret from the other language.
These cannot be passed across the FFI by value but only behind an indirection,
such as a reference `&`, a Rust `Box`, or a `UniquePtr`. Can be a type alias
for an arbitrarily complicated generic language-specific type depending on
your use case.
- **Functions** &mdash; implemented in either language, callable from the other
language.
Within the `extern "C"` part of the CXX bridge we list the types and functions
for which C++ is the source of truth, as well as the header(s) that declare
those APIs. In the future it's possible that this section could be generated
bindgen-style from the headers but for now we need the signatures written out;
static assertions will verify that they are accurate.
Within the `extern "Rust"` part, we list types and functions for which Rust is
the source of truth. These all implicitly refer to the `super` module, the
parent module of the CXX bridge. You can think of the two items listed in the
example above as being like `use super::ThingR` and `use super::print_r` except
re-exported to C++. The parent module will either contain the definitions
directly for simple things, or contain the relevant `use` statements to bring
them into scope from elsewhere.
Your function implementations themselves, whether in C++ or Rust, *do not* need
to be defined as `extern "C"` ABI or no\_mangle. CXX will put in the right shims
where necessary to make it all work.
<br>
## Comparison vs bindgen and cbindgen
Notice that with CXX there is repetition of all the function signatures: they
are typed out once where the implementation is defined (in C++ or Rust) and
again inside the cxx::bridge module, though compile-time assertions guarantee
these are kept in sync. This is different from [bindgen] and [cbindgen] where
function signatures are typed by a human once and the tool consumes them in one
language and emits them in the other language.
[bindgen]: https://github.com/rust-lang/rust-bindgen
[cbindgen]: https://github.com/eqrion/cbindgen/
This is because CXX fills a somewhat different role. It is a lower level tool
than bindgen or cbindgen in a sense; you can think of it as being a replacement
for the concept of `extern "C"` signatures as we know them, rather than a
replacement for a bindgen. It would be reasonable to build a higher level
bindgen-like tool on top of CXX which consumes a C++ header and/or Rust module
(and/or IDL like Thrift) as source of truth and generates the cxx::bridge,
eliminating the repetition while leveraging the static analysis safety
guarantees of CXX.
But note in other ways CXX is higher level than the bindgens, with rich support
for common standard library types. Frequently with bindgen when we are dealing
with an idiomatic C++ API we would end up manually wrapping that API in C-style
raw pointer functions, applying bindgen to get unsafe raw pointer Rust
functions, and replicating the API again to expose those idiomatically in Rust.
That's a much worse form of repetition because it is unsafe all the way through.
By using a CXX bridge as the shared understanding between the languages, rather
than `extern "C"` C-style signatures as the shared understanding, common FFI use
cases become expressible using 100% safe code.
It would also be reasonable to mix and match, using CXX bridge for the 95% of
your FFI that is straightforward and doing the remaining few oddball signatures
the old fashioned way with bindgen and cbindgen, if for some reason CXX's static
restrictions get in the way. Please file an issue if you end up taking this
approach so that we know what ways it would be worthwhile to make the tool more
expressive.
<br>
## Cargo-based setup
For builds that are orchestrated by Cargo, you will use a build script that runs
CXX's C++ code generator and compiles the resulting C++ code along with any
other C++ code for your crate.
The canonical build script is as follows. The indicated line returns a
[`cc::Build`] instance (from the usual widely used `cc` crate) on which you can
set up any additional source files and compiler flags as normal.
[`cc::Build`]: https://docs.rs/cc/1.0/cc/struct.Build.html
```rust
// build.rs
fn main() {
cxx::Build::new()
.bridge("src/main.rs") // returns a cc::Build
.file("../demo-cxx/demo.cc")
.flag("-std=c++11")
.compile("cxxbridge-demo");
println!("cargo:rerun-if-changed=src/main.rs");
println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
}
```
<br>
## Non-Cargo setup
For use in non-Cargo builds like Bazel or Buck, CXX provides an alternate way of
invoking the C++ code generator as a standalone command line tool. The tool is
packaged as the `cxxbridge-cmd` crate on crates.io or can be built from the
*cmd* directory of this repo.
```bash
$ cargo install cxxbridge-cmd
$ cxxbridge src/main.rs --header > path/to/mybridge.h
$ cxxbridge src/main.rs > path/to/mybridge.cc
```
<br>
## Safety
Be aware that the design of this library is intentionally restrictive and
opinionated! It isn't a goal to be powerful enough to handle arbitrary
signatures in either language. Instead this project is about carving out a
reasonably expressive set of functionality about which we can make useful safety
guarantees today and maybe extend over time. You may find that it takes some
practice to use CXX bridge effectively as it won't work in all the ways that you
are used to.
Some of the considerations that go into ensuring safety are:
- By design, our paired code generators work together to control both sides of
the FFI boundary. Ordinarily in Rust writing your own `extern "C"` blocks is
unsafe because the Rust compiler has no way to know whether the signatures
you've written actually match the signatures implemented in the other
language. With CXX we achieve that visibility and know what's on the other
side.
- Our static analysis detects and prevents passing types by value that shouldn't
be passed by value from C++ to Rust, for example because they may contain
internal pointers that would be screwed up by Rust's move behavior.
- To many people's surprise, it is possible to have a struct in Rust and a
struct in C++ with exactly the same layout / fields / alignment / everything,
and still not the same ABI when passed by value. This is a longstanding
bindgen bug that leads to segfaults in absolutely correct-looking code
([rust-lang/rust-bindgen#778]). CXX knows about this and can insert the
necessary zero-cost workaround transparently where needed, so go ahead and
pass your structs by value without worries. This is made possible by owning
both sides of the boundary rather than just one.
- Template instantiations: for example in order to expose a UniquePtr\<T\> type
in Rust backed by a real C++ unique\_ptr, we have a way of using a Rust trait
to connect the behavior back to the template instantiations performed by the
other language.
[rust-lang/rust-bindgen#778]: https://github.com/rust-lang/rust-bindgen/issues/778
<br>
## Builtin types
In addition to all the primitive types (i32 ⟷ int32_t), the following common
types may be used in the fields of shared structs and the arguments and returns
of functions.
<table>
<tr><th>name in Rust</th><th>name in C++</th><th>restrictions</th></tr>
<tr><td>String</td><td>cxxbridge::RustString</td><td></td></tr>
<tr><td>&amp;str</td><td>cxxbridge::RustStr</td><td></td></tr>
<tr><td><a href="https://docs.rs/cxx/0.0/cxx/struct.CxxString.html">CxxString</a></td><td>std::string</td><td><sup><i>cannot be passed by value</i></sup></td></tr>
<tr><td>Box&lt;T&gt;</td><td>cxxbridge::RustBox&lt;T&gt;</td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
<tr><td><a href="https://docs.rs/cxx/0.0/cxx/struct.UniquePtr.html">UniquePtr&lt;T&gt;</a></td><td>std::unique_ptr&lt;T&gt;</td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr>
<tr><td></td><td></td><td></td></tr>
</table>
The C++ API of the `cxxbridge` namespace is defined by the *include/cxxbridge.h*
file in this repo. You will need to include this header in your C++ code when
working with those types.
The following types are intended to be supported "soon" but are just not
implemented yet. I don't expect any of these to be hard to make work but it's a
matter of designing a nice API for each in its non-native language.
<table>
<tr><th>name in Rust</th><th>name in C++</th></tr>
<tr><td>&amp;[T]</td><td></td></tr>
<tr><td>Vec&lt;T&gt;</td><td></td></tr>
<tr><td>BTreeMap&lt;K, V&gt;</td><td></td></tr>
<tr><td>HashMap&lt;K, V&gt;</td><td></td></tr>
<tr><td></td><td>std::vector&lt;T&gt;</td></tr>
<tr><td></td><td>std::map&lt;K, V&gt;</td></tr>
<tr><td></td><td>std::unordered_map&lt;K, V&gt;</td></tr>
</table>
<br>
## Remaining work
This is still early days for CXX; I am releasing it as a minimum viable product
to collect feedback on the direction and invite collaborators. Here are some of
the facets that I still intend for this project to tackle:
- [ ] Support associated methods: `extern "Rust" { fn f(self: &Struct); }`
- [ ] Support C++ member functions
- [ ] Support passing function pointers across the FFI
- [ ] Support translating between Result ⟷ exceptions
- [ ] Support structs with type parameters
- [ ] Support async functions
On the build side, I don't have much experience with the `cc` crate so I expect
there may be someone who can suggest ways to make that aspect of this crate
friendlier or more robust. Please report issues if you run into trouble building
or linking any of this stuff.
Finally, I know more about Rust library design than C++ library design so I
would appreciate help making the C++ APIs in this project more idiomatic where
anyone has suggestions.
<br>
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this project by you, as defined in the Apache-2.0 license,
shall be dual licensed as above, without any additional terms or conditions.
</sub>

9
build.rs Normal file
View File

@ -0,0 +1,9 @@
fn main() {
cc::Build::new()
.file("src/cxxbridge.cc")
.flag("-std=c++11")
.compile("cxxbridge00");
println!("cargo:rustc-flags=-l dylib=stdc++");
println!("cargo:rerun-if-changed=src/cxxbridge.cc");
println!("cargo:rerun-if-changed=include/cxxbridge.h");
}

25
cmd/Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
name = "cxxbridge-cmd"
version = "0.0.0"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
description = "C++ code generator for integrating `cxx` crate into a non-Cargo build."
repository = "https://github.com/dtolnay/cxx"
[[bin]]
name = "cxxbridge"
path = "src/main.rs"
[badges]
travis-ci = { repository = "dtolnay/cxx" }
[dependencies]
anyhow = "1.0"
codespan = "0.7"
codespan-reporting = "0.7"
proc-macro2 = { version = "1.0", features = ["span-locations"] }
quote = "1.0"
structopt = "0.3"
syn = { version = "1.0", features = ["full"] }
thiserror = "1.0"

1
cmd/src/gen Symbolic link
View File

@ -0,0 +1 @@
../../gen

29
cmd/src/main.rs Normal file
View File

@ -0,0 +1,29 @@
mod gen;
mod syntax;
use std::io::{self, Write};
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt(name = "cxxbridge", author)]
struct Opt {
/// Input Rust source file containing #[cxx::bridge]
#[structopt(parse(from_os_str))]
input: PathBuf,
/// Emit header with declarations only
#[structopt(long)]
header: bool,
}
fn main() {
let opt = Opt::from_args();
let gen = if opt.header {
gen::do_generate_header
} else {
gen::do_generate_bridge
};
let bridge = gen(&opt.input);
let _ = io::stdout().lock().write_all(bridge.as_ref());
}

1
cmd/src/syntax Symbolic link
View File

@ -0,0 +1 @@
../../syntax

21
demo-cxx/demo.cc Normal file
View File

@ -0,0 +1,21 @@
#include "demo-cxx/demo.h"
#include "demo-rs/src/main.rs"
#include <iostream>
namespace org {
namespace rust {
ThingC::ThingC(std::string appname) : appname(std::move(appname)) {}
ThingC::~ThingC() { std::cout << "done with ThingC" << std::endl; }
std::unique_ptr<ThingC> make_demo(cxxbridge::RustStr appname) {
return std::unique_ptr<ThingC>(new ThingC(appname));
}
const std::string &get_name(const ThingC &thing) { return thing.appname; }
void do_thing(SharedThing state) { print_r(*state.y); }
} // namespace rust
} // namespace org

24
demo-cxx/demo.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include "../include/cxxbridge.h"
#include <memory>
#include <string>
namespace org {
namespace rust {
class ThingC {
public:
ThingC(std::string appname);
~ThingC();
std::string appname;
};
struct SharedThing;
std::unique_ptr<ThingC> make_demo(cxxbridge::RustStr appname);
const std::string &get_name(const ThingC &thing);
void do_thing(SharedThing state);
} // namespace rust
} // namespace org

12
demo-rs/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "cxxbridge-demo"
version = "0.0.0"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
publish = false
[dependencies]
cxx = { path = ".." }
[build-dependencies]
cxx = { path = ".." }

11
demo-rs/build.rs Normal file
View File

@ -0,0 +1,11 @@
fn main() {
cxx::Build::new()
.bridge("src/main.rs")
.file("../demo-cxx/demo.cc")
.flag("-std=c++11")
.compile("cxxbridge-demo");
println!("cargo:rerun-if-changed=src/main.rs");
println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
}

39
demo-rs/src/main.rs Normal file
View File

@ -0,0 +1,39 @@
#[cxx::bridge(namespace = org::rust)]
mod ffi {
struct SharedThing {
z: i32,
y: Box<ThingR>,
x: UniquePtr<ThingC>,
}
extern "C" {
include!("demo-cxx/demo.h");
type ThingC;
fn make_demo(appname: &str) -> UniquePtr<ThingC>;
fn get_name(thing: &ThingC) -> &CxxString;
fn do_thing(state: SharedThing);
}
extern "Rust" {
type ThingR;
fn print_r(r: &ThingR);
}
}
pub struct ThingR(usize);
fn print_r(r: &ThingR) {
println!("called back with r={}", r.0);
}
fn main() {
let x = ffi::make_demo("demo of cxx::bridge");
println!("this is a {}", ffi::get_name(x.as_ref().unwrap()));
ffi::do_thing(ffi::SharedThing {
z: 222,
y: Box::new(ThingR(333)),
x,
});
}

77
gen/error.rs Normal file
View File

@ -0,0 +1,77 @@
use crate::gen::Error;
use crate::syntax;
use anyhow::anyhow;
use codespan::{FileId, Files};
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream, WriteColor};
use codespan_reporting::term::{self, Config};
use std::io::Write;
use std::ops::Range;
use std::path::Path;
use std::process;
pub(super) fn format_err(path: &Path, source: &str, error: Error) -> ! {
match error {
Error::Syn(syn_error) => {
let writer = StandardStream::stderr(ColorChoice::Auto);
let ref mut stderr = writer.lock();
for error in syn_error {
let _ = writeln!(stderr);
display_syn_error(stderr, path, source, error);
}
}
_ => eprintln!("cxxbridge: {:?}", anyhow!(error)),
}
process::exit(1);
}
fn display_syn_error(stderr: &mut dyn WriteColor, path: &Path, source: &str, error: syn::Error) {
let span = error.span();
let start = span.start();
let end = span.end();
let mut start_offset = 0;
for _ in 1..start.line {
start_offset += source[start_offset..].find('\n').unwrap() + 1;
}
start_offset += start.column;
let mut end_offset = start_offset;
if start.line == end.line {
end_offset -= start.column;
} else {
for _ in 0..end.line - start.line {
end_offset += source[end_offset..].find('\n').unwrap() + 1;
}
}
end_offset += end.column;
let mut files = Files::new();
let file = files.add(path.to_string_lossy(), source);
let range = start_offset as u32..end_offset as u32;
let diagnostic = diagnose(file, range, error);
let config = Config::default();
let _ = term::emit(stderr, &config, &files, &diagnostic);
}
fn diagnose(file: FileId, range: Range<u32>, error: syn::Error) -> Diagnostic {
let message = error.to_string();
let info = syntax::error::ERRORS
.iter()
.find(|e| message.contains(e.msg));
let mut diagnostic = if let Some(info) = info {
let label = Label::new(file, range, info.label.unwrap_or(&message));
let mut diagnostic = Diagnostic::new_error(&message, label);
if let Some(note) = info.note {
diagnostic = diagnostic.with_notes(vec![note.to_owned()]);
}
diagnostic
} else {
let label = Label::new(file, range, &message);
Diagnostic::new_error(&message, label)
};
diagnostic.code = Some("cxxbridge".to_owned());
diagnostic
}

1
gen/include Symbolic link
View File

@ -0,0 +1 @@
../include

13
gen/include.rs Normal file
View File

@ -0,0 +1,13 @@
static HEADER: &str = include_str!("include/cxxbridge.h");
pub fn get(guard: &str) -> &'static str {
let ifndef = format!("#ifndef {}\n", guard);
let endif = format!("#endif // {}\n", guard);
let begin = HEADER.find(&ifndef);
let end = HEADER.find(&endif);
if let (Some(begin), Some(end)) = (begin, end) {
&HEADER[begin..end + endif.len()]
} else {
panic!("not found in cxxbridge.h header: {}", guard)
}
}

114
gen/mod.rs Normal file
View File

@ -0,0 +1,114 @@
// Functionality that is shared between the cxx::generate_bridge entry point and
// the cmd.
mod error;
pub(super) mod include;
pub(super) mod out;
mod write;
use self::error::format_err;
use self::out::OutFile;
use crate::syntax::{self, check, ident, Types};
use quote::quote;
use std::fs;
use std::io;
use std::path::Path;
use syn::parse::ParseStream;
use syn::{Attribute, File, Item, Token};
use thiserror::Error;
pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Error, Debug)]
pub(super) enum Error {
#[error("no #[cxx::bridge] module found")]
NoBridgeMod,
#[error("#[cxx::bridge] module must have inline contents")]
OutOfLineMod,
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Syn(#[from] syn::Error),
}
struct Input {
namespace: Vec<String>,
module: Vec<Item>,
}
pub(super) fn do_generate_bridge(path: &Path) -> OutFile {
let header = false;
generate(path, header)
}
pub(super) fn do_generate_header(path: &Path) -> OutFile {
let header = true;
generate(path, header)
}
fn generate(path: &Path, header: bool) -> OutFile {
let source = match fs::read_to_string(path) {
Ok(source) => source,
Err(err) => format_err(path, "", Error::Io(err)),
};
match (|| -> Result<_> {
let syntax = syn::parse_file(&source)?;
let bridge = find_bridge_mod(syntax)?;
let apis = syntax::parse_items(bridge.module)?;
let types = Types::collect(&apis)?;
check::typecheck(&apis, &types)?;
let out = write::gen(bridge.namespace, &apis, &types, header);
Ok(out)
})() {
Ok(out) => out,
Err(err) => format_err(path, &source, err),
}
}
fn find_bridge_mod(syntax: File) -> Result<Input> {
for item in syntax.items {
if let Item::Mod(item) = item {
for attr in &item.attrs {
let path = &attr.path;
if quote!(#path).to_string() == "cxx :: bridge" {
let module = match item.content {
Some(module) => module.1,
None => {
return Err(Error::Syn(syn::Error::new_spanned(
item,
Error::OutOfLineMod,
)));
}
};
return Ok(Input {
namespace: parse_args(attr)?,
module,
});
}
}
}
}
Err(Error::NoBridgeMod)
}
fn parse_args(attr: &Attribute) -> syn::Result<Vec<String>> {
if attr.tokens.is_empty() {
return Ok(Vec::new());
}
attr.parse_args_with(|input: ParseStream| {
mod kw {
syn::custom_keyword!(namespace);
}
input.parse::<kw::namespace>()?;
input.parse::<Token![=]>()?;
let path = syn::Path::parse_mod_style(input)?;
input.parse::<Option<Token![,]>>()?;
path.segments
.into_iter()
.map(|seg| {
ident::check(&seg.ident)?;
Ok(seg.ident.to_string())
})
.collect()
})
}

74
gen/out.rs Normal file
View File

@ -0,0 +1,74 @@
use std::fmt::{self, Arguments, Write};
pub(crate) struct OutFile {
pub namespace: Vec<String>,
pub header: bool,
content: Vec<u8>,
section_pending: bool,
block: &'static str,
block_pending: bool,
}
impl OutFile {
pub fn new(namespace: Vec<String>, header: bool) -> Self {
OutFile {
namespace,
header,
content: Vec::new(),
section_pending: false,
block: "",
block_pending: false,
}
}
// Write a blank line if the preceding section had any contents.
pub fn next_section(&mut self) {
self.section_pending = true;
}
pub fn begin_block(&mut self, block: &'static str) {
self.block = block;
self.block_pending = true;
}
pub fn end_block(&mut self) {
if self.block_pending {
self.block_pending = false;
} else {
self.content.extend_from_slice(b"} // ");
self.content.extend_from_slice(self.block.as_bytes());
self.content.push(b'\n');
self.block = "";
self.section_pending = true;
}
}
pub fn write_fmt(&mut self, args: Arguments) {
Write::write_fmt(self, args).unwrap();
}
}
impl Write for OutFile {
fn write_str(&mut self, s: &str) -> fmt::Result {
if !s.is_empty() {
if self.block_pending {
self.content.push(b'\n');
self.content.extend_from_slice(self.block.as_bytes());
self.content.extend_from_slice(b" {\n");
self.block_pending = false;
self.section_pending = false;
} else if self.section_pending {
self.content.push(b'\n');
self.section_pending = false;
}
self.content.extend_from_slice(s.as_bytes());
}
Ok(())
}
}
impl AsRef<[u8]> for OutFile {
fn as_ref(&self) -> &[u8] {
&self.content
}
}

623
gen/write.rs Normal file
View File

@ -0,0 +1,623 @@
use crate::gen::include;
use crate::gen::out::OutFile;
use crate::syntax::atom::Atom::{self, *};
use crate::syntax::{Api, ExternFn, Struct, Type, Types, Var};
use proc_macro2::Ident;
pub(super) fn gen(namespace: Vec<String>, apis: &[Api], types: &Types, header: bool) -> OutFile {
let mut out_file = OutFile::new(namespace.clone(), header);
let out = &mut out_file;
if header {
writeln!(out, "#pragma once");
}
for api in apis {
if let Api::Include(include) = api {
writeln!(out, "#include \"{}\"", include.value().escape_default());
}
}
write_includes(out, types);
write_include_cxxbridge(out, types);
if !header {
out.next_section();
write_namespace_alias(out, types);
}
out.next_section();
for name in &namespace {
writeln!(out, "namespace {} {{", name);
}
if header {
out.next_section();
write_namespace_alias(out, types);
}
out.next_section();
for api in apis {
match api {
Api::Struct(strct) => write_struct_decl(out, &strct.ident),
Api::CxxType(ety) | Api::RustType(ety) => write_struct_decl(out, &ety.ident),
_ => {}
}
}
for api in apis {
if let Api::Struct(strct) = api {
out.next_section();
write_struct(out, strct);
}
}
if !header {
out.begin_block("extern \"C\"");
for api in apis {
let (efn, write): (_, fn(_, _, _)) = match api {
Api::CxxFunction(efn) => (efn, write_cxx_function_shim),
Api::RustFunction(efn) => (efn, write_rust_function_decl),
_ => continue,
};
out.next_section();
write(out, efn, types);
}
out.end_block();
}
for api in apis {
if let Api::RustFunction(efn) = api {
out.next_section();
write_rust_function_shim(out, efn, types);
}
}
out.next_section();
for name in namespace.iter().rev() {
writeln!(out, "}} // namespace {}", name);
}
if !header {
out.next_section();
write_generic_instantiations(out, types);
}
out_file
}
fn write_includes(out: &mut OutFile, types: &Types) {
let mut has_int = false;
let mut has_unique_ptr = false;
let mut has_string = false;
for ty in types {
match ty {
Type::Ident(ident) => match Atom::from(ident) {
Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize) | Some(I8)
| Some(I16) | Some(I32) | Some(I64) | Some(Isize) => has_int = true,
Some(CxxString) => has_string = true,
Some(Bool) | Some(RustString) | None => {}
},
Type::UniquePtr(_) => has_unique_ptr = true,
_ => {}
}
}
if has_int {
writeln!(out, "#include <cstdint>");
}
if has_unique_ptr {
writeln!(out, "#include <memory>");
}
if has_string {
writeln!(out, "#include <string>");
}
}
fn write_include_cxxbridge(out: &mut OutFile, types: &Types) {
let mut needs_rust_box = false;
for ty in types {
if let Type::RustBox(_) = ty {
needs_rust_box = true;
break;
}
}
out.begin_block("namespace cxxbridge00");
if needs_rust_box {
writeln!(out, "// #include \"cxxbridge.h\"");
for line in include::get("CXXBRIDGE00_RUST_BOX").lines() {
if !line.trim_start().starts_with("//") {
writeln!(out, "{}", line);
}
}
}
out.end_block();
}
fn write_namespace_alias(out: &mut OutFile, types: &Types) {
let mut needs_namespace_alias = false;
for ty in types {
if let Type::RustBox(_) = ty {
needs_namespace_alias = true;
break;
}
}
if needs_namespace_alias {
writeln!(out, "namespace cxxbridge = cxxbridge00;");
}
}
fn write_struct(out: &mut OutFile, strct: &Struct) {
for line in strct.doc.to_string().lines() {
writeln!(out, "//{}", line);
}
writeln!(out, "struct {} final {{", strct.ident);
for field in &strct.fields {
write!(out, " ");
write_type_space(out, &field.ty);
writeln!(out, "{};", field.ident);
}
writeln!(out, "}};");
}
fn write_struct_decl(out: &mut OutFile, ident: &Ident) {
writeln!(out, "struct {};", ident);
}
fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
let indirect_return = efn
.ret
.as_ref()
.map_or(false, |ret| types.needs_indirect_abi(ret));
write_extern_return_type(out, &efn.ret, types);
for name in out.namespace.clone() {
write!(out, "{}$", name);
}
write!(out, "cxxbridge00${}(", efn.ident);
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
write!(out, ", ");
}
write_extern_arg(out, arg, types);
}
if indirect_return {
if !efn.args.is_empty() {
write!(out, ", ");
}
write_return_type(out, &efn.ret);
write!(out, "*return$");
}
writeln!(out, ") noexcept {{");
write!(out, " ");
write_return_type(out, &efn.ret);
write!(out, "(*{}$)(", efn.ident);
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
write!(out, ", ");
}
write_type(out, &arg.ty);
}
writeln!(out, ") = {};", efn.ident);
write!(out, " ");
if indirect_return {
write!(out, "new (return$) ");
write_type(out, efn.ret.as_ref().unwrap());
write!(out, "(");
} else if efn.ret.is_some() {
write!(out, "return ");
}
write!(out, "{}$(", efn.ident);
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
write!(out, ", ");
}
if let Type::RustBox(_) = &arg.ty {
write_type(out, &arg.ty);
write!(out, "::from_raw({})", arg.ident);
} else if let Type::UniquePtr(_) = &arg.ty {
write_type(out, &arg.ty);
write!(out, "({})", arg.ident);
} else if types.needs_indirect_abi(&arg.ty) {
write!(out, "std::move(*{})", arg.ident);
} else {
write!(out, "{}", arg.ident);
}
}
write!(out, ")");
match &efn.ret {
Some(Type::RustBox(_)) => write!(out, ".into_raw()"),
Some(Type::UniquePtr(_)) => write!(out, ".release()"),
_ => {}
}
if indirect_return {
write!(out, ")");
}
writeln!(out, ";");
writeln!(out, "}}");
}
fn write_rust_function_decl(out: &mut OutFile, efn: &ExternFn, types: &Types) {
write_extern_return_type(out, &efn.ret, types);
for name in out.namespace.clone() {
write!(out, "{}$", name);
}
write!(out, "cxxbridge00${}(", efn.ident);
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
write!(out, ", ");
}
write_extern_arg(out, arg, types);
}
if efn
.ret
.as_ref()
.map_or(false, |ret| types.needs_indirect_abi(ret))
{
if !efn.args.is_empty() {
write!(out, ", ");
}
write_return_type(out, &efn.ret);
write!(out, "*return$");
}
writeln!(out, ") noexcept;");
}
fn write_rust_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
let indirect_return = efn
.ret
.as_ref()
.map_or(false, |ret| types.needs_indirect_abi(ret));
for line in efn.doc.to_string().lines() {
writeln!(out, "//{}", line);
}
write_return_type(out, &efn.ret);
write!(out, "{}(", efn.ident);
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
write!(out, ", ");
}
write_type_space(out, &arg.ty);
write!(out, "{}", arg.ident);
}
write!(out, ") noexcept");
if out.header {
writeln!(out, ";");
} else {
writeln!(out, " {{");
write!(out, " ");
if indirect_return {
write!(out, "char return$[sizeof(");
write_type(out, efn.ret.as_ref().unwrap());
writeln!(out, ")];");
write!(out, " ");
} else if efn.ret.is_some() {
write!(out, "return ");
}
for name in out.namespace.clone() {
write!(out, "{}$", name);
}
write!(out, "cxxbridge00${}(", efn.ident);
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
write!(out, ", ");
}
if types.needs_indirect_abi(&arg.ty) {
write!(out, "&");
}
write!(out, "{}", arg.ident);
}
if indirect_return {
if !efn.args.is_empty() {
write!(out, ", ");
}
write!(out, "reinterpret_cast<");
write_return_type(out, &efn.ret);
write!(out, "*>(return$)");
}
writeln!(out, ");");
if indirect_return {
write!(out, " return ");
write_type(out, efn.ret.as_ref().unwrap());
write!(out, "(*reinterpret_cast<");
write_return_type(out, &efn.ret);
writeln!(out, "*>(return$));");
}
writeln!(out, "}}");
}
}
fn write_return_type(out: &mut OutFile, ty: &Option<Type>) {
match ty {
None => write!(out, "void "),
Some(ty) => write_type_space(out, ty),
}
}
fn write_extern_return_type(out: &mut OutFile, ty: &Option<Type>, types: &Types) {
match ty {
Some(Type::RustBox(ty)) | Some(Type::UniquePtr(ty)) => {
write_type_space(out, &ty.inner);
write!(out, "*");
}
Some(Type::Str(_)) => write!(out, "cxxbridge::RustStr::Repr "),
Some(ty) if types.needs_indirect_abi(ty) => write!(out, "void "),
_ => write_return_type(out, ty),
}
}
fn write_extern_arg(out: &mut OutFile, arg: &Var, types: &Types) {
match &arg.ty {
Type::RustBox(ty) | Type::UniquePtr(ty) => {
write_type_space(out, &ty.inner);
write!(out, "*");
}
Type::Str(_) => write!(out, "cxxbridge::RustStr::Repr "),
_ => write_type_space(out, &arg.ty),
}
if types.needs_indirect_abi(&arg.ty) {
write!(out, "*");
}
write!(out, "{}", arg.ident);
}
fn write_type(out: &mut OutFile, ty: &Type) {
match ty {
Type::Ident(ident) => match Atom::from(ident) {
Some(Bool) => write!(out, "bool"),
Some(U8) => write!(out, "uint8_t"),
Some(U16) => write!(out, "uint16_t"),
Some(U32) => write!(out, "uint32_t"),
Some(U64) => write!(out, "uint64_t"),
Some(Usize) => write!(out, "size_t"),
Some(I8) => write!(out, "int8_t"),
Some(I16) => write!(out, "int16_t"),
Some(I32) => write!(out, "int32_t"),
Some(I64) => write!(out, "int64_t"),
Some(Isize) => write!(out, "ssize_t"),
Some(CxxString) => write!(out, "std::string"),
Some(RustString) => write!(out, "cxxbridge::RustString"),
None => write!(out, "{}", ident),
},
Type::RustBox(ty) => {
write!(out, "cxxbridge::RustBox<");
write_type(out, &ty.inner);
write!(out, ">");
}
Type::UniquePtr(ptr) => {
write!(out, "std::unique_ptr<");
write_type(out, &ptr.inner);
write!(out, ">");
}
Type::Ref(r) => {
if r.mutability.is_none() {
write!(out, "const ");
}
write_type(out, &r.inner);
write!(out, " &");
}
Type::Str(_) => {
write!(out, "cxxbridge::RustStr");
}
}
}
fn write_type_space(out: &mut OutFile, ty: &Type) {
write_type(out, ty);
match ty {
Type::Ident(_) | Type::RustBox(_) | Type::UniquePtr(_) | Type::Str(_) => write!(out, " "),
Type::Ref(_) => {}
}
}
fn write_generic_instantiations(out: &mut OutFile, types: &Types) {
fn allow_unique_ptr(ident: &Ident) -> bool {
Atom::from(ident).is_none()
}
out.begin_block("extern \"C\"");
for ty in types {
if let Type::RustBox(ty) = ty {
if let Type::Ident(inner) = &ty.inner {
out.next_section();
write_rust_box_extern(out, inner);
}
} else if let Type::UniquePtr(ptr) = ty {
if let Type::Ident(inner) = &ptr.inner {
if allow_unique_ptr(inner) {
out.next_section();
write_unique_ptr(out, inner);
}
}
}
}
out.end_block();
out.begin_block("namespace cxxbridge00");
for ty in types {
if let Type::RustBox(ty) = ty {
if let Type::Ident(inner) = &ty.inner {
write_rust_box_impl(out, inner);
}
}
}
out.end_block();
}
fn write_rust_box_extern(out: &mut OutFile, ident: &Ident) {
let mut inner = String::new();
for name in &out.namespace {
inner += name;
inner += "::";
}
inner += &ident.to_string();
let instance = inner.replace("::", "$");
writeln!(out, "#ifndef CXXBRIDGE00_RUST_BOX_{}", instance);
writeln!(out, "#define CXXBRIDGE00_RUST_BOX_{}", instance);
writeln!(
out,
"void cxxbridge00$rust_box${}$uninit(cxxbridge::RustBox<{}> *ptr) noexcept;",
instance, inner,
);
writeln!(
out,
"void cxxbridge00$rust_box${}$set_raw(cxxbridge::RustBox<{}> *ptr, {} *raw) noexcept;",
instance, inner, inner
);
writeln!(
out,
"void cxxbridge00$rust_box${}$drop(cxxbridge::RustBox<{}> *ptr) noexcept;",
instance, inner,
);
writeln!(
out,
"const {} *cxxbridge00$rust_box${}$deref(const cxxbridge::RustBox<{}> *ptr) noexcept;",
inner, instance, inner,
);
writeln!(
out,
"{} *cxxbridge00$rust_box${}$deref_mut(cxxbridge::RustBox<{}> *ptr) noexcept;",
inner, instance, inner,
);
writeln!(out, "#endif // CXXBRIDGE00_RUST_BOX_{}", instance);
}
fn write_rust_box_impl(out: &mut OutFile, ident: &Ident) {
let mut inner = String::new();
for name in &out.namespace {
inner += name;
inner += "::";
}
inner += &ident.to_string();
let instance = inner.replace("::", "$");
writeln!(out, "template <>");
writeln!(out, "void RustBox<{}>::uninit() noexcept {{", inner);
writeln!(
out,
" return cxxbridge00$rust_box${}$uninit(this);",
instance
);
writeln!(out, "}}");
writeln!(out, "template <>");
writeln!(
out,
"void RustBox<{}>::set_raw({} *raw) noexcept {{",
inner, inner,
);
writeln!(
out,
" return cxxbridge00$rust_box${}$set_raw(this, raw);",
instance
);
writeln!(out, "}}");
writeln!(out, "template <>");
writeln!(out, "void RustBox<{}>::drop() noexcept {{", inner);
writeln!(
out,
" return cxxbridge00$rust_box${}$drop(this);",
instance
);
writeln!(out, "}}");
writeln!(out, "template <>");
writeln!(
out,
"const {} *RustBox<{}>::deref() const noexcept {{",
inner, inner,
);
writeln!(
out,
" return cxxbridge00$rust_box${}$deref(this);",
instance
);
writeln!(out, "}}");
writeln!(out, "template <>");
writeln!(
out,
"{} *RustBox<{}>::deref_mut() noexcept {{",
inner, inner,
);
writeln!(
out,
" return cxxbridge00$rust_box${}$deref_mut(this);",
instance
);
writeln!(out, "}}");
}
fn write_unique_ptr(out: &mut OutFile, ident: &Ident) {
let mut inner = String::new();
for name in &out.namespace {
inner += name;
inner += "::";
}
inner += &ident.to_string();
let instance = inner.replace("::", "$");
writeln!(out, "#ifndef CXXBRIDGE00_UNIQUE_PTR_{}", instance);
writeln!(out, "#define CXXBRIDGE00_UNIQUE_PTR_{}", instance);
writeln!(
out,
"static_assert(sizeof(std::unique_ptr<{}>) == sizeof(void *), \"\");",
inner,
);
writeln!(
out,
"static_assert(alignof(std::unique_ptr<{}>) == alignof(void *), \"\");",
inner,
);
writeln!(
out,
"void cxxbridge00$unique_ptr${}$null(std::unique_ptr<{}> *ptr) noexcept {{",
instance, inner,
);
writeln!(out, " new (ptr) std::unique_ptr<{}>();", inner);
writeln!(out, "}}");
writeln!(
out,
"void cxxbridge00$unique_ptr${}$new(std::unique_ptr<{}> *ptr, {} *value) noexcept {{",
instance, inner, inner,
);
writeln!(
out,
" new (ptr) std::unique_ptr<{}>(new {}(std::move(*value)));",
inner, inner,
);
writeln!(out, "}}");
writeln!(
out,
"void cxxbridge00$unique_ptr${}$raw(std::unique_ptr<{}> *ptr, {} *raw) noexcept {{",
instance, inner, inner,
);
writeln!(out, " new (ptr) std::unique_ptr<{}>(raw);", inner);
writeln!(out, "}}");
writeln!(
out,
"const {} *cxxbridge00$unique_ptr${}$get(const std::unique_ptr<{}>& ptr) noexcept {{",
inner, instance, inner,
);
writeln!(out, " return ptr.get();");
writeln!(out, "}}");
writeln!(
out,
"{} *cxxbridge00$unique_ptr${}$release(std::unique_ptr<{}>& ptr) noexcept {{",
inner, instance, inner,
);
writeln!(out, " return ptr.release();");
writeln!(out, "}}");
writeln!(
out,
"void cxxbridge00$unique_ptr${}$drop(std::unique_ptr<{}> *ptr) noexcept {{",
instance, inner,
);
writeln!(out, " ptr->~unique_ptr();");
writeln!(out, "}}");
writeln!(out, "#endif // CXXBRIDGE00_UNIQUE_PTR_{}", instance);
}

132
include/cxxbridge.h Normal file
View File

@ -0,0 +1,132 @@
#pragma once
#include <array>
#include <cstdint>
#include <iostream>
#include <string>
namespace cxxbridge00 {
class RustString final {
public:
RustString() noexcept;
RustString(const RustString &other) noexcept;
RustString(RustString &&other) noexcept;
RustString(const char *s);
RustString(const std::string &s);
RustString &operator=(const RustString &other) noexcept;
RustString &operator=(RustString &&other) noexcept;
~RustString() noexcept;
operator std::string() const;
// Note: no null terminator.
const char *data() const noexcept;
size_t size() const noexcept;
size_t length() const noexcept;
private:
// Size and alignment statically verified by rust_string.rs.
std::array<uintptr_t, 3> repr;
};
class RustStr final {
public:
RustStr() noexcept;
RustStr(const char *s);
RustStr(const std::string &s);
RustStr(std::string &&s) = delete;
RustStr(const RustStr &other) noexcept;
RustStr &operator=(RustStr other) noexcept;
operator std::string() const;
// Note: no null terminator.
const char *data() const noexcept;
size_t size() const noexcept;
size_t length() const noexcept;
// Repr is PRIVATE; must not be used other than by our generated code.
//
// Not necessarily ABI compatible with &str. Codegen will translate to
// cxx::rust_str::RustStr which matches this layout.
struct Repr {
const char *ptr;
size_t len;
};
RustStr(Repr repr) noexcept;
operator Repr() noexcept;
private:
Repr repr;
};
#ifndef CXXBRIDGE00_RUST_BOX
#define CXXBRIDGE00_RUST_BOX
template <typename T> class RustBox final {
public:
RustBox(const RustBox &other) : RustBox(*other) {}
RustBox(RustBox &&other) noexcept : repr(other.repr) { other.repr = 0; }
RustBox(const T &val) {
this->uninit();
new (this->deref_mut()) T(val);
}
RustBox &operator=(const RustBox &other) {
if (this != &other) {
if (this->repr) {
**this = *other;
} else {
this->uninit();
new (this->deref_mut()) T(*other);
}
}
return *this;
}
RustBox &operator=(RustBox &&other) noexcept {
if (this->repr) {
this->drop();
}
this->repr = other.repr;
other.repr = 0;
return *this;
}
~RustBox() noexcept {
if (this->repr) {
this->drop();
}
}
const T *operator->() const noexcept { return this->deref(); }
const T &operator*() const noexcept { return *this->deref(); }
T *operator->() noexcept { return this->deref_mut(); }
T &operator*() noexcept { return *this->deref_mut(); }
// Important: requires that `raw` came from an into_raw call. Do not pass a
// pointer from `new` or any other source.
static RustBox from_raw(T *raw) noexcept {
RustBox box;
box.set_raw(raw);
return box;
}
T *into_raw() noexcept {
T *raw = this->deref_mut();
this->repr = 0;
return raw;
}
private:
RustBox() noexcept {}
void uninit() noexcept;
void set_raw(T *) noexcept;
T *get_raw() noexcept;
void drop() noexcept;
const T *deref() const noexcept;
T *deref_mut() noexcept;
uintptr_t repr;
};
#endif // CXXBRIDGE00_RUST_BOX
std::ostream &operator<<(std::ostream &os, const RustString &s);
std::ostream &operator<<(std::ostream &os, const RustStr &s);
} // namespace cxxbridge00
namespace cxxbridge = cxxbridge00;

22
macro/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "cxxbridge-macro"
version = "0.0.0"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
description = "Implementation detail of the `cxx` crate."
repository = "https://github.com/dtolnay/cxx"
[lib]
proc-macro = true
[badges]
travis-ci = { repository = "dtolnay/cxx" }
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0", features = ["full"] }
[dev-dependencies]
cxx = { version = "0.0", path = ".." }

447
macro/src/expand.rs Normal file
View File

@ -0,0 +1,447 @@
use crate::namespace::Namespace;
use crate::syntax::atom::Atom;
use crate::syntax::{self, check, Api, ExternFn, ExternType, Struct, Type, Types, Var};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned};
use syn::{Error, ItemMod, Result, Token};
pub fn bridge(namespace: &Namespace, ffi: ItemMod) -> Result<TokenStream> {
let ident = &ffi.ident;
let content = ffi.content.ok_or(Error::new(
Span::call_site(),
"#[cxx::bridge] module must have inline contents",
))?;
let apis = syntax::parse_items(content.1)?;
let ref types = Types::collect(&apis)?;
check::typecheck(&apis, types)?;
let mut expanded = TokenStream::new();
let mut hidden = TokenStream::new();
let mut has_rust_type = false;
for api in &apis {
if let Api::RustType(ety) = api {
expanded.extend(expand_rust_type(ety));
if !has_rust_type {
hidden.extend(quote!(const fn __assert_sized<T>() {}));
has_rust_type = true;
}
let ident = &ety.ident;
hidden.extend(quote!(__assert_sized::<#ident>();));
}
}
for api in &apis {
match api {
Api::Include(_) | Api::RustType(_) => {}
Api::Struct(strct) => expanded.extend(expand_struct(strct)),
Api::CxxType(ety) => expanded.extend(expand_cxx_type(ety)),
Api::CxxFunction(efn) => {
expanded.extend(expand_cxx_function_shim(namespace, efn, types));
}
Api::RustFunction(efn) => {
hidden.extend(expand_rust_function_shim(namespace, efn, types))
}
}
}
for ty in types {
if let Type::RustBox(ty) = ty {
if let Type::Ident(ident) = &ty.inner {
if Atom::from(ident).is_none() {
hidden.extend(expand_rust_box(namespace, ident));
}
}
} else if let Type::UniquePtr(ptr) = ty {
if let Type::Ident(ident) = &ptr.inner {
if Atom::from(ident).is_none() {
expanded.extend(expand_unique_ptr(namespace, ident));
}
}
}
}
// Work around https://github.com/rust-lang/rust/issues/67851.
if !hidden.is_empty() {
expanded.extend(quote! {
#[doc(hidden)]
const _: () = {
#hidden
};
});
}
let attrs = ffi
.attrs
.into_iter()
.filter(|attr| attr.path.is_ident("doc"));
let vis = &ffi.vis;
Ok(quote! {
#(#attrs)*
#[deny(improper_ctypes)]
#[allow(non_snake_case)]
#vis mod #ident {
#expanded
}
})
}
fn expand_struct(strct: &Struct) -> TokenStream {
let ident = &strct.ident;
let doc = &strct.doc;
let derives = &strct.derives;
let fields = strct.fields.iter().map(|field| {
// This span on the pub makes "private type in public interface" errors
// appear in the right place.
let vis = Token![pub](field.ident.span());
quote!(#vis #field)
});
quote! {
#doc
#[derive(#(#derives),*)]
#[repr(C)]
pub struct #ident {
#(#fields,)*
}
}
}
fn expand_cxx_type(ety: &ExternType) -> TokenStream {
let ident = &ety.ident;
let doc = &ety.doc;
quote! {
#doc
#[repr(C)]
pub struct #ident {
_private: ::cxx::private::Opaque,
}
}
}
fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
let ident = &efn.ident;
let args = efn.args.iter().map(|arg| expand_extern_arg(arg, types));
let ret = expand_extern_return_type(&efn.ret, types);
let mut outparam = None;
if indirect_return(&efn.ret, types) {
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
outparam = Some(quote!(__return: *mut #ret));
}
let link_name = format!("{}cxxbridge00${}", namespace, ident);
let local_name = format_ident!("__{}", ident);
quote! {
#[link_name = #link_name]
fn #local_name(#(#args,)* #outparam) #ret;
}
}
fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
let ident = &efn.ident;
let doc = &efn.doc;
let decl = expand_cxx_function_decl(namespace, efn, types);
let args = &efn.args;
let ret = expand_return_type(&efn.ret);
let indirect_return = indirect_return(&efn.ret, types);
let vars = efn.args.iter().map(|arg| {
let var = &arg.ident;
match &arg.ty {
Type::Ident(ident) if ident == "String" => {
quote!(#var.as_mut_ptr() as *mut ::cxx::private::RustString)
}
Type::RustBox(_) => quote!(::std::boxed::Box::into_raw(#var)),
Type::UniquePtr(_) => quote!(::cxx::UniquePtr::into_raw(#var)),
Type::Ref(ty) => match &ty.inner {
Type::Ident(ident) if ident == "String" => {
quote!(::cxx::private::RustString::from_ref(#var))
}
_ => quote!(#var),
},
Type::Str(_) => quote!(::cxx::private::RustStr::from(#var)),
ty if types.needs_indirect_abi(ty) => quote!(#var.as_mut_ptr()),
_ => quote!(#var),
}
});
let mut setup = efn
.args
.iter()
.filter(|arg| types.needs_indirect_abi(&arg.ty))
.map(|arg| {
let var = &arg.ident;
// These are arguments for which C++ has taken ownership of the data
// behind the mut reference it received.
quote! {
let mut #var = std::mem::MaybeUninit::new(#var);
}
})
.collect::<TokenStream>();
let local_name = format_ident!("__{}", ident);
let call = if indirect_return {
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
setup.extend(quote! {
let mut __return = ::std::mem::MaybeUninit::<#ret>::uninit();
#local_name(#(#vars,)* __return.as_mut_ptr());
});
quote! {
__return.assume_init()
}
} else {
quote! {
#local_name(#(#vars),*)
}
};
let expr = efn
.ret
.as_ref()
.and_then(|ret| match ret {
Type::Ident(ident) if ident == "String" => Some(quote!(#call.into_string())),
Type::RustBox(_) => Some(quote!(::std::boxed::Box::from_raw(#call))),
Type::UniquePtr(_) => Some(quote!(::cxx::UniquePtr::from_raw(#call))),
Type::Ref(ty) => match &ty.inner {
Type::Ident(ident) if ident == "String" => Some(quote!(#call.as_string())),
_ => None,
},
Type::Str(_) => Some(quote!(#call.as_str())),
_ => None,
})
.unwrap_or(call);
quote! {
#doc
pub fn #ident(#(#args),*) #ret {
extern "C" {
#decl
}
unsafe {
#setup
#expr
}
}
}
}
fn expand_rust_type(ety: &ExternType) -> TokenStream {
let ident = &ety.ident;
quote! {
use super::#ident;
}
}
fn expand_rust_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
let ident = &efn.ident;
let args = efn.args.iter().map(|arg| expand_extern_arg(arg, types));
let vars = efn.args.iter().map(|arg| {
let ident = &arg.ident;
if types.needs_indirect_abi(&arg.ty) {
quote!(::std::ptr::read(#ident))
} else {
quote!(#ident)
}
});
let mut outparam = None;
let call = quote! {
::cxx::private::catch_unwind(__fn, move || super::#ident(#(#vars),*))
};
let mut expr = efn
.ret
.as_ref()
.and_then(|ret| match ret {
Type::Ident(ident) if ident == "String" => {
Some(quote!(::cxx::private::RustString::from(#call)))
}
Type::RustBox(_) => Some(quote!(::std::boxed::Box::into_raw(#call))),
Type::UniquePtr(_) => Some(quote!(::cxx::UniquePtr::into_raw(#call))),
Type::Ref(ty) => match &ty.inner {
Type::Ident(ident) if ident == "String" => {
Some(quote!(::cxx::private::RustString::from_ref(#call)))
}
_ => None,
},
Type::Str(_) => Some(quote!(::cxx::private::RustStr::from(#call))),
_ => None,
})
.unwrap_or(call);
if indirect_return(&efn.ret, types) {
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
outparam = Some(quote!(__return: *mut #ret));
expr = quote!(::std::ptr::write(__return, #expr));
}
let ret = expand_extern_return_type(&efn.ret, types);
let link_name = format!("{}cxxbridge00${}", namespace, ident);
let local_name = format_ident!("__{}", ident);
let catch_unwind_label = format!("::{}", ident);
quote! {
#[doc(hidden)]
#[export_name = #link_name]
unsafe extern "C" fn #local_name(#(#args,)* #outparam) #ret {
let __fn = concat!(module_path!(), #catch_unwind_label);
#expr
}
}
}
fn expand_rust_box(namespace: &Namespace, ident: &Ident) -> TokenStream {
let link_prefix = format!("cxxbridge00$rust_box${}{}$", namespace, ident);
let link_uninit = format!("{}uninit", link_prefix);
let link_set_raw = format!("{}set_raw", link_prefix);
let link_drop = format!("{}drop", link_prefix);
let link_deref = format!("{}deref", link_prefix);
let link_deref_mut = format!("{}deref_mut", link_prefix);
let local_prefix = format_ident!("{}__box_", ident);
let local_uninit = format_ident!("{}uninit", local_prefix);
let local_set_raw = format_ident!("{}set_raw", local_prefix);
let local_drop = format_ident!("{}drop", local_prefix);
let local_deref = format_ident!("{}deref", local_prefix);
let local_deref_mut = format_ident!("{}deref_mut", local_prefix);
let span = ident.span();
quote_spanned! {span=>
#[doc(hidden)]
#[export_name = #link_uninit]
unsafe extern "C" fn #local_uninit(
this: *mut ::std::boxed::Box<::std::mem::MaybeUninit<#ident>>,
) {
::std::ptr::write(
this,
::std::boxed::Box::new(::std::mem::MaybeUninit::uninit()),
);
}
#[doc(hidden)]
#[export_name = #link_set_raw]
unsafe extern "C" fn #local_set_raw(
this: *mut ::std::boxed::Box<#ident>,
raw: *mut #ident,
) {
::std::ptr::write(this, ::std::boxed::Box::from_raw(raw));
}
#[doc(hidden)]
#[export_name = #link_drop]
unsafe extern "C" fn #local_drop(this: *mut ::std::boxed::Box<#ident>) {
::std::ptr::drop_in_place(this);
}
#[doc(hidden)]
#[export_name = #link_deref]
unsafe extern "C" fn #local_deref(
this: *const ::std::boxed::Box<::std::mem::MaybeUninit<#ident>>,
) -> *const ::std::mem::MaybeUninit<#ident> {
&**this
}
#[doc(hidden)]
#[export_name = #link_deref_mut]
unsafe extern "C" fn #local_deref_mut(
this: *mut ::std::boxed::Box<::std::mem::MaybeUninit<#ident>>,
) -> *mut ::std::mem::MaybeUninit<#ident> {
&mut **this
}
}
}
fn expand_unique_ptr(namespace: &Namespace, ident: &Ident) -> TokenStream {
let prefix = format!("cxxbridge00$unique_ptr${}{}$", namespace, ident);
let link_null = format!("{}null", prefix);
let link_new = format!("{}new", prefix);
let link_raw = format!("{}raw", prefix);
let link_get = format!("{}get", prefix);
let link_release = format!("{}release", prefix);
let link_drop = format!("{}drop", prefix);
quote! {
unsafe impl ::cxx::private::UniquePtrTarget for #ident {
fn __null() -> *mut ::std::ffi::c_void {
extern "C" {
#[link_name = #link_null]
fn __null(this: *mut *mut ::std::ffi::c_void);
}
let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>();
unsafe { __null(&mut repr) }
repr
}
fn __new(mut value: Self) -> *mut ::std::ffi::c_void {
extern "C" {
#[link_name = #link_new]
fn __new(this: *mut *mut ::std::ffi::c_void, value: *mut #ident);
}
let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>();
unsafe { __new(&mut repr, &mut value) }
repr
}
unsafe fn __raw(raw: *mut Self) -> *mut ::std::ffi::c_void {
extern "C" {
#[link_name = #link_raw]
fn __raw(this: *mut *mut ::std::ffi::c_void, raw: *mut #ident);
}
let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>();
__raw(&mut repr, raw);
repr
}
unsafe fn __get(repr: *mut ::std::ffi::c_void) -> *const Self {
extern "C" {
#[link_name = #link_get]
fn __get(this: *const *mut ::std::ffi::c_void) -> *const #ident;
}
__get(&repr)
}
unsafe fn __release(mut repr: *mut ::std::ffi::c_void) -> *mut Self {
extern "C" {
#[link_name = #link_release]
fn __release(this: *mut *mut ::std::ffi::c_void) -> *mut #ident;
}
__release(&mut repr)
}
unsafe fn __drop(mut repr: *mut ::std::ffi::c_void) {
extern "C" {
#[link_name = #link_drop]
fn __drop(this: *mut *mut ::std::ffi::c_void);
}
__drop(&mut repr);
}
}
}
}
fn expand_return_type(ret: &Option<Type>) -> TokenStream {
match ret {
Some(ret) => quote!(-> #ret),
None => TokenStream::new(),
}
}
fn indirect_return(ret: &Option<Type>, types: &Types) -> bool {
ret.as_ref()
.map_or(false, |ret| types.needs_indirect_abi(ret))
}
fn expand_extern_type(ty: &Type) -> TokenStream {
match ty {
Type::Ident(ident) if ident == "String" => quote!(::cxx::private::RustString),
Type::RustBox(ty) | Type::UniquePtr(ty) => {
let inner = &ty.inner;
quote!(*mut #inner)
}
Type::Ref(ty) => match &ty.inner {
Type::Ident(ident) if ident == "String" => quote!(&::cxx::private::RustString),
_ => quote!(#ty),
},
Type::Str(_) => quote!(::cxx::private::RustStr),
_ => quote!(#ty),
}
}
fn expand_extern_return_type(ret: &Option<Type>, types: &Types) -> TokenStream {
let ret = match ret {
Some(ret) if !types.needs_indirect_abi(ret) => ret,
_ => return TokenStream::new(),
};
let ty = expand_extern_type(ret);
quote!(-> #ty)
}
fn expand_extern_arg(arg: &Var, types: &Types) -> TokenStream {
let ident = &arg.ident;
let ty = expand_extern_type(&arg.ty);
if types.needs_indirect_abi(&arg.ty) {
quote!(#ident: *mut #ty)
} else {
quote!(#ident: #ty)
}
}

46
macro/src/lib.rs Normal file
View File

@ -0,0 +1,46 @@
#![allow(
clippy::large_enum_variant,
clippy::new_without_default,
clippy::or_fun_call,
clippy::toplevel_ref_arg,
clippy::useless_let_if_seq
)]
extern crate proc_macro;
mod expand;
mod namespace;
mod syntax;
use crate::namespace::Namespace;
use proc_macro::TokenStream;
use syn::{parse_macro_input, ItemMod};
/// `#[cxx::bridge] mod ffi { ... }`
///
/// Refer to the crate-level documentation for the explanation of how this macro
/// is intended to be used.
///
/// The only additional thing to note here is namespace support &mdash; if the
/// types and functions on the `extern "C"` side of our bridge are in a
/// namespace, specify that namespace as an argument of the cxx::bridge
/// attribute macro.
///
/// ```
/// #[cxx::bridge(namespace = mycompany::rust)]
/// # mod ffi {}
/// ```
///
/// The types and functions from the `extern "Rust"` side of the bridge will be
/// placed into that same namespace in the generated C++ code.
#[proc_macro_attribute]
pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream {
let _ = syntax::error::ERRORS;
let namespace = parse_macro_input!(args as Namespace);
let ffi = parse_macro_input!(input as ItemMod);
expand::bridge(&namespace, ffi)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

39
macro/src/namespace.rs Normal file
View File

@ -0,0 +1,39 @@
use crate::syntax::ident;
use std::fmt::{self, Display};
use syn::parse::{Parse, ParseStream, Result};
use syn::{Path, Token};
mod kw {
syn::custom_keyword!(namespace);
}
pub struct Namespace {
segments: Vec<String>,
}
impl Parse for Namespace {
fn parse(input: ParseStream) -> Result<Self> {
let mut segments = Vec::new();
if !input.is_empty() {
input.parse::<kw::namespace>()?;
input.parse::<Token![=]>()?;
let path = input.call(Path::parse_mod_style)?;
for segment in path.segments {
ident::check(&segment.ident)?;
segments.push(segment.ident.to_string());
}
input.parse::<Option<Token![,]>>()?;
}
Ok(Namespace { segments })
}
}
impl Display for Namespace {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for segment in &self.segments {
f.write_str(segment)?;
f.write_str("$")?;
}
Ok(())
}
}

1
macro/src/syntax Symbolic link
View File

@ -0,0 +1 @@
../../syntax/

91
src/cxx_string.rs Normal file
View File

@ -0,0 +1,91 @@
use std::borrow::Cow;
use std::fmt::{self, Debug, Display};
use std::slice;
use std::str::{self, Utf8Error};
extern "C" {
#[link_name = "cxxbridge00$cxx_string$data"]
fn string_data(_: &CxxString) -> *const u8;
#[link_name = "cxxbridge00$cxx_string$length"]
fn string_length(_: &CxxString) -> usize;
}
/// Binding to C++ `std::string`.
///
/// # Invariants
///
/// As an invariant of this API and the static analysis of the cxx::bridge
/// macro, in Rust code we can never obtain a `CxxString` by value. C++'s string
/// requires a move constructor and may hold internal pointers, which is not
/// compatible with Rust's move behavior. Instead in Rust code we will only ever
/// look at a CxxString through a reference or smart pointer, as in `&CxxString`
/// or `UniquePtr<CxxString>`.
#[repr(C)]
pub struct CxxString {
_private: [u8; 0],
}
impl CxxString {
/// Returns the length of the string in bytes.
///
/// Matches the behavior of C++ [std::string::size][size].
///
/// [size]: https://en.cppreference.com/w/cpp/string/basic_string/size
pub fn len(&self) -> usize {
unsafe { string_length(self) }
}
/// Returns true if `self` has a length of zero bytes.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns a byte slice of this string's contents.
pub fn as_bytes(&self) -> &[u8] {
let data = self.as_ptr();
let len = self.len();
unsafe { slice::from_raw_parts(data, len) }
}
/// Produces a pointer to the first character of the string.
///
/// Matches the behavior of C++ [std::string::data][data].
///
/// Note that the return type may look like `const char *` but is not a
/// `const char *` in the typical C sense, as C++ strings may contain
/// internal null bytes. As such, the returned pointer only makes sense as a
/// string in combination with the length returned by [`len()`](#len).
///
/// [data]: https://en.cppreference.com/w/cpp/string/basic_string/data
pub fn as_ptr(&self) -> *const u8 {
unsafe { string_data(self) }
}
/// Validates that the C++ string contains UTF-8 data and produces a view of
/// it as a Rust &amp;str, otherwise an error.
pub fn to_str(&self) -> Result<&str, Utf8Error> {
str::from_utf8(self.as_bytes())
}
/// If the contents of the C++ string are valid UTF-8, this function returns
/// a view as a Cow::Borrowed &amp;str. Otherwise replaces any invalid UTF-8
/// sequences with the U+FFFD [replacement character] and returns a
/// Cow::Owned String.
///
/// [replacement character]: https://doc.rust-lang.org/std/char/constant.REPLACEMENT_CHARACTER.html
pub fn to_string_lossy(&self) -> Cow<str> {
String::from_utf8_lossy(self.as_bytes())
}
}
impl Display for CxxString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&String::from_utf8_lossy(self.as_bytes()), f)
}
}
impl Debug for CxxString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(&String::from_utf8_lossy(self.as_bytes()), f)
}
}

168
src/cxxbridge.cc Normal file
View File

@ -0,0 +1,168 @@
#include "../include/cxxbridge.h"
#include <cstring>
#include <memory>
#include <stdexcept>
namespace cxxbridge = cxxbridge00;
extern "C" {
const char *cxxbridge00$cxx_string$data(const std::string &s) noexcept {
return s.data();
}
size_t cxxbridge00$cxx_string$length(const std::string &s) noexcept {
return s.length();
}
// RustString
void cxxbridge00$rust_string$new(cxxbridge::RustString *self) noexcept;
void cxxbridge00$rust_string$clone(cxxbridge::RustString *self,
const cxxbridge::RustString &other) noexcept;
bool cxxbridge00$rust_string$from(cxxbridge::RustString *self, const char *ptr,
size_t len) noexcept;
void cxxbridge00$rust_string$drop(cxxbridge::RustString *self) noexcept;
const char *
cxxbridge00$rust_string$ptr(const cxxbridge::RustString *self) noexcept;
size_t cxxbridge00$rust_string$len(const cxxbridge::RustString *self) noexcept;
// RustStr
bool cxxbridge00$rust_str$valid(const char *ptr, size_t len) noexcept;
} // extern "C"
namespace cxxbridge00 {
RustString::RustString() noexcept { cxxbridge00$rust_string$new(this); }
RustString::RustString(const RustString &other) noexcept {
cxxbridge00$rust_string$clone(this, other);
}
RustString::RustString(RustString &&other) noexcept {
this->repr = other.repr;
cxxbridge00$rust_string$new(&other);
}
RustString::RustString(const char *s) {
auto len = strlen(s);
if (!cxxbridge00$rust_string$from(this, s, len)) {
throw std::invalid_argument("data for RustString is not utf-8");
}
}
RustString::RustString(const std::string &s) {
auto ptr = s.data();
auto len = s.length();
if (!cxxbridge00$rust_string$from(this, ptr, len)) {
throw std::invalid_argument("data for RustString is not utf-8");
}
}
RustString::~RustString() noexcept { cxxbridge00$rust_string$drop(this); }
RustString::operator std::string() const {
return std::string(this->data(), this->size());
}
RustString &RustString::operator=(const RustString &other) noexcept {
if (this != &other) {
cxxbridge00$rust_string$drop(this);
cxxbridge00$rust_string$clone(this, other);
}
return *this;
}
RustString &RustString::operator=(RustString &&other) noexcept {
if (this != &other) {
cxxbridge00$rust_string$drop(this);
this->repr = other.repr;
cxxbridge00$rust_string$new(&other);
}
return *this;
}
const char *RustString::data() const noexcept {
return cxxbridge00$rust_string$ptr(this);
}
size_t RustString::size() const noexcept {
return cxxbridge00$rust_string$len(this);
}
size_t RustString::length() const noexcept {
return cxxbridge00$rust_string$len(this);
}
std::ostream &operator<<(std::ostream &os, const RustString &s) {
os.write(s.data(), s.size());
return os;
}
RustStr::RustStr() noexcept
: repr(Repr{reinterpret_cast<const char *>(this), 0}) {}
RustStr::RustStr(const char *s) : repr(Repr{s, strlen(s)}) {
if (!cxxbridge00$rust_str$valid(this->repr.ptr, this->repr.len)) {
throw std::invalid_argument("data for RustStr is not utf-8");
}
}
RustStr::RustStr(const std::string &s) : repr(Repr{s.data(), s.length()}) {
if (!cxxbridge00$rust_str$valid(this->repr.ptr, this->repr.len)) {
throw std::invalid_argument("data for RustStr is not utf-8");
}
}
RustStr::RustStr(const RustStr &) noexcept = default;
RustStr &RustStr::operator=(RustStr other) noexcept {
this->repr = other.repr;
return *this;
}
RustStr::operator std::string() const {
return std::string(this->data(), this->size());
}
const char *RustStr::data() const noexcept { return this->repr.ptr; }
size_t RustStr::size() const noexcept { return this->repr.len; }
size_t RustStr::length() const noexcept { return this->repr.len; }
RustStr::RustStr(Repr repr_) noexcept : repr(repr_) {}
RustStr::operator Repr() noexcept { return this->repr; }
std::ostream &operator<<(std::ostream &os, const RustStr &s) {
os.write(s.data(), s.size());
return os;
}
} // namespace cxxbridge00
extern "C" {
void cxxbridge00$unique_ptr$std$string$null(
std::unique_ptr<std::string> *ptr) noexcept {
new (ptr) std::unique_ptr<std::string>();
}
void cxxbridge00$unique_ptr$std$string$new(std::unique_ptr<std::string> *ptr,
std::string *value) noexcept {
new (ptr) std::unique_ptr<std::string>(new std::string(std::move(*value)));
}
void cxxbridge00$unique_ptr$std$string$raw(std::unique_ptr<std::string> *ptr,
std::string *raw) noexcept {
new (ptr) std::unique_ptr<std::string>(raw);
}
const std::string *cxxbridge00$unique_ptr$std$string$get(
const std::unique_ptr<std::string> &ptr) noexcept {
return ptr.get();
}
std::string *cxxbridge00$unique_ptr$std$string$release(
std::unique_ptr<std::string> &ptr) noexcept {
return ptr.release();
}
void cxxbridge00$unique_ptr$std$string$drop(
std::unique_ptr<std::string> *ptr) noexcept {
ptr->~unique_ptr();
}
} // extern "C"

16
src/error.rs Normal file
View File

@ -0,0 +1,16 @@
use std::io;
use std::path::StripPrefixError;
use thiserror::Error;
pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Error, Debug)]
#[error(transparent)]
pub(super) enum Error {
#[error("missing OUT_DIR environment variable")]
MissingOutDir,
#[error("failed to locate target dir")]
TargetDir,
Io(#[from] io::Error),
StripPrefix(#[from] StripPrefixError),
}

1
src/gen Symbolic link
View File

@ -0,0 +1 @@
../gen

458
src/lib.rs Normal file
View File

@ -0,0 +1,458 @@
//! This library provides a **safe** mechanism for calling C++ code from Rust
//! and Rust code from C++, not subject to the many ways that things can go
//! wrong when using bindgen or cbindgen to generate unsafe C-style bindings.
//!
//! <br>
//!
//! *Compiler support: requires rustc 1.42+ (beta on January 30, stable on March
//! 12)*
//!
//! <br>
//!
//! # Overview
//!
//! The idea is that we define the signatures of both sides of our FFI boundary
//! embedded together in one Rust module (the next section shows an example).
//! From this, CXX receives a complete picture of the boundary to perform static
//! analyses against the types and function signatures to uphold both Rust's and
//! C++'s invariants and requirements.
//!
//! If everything checks out statically, then CXX uses a pair of code generators
//! to emit the relevant `extern "C"` signatures on both sides together with any
//! necessary static assertions for later in the build process to verify
//! correctness. On the Rust side this code generator is simply an attribute
//! procedural macro. On the C++ side it can be a small Cargo build script if
//! your build is managed by Cargo, or for other build systems like Bazel or
//! Buck we provide a command line tool which generates the header and source
//! file and should be easy to integrate.
//!
//! The resulting FFI bridge operates at zero or negligible overhead, i.e. no
//! copying, no serialization, no memory allocation, no runtime checks needed.
//!
//! The FFI signatures are able to use native types from whichever side they
//! please, such as Rust's `String` or C++'s `std::string`, Rust's `Box` or
//! C++'s `std::unique_ptr`, Rust's `Vec` or C++'s `std::vector`, etc in any
//! combination. CXX guarantees an ABI-compatible signature that both sides
//! understand, based on builtin bindings for key standard library types to
//! expose an idiomatic API on those types to the other language. For example
//! when manipulating a C++ string from Rust, its `len()` method becomes a call
//! of the `size()` member function defined by C++; when manipulation a Rust
//! string from C++, its `size()` member function calls Rust's `len()`.
//!
//! <br>
//!
//! # Example
//!
//! A runnable version of this example is provided under the *demo-rs* directory
//! of https://github.com/dtolnay/cxx (with the C++ side of the implementation
//! in the *demo-cxx* directory). To try it out, jump into demo-rs and run
//! `cargo run`.
//!
//! ```no_run
//! #[cxx::bridge]
//! mod ffi {
//! // Any shared structs, whose fields will be visible to both languages.
//! struct SharedThing {
//! z: i32,
//! y: Box<ThingR>,
//! x: UniquePtr<ThingC>,
//! }
//!
//! extern "C" {
//! // One or more headers with the matching C++ declarations. Our code
//! // generators don't read it but it gets #include'd and used in static
//! // assertions to ensure our picture of the FFI boundary is accurate.
//! include!("demo-cxx/demo.h");
//!
//! // Zero or more opaque types which both languages can pass around but
//! // only C++ can see the fields.
//! type ThingC;
//!
//! // Functions implemented in C++.
//! fn make_demo(appname: &str) -> UniquePtr<ThingC>;
//! fn get_name(thing: &ThingC) -> &CxxString;
//! fn do_thing(state: SharedThing);
//! }
//!
//! extern "Rust" {
//! // Zero or more opaque types which both languages can pass around but
//! // only Rust can see the fields.
//! type ThingR;
//!
//! // Functions implemented in Rust.
//! fn print_r(r: &ThingR);
//! }
//! }
//! #
//! # pub struct ThingR(usize);
//! #
//! # fn print_r(r: &ThingR) {
//! # println!("called back with r={}", r.0);
//! # }
//! #
//! # fn main() {}
//! ```
//!
//! Now we simply provide C++ definitions of all the things in the `extern "C"`
//! block and Rust definitions of all the things in the `extern "Rust"` block,
//! and get to call back and forth safely.
//!
//! Here are links to the complete set of source files involved in the demo:
//!
//! - [demo-rs/src/main.rs](https://github.com/dtolnay/cxx/blob/master/demo-rs/src/main.rs)
//! - [demo-rs/build.rs](https://github.com/dtolnay/cxx/blob/master/demo-rs/build.rs)
//! - [demo-cxx/demo.h](https://github.com/dtolnay/cxx/blob/master/demo-cxx/demo.h)
//! - [demo-cxx/demo.cc](https://github.com/dtolnay/cxx/blob/master/demo-cxx/demo.cc)
//!
//! To look at the code generated in both languages for the example by the CXX
//! code generators:
//!
//! ```console
//! # run Rust code generator and print to stdout
//! # (requires https://github.com/dtolnay/cargo-expand)
//! $ cargo expand --manifest-path demo-rs/Cargo.toml
//!
//! # run C++ code generator and print to stdout
//! $ cargo run --manifest-path cmd/Cargo.toml -- demo-rs/src/main.rs
//! ```
//!
//! <br>
//!
//! # Details
//!
//! As seen in the example, the language of the FFI boundary involves 3 kinds of
//! items:
//!
//! - **Shared structs** &mdash; their fields are made visible to both
//! languages. The definition written within cxx::bridge is the single source
//! of truth.
//!
//! - **Opaque types** &mdash; their fields are secret from the other language.
//! These cannot be passed across the FFI by value but only behind an
//! indirection, such as a reference `&`, a Rust `Box`, or a `UniquePtr`. Can
//! be a type alias for an arbitrarily complicated generic language-specific
//! type depending on your use case.
//!
//! - **Functions** &mdash; implemented in either language, callable from the
//! other language.
//!
//! Within the `extern "C"` part of the CXX bridge we list the types and
//! functions for which C++ is the source of truth, as well as the header(s)
//! that declare those APIs. In the future it's possible that this section could
//! be generated bindgen-style from the headers but for now we need the
//! signatures written out; static assertions will verify that they are
//! accurate.
//!
//! Within the `extern "Rust"` part, we list types and functions for which Rust
//! is the source of truth. These all implicitly refer to the `super` module,
//! the parent module of the CXX bridge. You can think of the two items listed
//! in the example above as being like `use super::ThingR` and `use
//! super::print_r` except re-exported to C++. The parent module will either
//! contain the definitions directly for simple things, or contain the relevant
//! `use` statements to bring them into scope from elsewhere.
//!
//! Your function implementations themselves, whether in C++ or Rust, *do not*
//! need to be defined as `extern "C"` ABI or no\_mangle. CXX will put in the
//! right shims where necessary to make it all work.
//!
//! <br>
//!
//! # Comparison vs bindgen and cbindgen
//!
//! Notice that with CXX there is repetition of all the function signatures:
//! they are typed out once where the implementation is defined (in C++ or Rust)
//! and again inside the cxx::bridge module, though compile-time assertions
//! guarantee these are kept in sync. This is different from [bindgen] and
//! [cbindgen] where function signatures are typed by a human once and the tool
//! consumes them in one language and emits them in the other language.
//!
//! [bindgen]: https://github.com/rust-lang/rust-bindgen
//! [cbindgen]: https://github.com/eqrion/cbindgen/
//!
//! This is because CXX fills a somewhat different role. It is a lower level
//! tool than bindgen or cbindgen in a sense; you can think of it as being a
//! replacement for the concept of `extern "C"` signatures as we know them,
//! rather than a replacement for a bindgen. It would be reasonable to build a
//! higher level bindgen-like tool on top of CXX which consumes a C++ header
//! and/or Rust module (and/or IDL like Thrift) as source of truth and generates
//! the cxx::bridge, eliminating the repetition while leveraging the static
//! analysis safety guarantees of CXX.
//!
//! But note in other ways CXX is higher level than the bindgens, with rich
//! support for common standard library types. Frequently with bindgen when we
//! are dealing with an idiomatic C++ API we would end up manually wrapping that
//! API in C-style raw pointer functions, applying bindgen to get unsafe raw
//! pointer Rust functions, and replicating the API again to expose those
//! idiomatically in Rust. That's a much worse form of repetition because it is
//! unsafe all the way through.
//!
//! By using a CXX bridge as the shared understanding between the languages,
//! rather than `extern "C"` C-style signatures as the shared understanding,
//! common FFI use cases become expressible using 100% safe code.
//!
//! It would also be reasonable to mix and match, using CXX bridge for the 95%
//! of your FFI that is straightforward and doing the remaining few oddball
//! signatures the old fashioned way with bindgen and cbindgen, if for some
//! reason CXX's static restrictions get in the way. Please file an issue if you
//! end up taking this approach so that we know what ways it would be worthwhile
//! to make the tool more expressive.
//!
//! <br>
//!
//! # Cargo-based setup
//!
//! For builds that are orchestrated by Cargo, you will use a build script that
//! runs CXX's C++ code generator and compiles the resulting C++ code along with
//! any other C++ code for your crate.
//!
//! The canonical build script is as follows. The indicated line returns a
//! [`cc::Build`] instance (from the usual widely used `cc` crate) on which you
//! can set up any additional source files and compiler flags as normal.
//!
//! [`cc::Build`]: https://docs.rs/cc/1.0/cc/struct.Build.html
//!
//! ```no_run
//! // build.rs
//!
//! fn main() {
//! cxx::Build::new()
//! .bridge("src/main.rs") // returns a cc::Build
//! .file("../demo-cxx/demo.cc")
//! .flag("-std=c++11")
//! .compile("cxxbridge-demo");
//!
//! println!("cargo:rerun-if-changed=src/main.rs");
//! println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
//! println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
//! }
//! ```
//!
//! <br><br>
//!
//! # Non-Cargo setup
//!
//! For use in non-Cargo builds like Bazel or Buck, CXX provides an alternate
//! way of invoking the C++ code generator as a standalone command line tool.
//! The tool is packaged as the `cxxbridge-cmd` crate on crates.io or can be
//! built from the *cmd* directory of https://github.com/dtolnay/cxx.
//!
//! ```bash
//! $ cargo install cxxbridge-cmd
//!
//! $ cxxbridge src/main.rs --header > path/to/mybridge.h
//! $ cxxbridge src/main.rs > path/to/mybridge.cc
//! ```
//!
//! <br>
//!
//! # Safety
//!
//! Be aware that the design of this library is intentionally restrictive and
//! opinionated! It isn't a goal to be powerful enough to handle arbitrary
//! signatures in either language. Instead this project is about carving out a
//! reasonably expressive set of functionality about which we can make useful
//! safety guarantees today and maybe extend over time. You may find that it
//! takes some practice to use CXX bridge effectively as it won't work in all
//! the ways that you are used to.
//!
//! Some of the considerations that go into ensuring safety are:
//!
//! - By design, our paired code generators work together to control both sides
//! of the FFI boundary. Ordinarily in Rust writing your own `extern "C"`
//! blocks is unsafe because the Rust compiler has no way to know whether the
//! signatures you've written actually match the signatures implemented in the
//! other language. With CXX we achieve that visibility and know what's on the
//! other side.
//!
//! - Our static analysis detects and prevents passing types by value that
//! shouldn't be passed by value from C++ to Rust, for example because they
//! may contain internal pointers that would be screwed up by Rust's move
//! behavior.
//!
//! - To many people's surprise, it is possible to have a struct in Rust and a
//! struct in C++ with exactly the same layout / fields / alignment /
//! everything, and still not the same ABI when passed by value. This is a
//! longstanding bindgen bug that leads to segfaults in absolutely
//! correct-looking code ([rust-lang/rust-bindgen#778]). CXX knows about this
//! and can insert the necessary zero-cost workaround transparently where
//! needed, so go ahead and pass your structs by value without worries. This
//! is made possible by owning both sides of the boundary rather than just
//! one.
//!
//! - Template instantiations: for example in order to expose a UniquePtr\<T\>
//! type in Rust backed by a real C++ unique\_ptr, we have a way of using a
//! Rust trait to connect the behavior back to the template instantiations
//! performed by the other language.
//!
//! [rust-lang/rust-bindgen#778]: https://github.com/rust-lang/rust-bindgen/issues/778
//!
//! <br>
//!
//! # Builtin types
//!
//! In addition to all the primitive types (i32 ⟷ int32_t), the following common
//! types may be used in the fields of shared structs and the arguments and
//! returns of functions.
//!
//! <table>
//! <tr><th>name in Rust</th><th>name in C++</th><th>restrictions</th></tr>
//! <tr><td>String</td><td>cxxbridge::RustString</td><td></td></tr>
//! <tr><td>&amp;str</td><td>cxxbridge::RustStr</td><td></td></tr>
//! <tr><td><a href="https://docs.rs/cxx/0.0/cxx/struct.CxxString.html">CxxString</a></td><td>std::string</td><td><sup><i>cannot be passed by value</i></sup></td></tr>
//! <tr><td>Box&lt;T&gt;</td><td>cxxbridge::RustBox&lt;T&gt;</td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
//! <tr><td><a href="https://docs.rs/cxx/0.0/cxx/struct.UniquePtr.html">UniquePtr&lt;T&gt;</a></td><td>std::unique_ptr&lt;T&gt;</td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr>
//! <tr><td></td><td></td><td></td></tr>
//! </table>
//!
//! The C++ API of the `cxxbridge` namespace is defined by the
//! *include/cxxbridge.h* file in https://github.com/dtolnay/cxx. You will need
//! to include this header in your C++ code when working with those types.
//!
//! The following types are intended to be supported "soon" but are just not
//! implemented yet. I don't expect any of these to be hard to make work but
//! it's a matter of designing a nice API for each in its non-native language.
//!
//! <table>
//! <tr><th>name in Rust</th><th>name in C++</th></tr>
//! <tr><td>&amp;[T]</td><td></td></tr>
//! <tr><td>Vec&lt;T&gt;</td><td></td></tr>
//! <tr><td>BTreeMap&lt;K, V&gt;</td><td></td></tr>
//! <tr><td>HashMap&lt;K, V&gt;</td><td></td></tr>
//! <tr><td></td><td>std::vector&lt;T&gt;</td></tr>
//! <tr><td></td><td>std::map&lt;K, V&gt;</td></tr>
//! <tr><td></td><td>std::unordered_map&lt;K, V&gt;</td></tr>
//! </table>
#![deny(improper_ctypes)]
#![allow(
clippy::large_enum_variant,
clippy::missing_safety_doc,
clippy::module_inception,
clippy::new_without_default,
clippy::or_fun_call,
clippy::ptr_arg,
clippy::toplevel_ref_arg,
clippy::transmute_ptr_to_ptr,
clippy::useless_let_if_seq
)]
mod cxx_string;
mod error;
mod gen;
mod opaque;
mod paths;
mod rust_str;
mod rust_string;
mod syntax;
mod unique_ptr;
mod unwind;
pub use crate::cxx_string::CxxString;
pub use crate::unique_ptr::UniquePtr;
pub use cxxbridge_macro::bridge;
// Not public API.
#[doc(hidden)]
pub mod private {
pub use crate::opaque::Opaque;
pub use crate::rust_str::RustStr;
pub use crate::rust_string::RustString;
pub use crate::unique_ptr::UniquePtrTarget;
pub use crate::unwind::catch_unwind;
}
use crate::error::Result;
use std::fs;
use std::io::{self, Write};
use std::path::Path;
use std::process;
/// The CXX code generator for constructing and compiling C++ code.
///
/// This is intended to be used from Cargo build scripts to execute CXX's
/// C++ code generator, set up any additional compiler flags depending on
/// the use case, and make the C++ compiler invocation.
///
/// <br>
///
/// # Example
///
/// Example of a canonical Cargo build script that builds a CXX bridge:
///
/// ```no_run
/// // build.rs
///
/// fn main() {
/// cxx::Build::new()
/// .bridge("src/main.rs")
/// .file("../demo-cxx/demo.cc")
/// .flag("-std=c++11")
/// .compile("cxxbridge-demo");
///
/// println!("cargo:rerun-if-changed=src/main.rs");
/// println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
/// println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
/// }
/// ```
///
/// A runnable working setup with this build script is shown in the
/// *demo-rs* and *demo-cxx* directories of
/// [https://github.com/dtolnay/cxx](https://github.com/dtolnay/cxx).
///
/// <br>
///
/// # Alternatives
///
/// For use in non-Cargo builds like Bazel or Buck, CXX provides an
/// alternate way of invoking the C++ code generator as a standalone command
/// line tool. The tool is packaged as the `cxxbridge-cmd` crate.
///
/// ```bash
/// $ cargo install cxxbridge-cmd # or build it from the repo
///
/// $ cxxbridge src/main.rs --header > path/to/mybridge.h
/// $ cxxbridge src/main.rs > path/to/mybridge.cc
/// ```
#[must_use]
pub struct Build {
_private: (),
}
impl Build {
/// Begin with a [`cc::Build`] in its default configuration.
pub fn new() -> Self {
Build { _private: () }
}
/// This returns a [`cc::Build`] on which you should continue to set up
/// any additional source files or compiler flags, and lastly call its
/// [`compile`] method to execute the C++ build.
///
/// [`compile`]: https://docs.rs/cc/1.0.49/cc/struct.Build.html#method.compile
#[must_use]
pub fn bridge(&self, rust_source_file: impl AsRef<Path>) -> cc::Build {
match try_generate_bridge(rust_source_file.as_ref()) {
Ok(build) => build,
Err(err) => {
let _ = writeln!(io::stderr(), "\n\ncxxbridge error: {}\n\n", err);
process::exit(1);
}
}
}
}
fn try_generate_bridge(rust_source_file: &Path) -> Result<cc::Build> {
let header = gen::do_generate_header(rust_source_file);
let header_path = paths::out_with_extension(rust_source_file, ".h")?;
fs::create_dir_all(header_path.parent().unwrap())?;
fs::write(&header_path, header)?;
paths::symlink_header(&header_path, rust_source_file);
let bridge = gen::do_generate_bridge(rust_source_file);
let bridge_path = paths::out_with_extension(rust_source_file, ".cc")?;
fs::write(&bridge_path, bridge)?;
let mut build = paths::cc_build();
build.file(&bridge_path);
Ok(build)
}

16
src/opaque.rs Normal file
View File

@ -0,0 +1,16 @@
use std::mem;
// . size = 0
// . align = 1
// . ffi-safe
// . !Send
// . !Sync
#[repr(C, packed)]
pub struct Opaque {
_private: [*const u8; 0],
}
fn _assert() {
let _: [(); 0] = [(); mem::size_of::<Opaque>()];
let _: [(); 1] = [(); mem::align_of::<Opaque>()];
}

72
src/paths.rs Normal file
View File

@ -0,0 +1,72 @@
use crate::error::{Error, Result};
use std::env;
use std::fs;
use std::os;
use std::path::{Path, PathBuf};
fn out_dir() -> Result<PathBuf> {
env::var_os("OUT_DIR")
.map(PathBuf::from)
.ok_or(Error::MissingOutDir)
}
pub(crate) fn cc_build() -> cc::Build {
try_cc_build().unwrap_or_default()
}
fn try_cc_build() -> Result<cc::Build> {
let target_dir = target_dir()?;
let mut build = cc::Build::new();
build.include(target_dir.join("cxxbridge"));
build.include(target_dir.parent().unwrap());
Ok(build)
}
// Symlink the header file into a predictable place. The header generated from
// path/to/mod.rs gets linked to targets/cxxbridge/path/to/mod.h.
pub(crate) fn symlink_header(path: &Path, original: &Path) {
let _ = try_symlink_header(path, original);
}
fn try_symlink_header(path: &Path, original: &Path) -> Result<()> {
let suffix = relative_to_parent_of_target_dir(original)?;
let dst = target_dir()?.join("cxxbridge").join(suffix);
fs::create_dir_all(dst.parent().unwrap())?;
#[cfg(unix)]
os::unix::fs::symlink(path, dst)?;
#[cfg(windows)]
os::windows::fs::symlink_file(path, dst)?;
Ok(())
}
fn relative_to_parent_of_target_dir(original: &Path) -> Result<PathBuf> {
let target_dir = target_dir()?;
let parent_of_target_dir = target_dir.parent().unwrap();
let original = original.canonicalize()?;
let suffix = original.strip_prefix(parent_of_target_dir)?;
Ok(suffix.to_owned())
}
pub(crate) fn out_with_extension(path: &Path, ext: &str) -> Result<PathBuf> {
let mut file_name = path.file_name().unwrap().to_owned();
file_name.push(ext);
let out_dir = out_dir()?;
let rel = relative_to_parent_of_target_dir(path)?;
Ok(out_dir.join(rel).with_file_name(file_name))
}
fn target_dir() -> Result<PathBuf> {
let mut dir = out_dir()?.canonicalize()?;
loop {
if dir.ends_with("target") {
return Ok(dir);
}
if !dir.pop() {
return Err(Error::TargetDir);
}
}
}

30
src/rust_str.rs Normal file
View File

@ -0,0 +1,30 @@
use std::slice;
use std::str;
// Not necessarily ABI compatible with &str. Codegen performs the translation.
#[repr(C)]
#[derive(Copy, Clone)]
pub struct RustStr {
ptr: *const u8,
len: usize,
}
impl RustStr {
pub fn from(s: &str) -> Self {
RustStr {
ptr: s.as_ptr(),
len: s.len(),
}
}
pub unsafe fn as_str<'a>(self) -> &'a str {
let slice = slice::from_raw_parts(self.ptr, self.len);
str::from_utf8_unchecked(slice)
}
}
#[export_name = "cxxbridge00$rust_str$valid"]
unsafe extern "C" fn str_valid(ptr: *const u8, len: usize) -> bool {
let slice = slice::from_raw_parts(ptr, len);
str::from_utf8(slice).is_ok()
}

73
src/rust_string.rs Normal file
View File

@ -0,0 +1,73 @@
use std::mem::{self, ManuallyDrop, MaybeUninit};
use std::ptr;
use std::slice;
use std::str;
#[repr(C)]
pub struct RustString {
repr: String,
}
impl RustString {
pub fn from(s: String) -> Self {
RustString { repr: s }
}
pub fn from_ref(s: &String) -> &Self {
unsafe { std::mem::transmute::<&String, &RustString>(s) }
}
pub fn into_string(self) -> String {
self.repr
}
pub fn as_string(&self) -> &String {
&self.repr
}
}
#[export_name = "cxxbridge00$rust_string$new"]
unsafe extern "C" fn string_new(this: &mut MaybeUninit<String>) {
ptr::write(this.as_mut_ptr(), String::new());
}
#[export_name = "cxxbridge00$rust_string$clone"]
unsafe extern "C" fn string_clone(this: &mut MaybeUninit<String>, other: &String) {
ptr::write(this.as_mut_ptr(), other.clone());
}
#[export_name = "cxxbridge00$rust_string$from"]
unsafe extern "C" fn string_from(
this: &mut MaybeUninit<String>,
ptr: *const u8,
len: usize,
) -> bool {
let slice = slice::from_raw_parts(ptr, len);
match str::from_utf8(slice) {
Ok(s) => {
ptr::write(this.as_mut_ptr(), s.to_owned());
true
}
Err(_) => false,
}
}
#[export_name = "cxxbridge00$rust_string$drop"]
unsafe extern "C" fn string_drop(this: &mut ManuallyDrop<String>) {
ManuallyDrop::drop(this);
}
#[export_name = "cxxbridge00$rust_string$ptr"]
unsafe extern "C" fn string_ptr(this: &String) -> *const u8 {
this.as_ptr()
}
#[export_name = "cxxbridge00$rust_string$len"]
unsafe extern "C" fn string_len(this: &String) -> usize {
this.len()
}
fn _assert() {
let _: [(); mem::size_of::<[usize; 3]>()] = [(); mem::size_of::<String>()];
let _: [(); mem::align_of::<usize>()] = [(); mem::align_of::<String>()];
}

1
src/syntax Symbolic link
View File

@ -0,0 +1 @@
../syntax

173
src/unique_ptr.rs Normal file
View File

@ -0,0 +1,173 @@
use crate::cxx_string::CxxString;
use std::ffi::c_void;
use std::fmt::{self, Debug, Display};
use std::marker::PhantomData;
use std::mem::{self, MaybeUninit};
use std::ptr;
/// Binding to C++ `std::unique_ptr<T, std::default_delete<T>>`.
#[repr(C)]
pub struct UniquePtr<T>
where
T: UniquePtrTarget,
{
repr: *mut c_void,
ty: PhantomData<T>,
}
impl<T> UniquePtr<T>
where
T: UniquePtrTarget,
{
/// Makes a new UniquePtr wrapping a null pointer.
///
/// Matches the behavior of default-constructing a std::unique\_ptr.
pub fn null() -> Self {
UniquePtr {
repr: T::__null(),
ty: PhantomData,
}
}
/// Allocates memory on the heap and makes a UniquePtr pointing to it.
pub fn new(value: T) -> Self {
UniquePtr {
repr: T::__new(value),
ty: PhantomData,
}
}
/// Checks whether the UniquePtr does not own an object.
///
/// This is the opposite of [std::unique_ptr\<T\>::operator bool](https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool).
pub fn is_null(&self) -> bool {
let ptr = unsafe { T::__get(self.repr) };
ptr.is_null()
}
/// Returns a reference to the object owned by this UniquePtr if any,
/// otherwise None.
pub fn as_ref(&self) -> Option<&T> {
unsafe { T::__get(self.repr).as_ref() }
}
/// Consumes the UniquePtr, releasing its ownership of the heap-allocated T.
///
/// Matches the behavior of [std::unique_ptr\<T\>::release](https://en.cppreference.com/w/cpp/memory/unique_ptr/release).
pub fn into_raw(self) -> *mut T {
let ptr = unsafe { T::__release(self.repr) };
mem::forget(self);
ptr
}
/// Constructs a UniquePtr retaking ownership of a pointer previously
/// obtained from `into_raw`.
///
/// # Safety
///
/// This function is unsafe because improper use may lead to memory
/// problems. For example a double-free may occur if the function is called
/// twice on the same raw pointer.
pub unsafe fn from_raw(raw: *mut T) -> Self {
UniquePtr {
repr: T::__raw(raw),
ty: PhantomData,
}
}
}
unsafe impl<T> Send for UniquePtr<T> where T: Send + UniquePtrTarget {}
unsafe impl<T> Sync for UniquePtr<T> where T: Sync + UniquePtrTarget {}
impl<T> Drop for UniquePtr<T>
where
T: UniquePtrTarget,
{
fn drop(&mut self) {
unsafe { T::__drop(self.repr) }
}
}
impl<T> Debug for UniquePtr<T>
where
T: Debug + UniquePtrTarget,
{
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self.as_ref() {
None => formatter.write_str("nullptr"),
Some(value) => Debug::fmt(value, formatter),
}
}
}
impl<T> Display for UniquePtr<T>
where
T: Display + UniquePtrTarget,
{
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self.as_ref() {
None => formatter.write_str("nullptr"),
Some(value) => Display::fmt(value, formatter),
}
}
}
// Methods are private; not intended to be implemented outside of cxxbridge
// codebase.
pub unsafe trait UniquePtrTarget {
#[doc(hidden)]
fn __null() -> *mut c_void;
#[doc(hidden)]
fn __new(value: Self) -> *mut c_void;
#[doc(hidden)]
unsafe fn __raw(raw: *mut Self) -> *mut c_void;
#[doc(hidden)]
unsafe fn __get(repr: *mut c_void) -> *const Self;
#[doc(hidden)]
unsafe fn __release(repr: *mut c_void) -> *mut Self;
#[doc(hidden)]
unsafe fn __drop(repr: *mut c_void);
}
extern "C" {
#[link_name = "cxxbridge00$unique_ptr$std$string$null"]
fn unique_ptr_std_string_null(this: *mut *mut c_void);
#[link_name = "cxxbridge00$unique_ptr$std$string$new"]
fn unique_ptr_std_string_new(this: *mut *mut c_void, value: *mut CxxString);
#[link_name = "cxxbridge00$unique_ptr$std$string$raw"]
fn unique_ptr_std_string_raw(this: *mut *mut c_void, raw: *mut CxxString);
#[link_name = "cxxbridge00$unique_ptr$std$string$get"]
fn unique_ptr_std_string_get(this: *const *mut c_void) -> *const CxxString;
#[link_name = "cxxbridge00$unique_ptr$std$string$release"]
fn unique_ptr_std_string_release(this: *mut *mut c_void) -> *mut CxxString;
#[link_name = "cxxbridge00$unique_ptr$std$string$drop"]
fn unique_ptr_std_string_drop(this: *mut *mut c_void);
}
unsafe impl UniquePtrTarget for CxxString {
fn __null() -> *mut c_void {
let mut repr = ptr::null_mut::<c_void>();
unsafe { unique_ptr_std_string_null(&mut repr) }
repr
}
fn __new(value: Self) -> *mut c_void {
let mut repr = ptr::null_mut::<c_void>();
let mut value = MaybeUninit::new(value);
unsafe { unique_ptr_std_string_new(&mut repr, value.as_mut_ptr() as *mut Self) }
repr
}
unsafe fn __raw(raw: *mut Self) -> *mut c_void {
let mut repr = ptr::null_mut::<c_void>();
unique_ptr_std_string_raw(&mut repr, raw);
repr
}
unsafe fn __get(repr: *mut c_void) -> *const Self {
unique_ptr_std_string_get(&repr)
}
unsafe fn __release(mut repr: *mut c_void) -> *mut Self {
unique_ptr_std_string_release(&mut repr)
}
unsafe fn __drop(mut repr: *mut c_void) {
unique_ptr_std_string_drop(&mut repr);
}
}

20
src/unwind.rs Normal file
View File

@ -0,0 +1,20 @@
use std::io::{self, Write};
use std::panic::{self, UnwindSafe};
use std::process;
pub fn catch_unwind<F, R>(label: &'static str, foreign_call: F) -> R
where
F: FnOnce() -> R + UnwindSafe,
{
match panic::catch_unwind(foreign_call) {
Ok(ret) => ret,
Err(_) => abort(label),
}
}
#[cold]
fn abort(label: &'static str) -> ! {
let mut stdout = io::stdout();
let _ = writeln!(stdout, "Error: panic in ffi function {}, aborting.", label);
process::abort();
}

40
syntax/atom.rs Normal file
View File

@ -0,0 +1,40 @@
use proc_macro2::Ident;
#[derive(Copy, Clone, PartialEq)]
pub enum Atom {
Bool,
U8,
U16,
U32,
U64,
Usize,
I8,
I16,
I32,
I64,
Isize,
CxxString,
RustString,
}
impl Atom {
pub fn from(ident: &Ident) -> Option<Self> {
use self::Atom::*;
match ident.to_string().as_str() {
"bool" => Some(Bool),
"u8" => Some(U8),
"u16" => Some(U16),
"u32" => Some(U32),
"u64" => Some(U64),
"usize" => Some(Usize),
"i8" => Some(I8),
"i16" => Some(I16),
"i32" => Some(I32),
"i64" => Some(I64),
"isize" => Some(Isize),
"CxxString" => Some(CxxString),
"String" => Some(RustString),
_ => None,
}
}
}

68
syntax/attrs.rs Normal file
View File

@ -0,0 +1,68 @@
use crate::syntax::{Derive, Doc};
use proc_macro2::Ident;
use syn::parse::{ParseStream, Parser};
use syn::{Attribute, Error, LitStr, Path, Result, Token};
pub(super) fn parse_doc(attrs: &[Attribute]) -> Result<Doc> {
let mut doc = Doc::new();
let derives = None;
parse(attrs, &mut doc, derives)?;
Ok(doc)
}
pub(super) fn parse(
attrs: &[Attribute],
doc: &mut Doc,
mut derives: Option<&mut Vec<Ident>>,
) -> Result<()> {
for attr in attrs {
if attr.path.is_ident("doc") {
let lit = parse_doc_attribute.parse2(attr.tokens.clone())?;
doc.push(lit);
continue;
} else if attr.path.is_ident("derive") {
if let Some(derives) = &mut derives {
derives.extend(attr.parse_args_with(parse_derive_attribute)?);
continue;
}
}
return Err(Error::new_spanned(attr, "unsupported attribute"));
}
Ok(())
}
fn parse_doc_attribute(input: ParseStream) -> Result<LitStr> {
input.parse::<Token![=]>()?;
let lit: LitStr = input.parse()?;
Ok(lit)
}
fn parse_derive_attribute(input: ParseStream) -> Result<Vec<Ident>> {
input
.parse_terminated::<Path, Token![,]>(Path::parse_mod_style)?
.into_iter()
.map(|path| match path.get_ident() {
Some(ident) if Derive::from(ident).is_some() => Ok(ident.clone()),
_ => Err(Error::new_spanned(path, "unsupported derive")),
})
.collect()
}
impl Derive {
pub fn from(ident: &Ident) -> Option<Self> {
match ident.to_string().as_str() {
"Clone" => Some(Derive::Clone),
"Copy" => Some(Derive::Copy),
_ => None,
}
}
}
impl AsRef<str> for Derive {
fn as_ref(&self) -> &str {
match self {
Derive::Clone => "Clone",
Derive::Copy => "Copy",
}
}
}

203
syntax/check.rs Normal file
View File

@ -0,0 +1,203 @@
use crate::syntax::atom::Atom::{self, *};
use crate::syntax::{error, ident, Api, ExternFn, Ty1, Type, Types, Var};
use proc_macro2::Ident;
use syn::{Error, Result};
pub(crate) fn typecheck(apis: &[Api], types: &Types) -> Result<()> {
let mut errors = Vec::new();
for ty in types {
match ty {
Type::Ident(ident) => {
if Atom::from(ident).is_none()
&& !types.structs.contains_key(ident)
&& !types.cxx.contains(ident)
&& !types.rust.contains(ident)
{
errors.push(unsupported_type(ident));
}
}
Type::RustBox(ptr) => {
if let Type::Ident(ident) = &ptr.inner {
if types.cxx.contains(ident) {
errors.push(unsupported_cxx_type_in_box(ptr));
}
if Atom::from(ident).is_none() {
continue;
}
}
errors.push(unsupported_box_target(ptr));
}
Type::UniquePtr(ptr) => {
if let Type::Ident(ident) = &ptr.inner {
if types.rust.contains(ident) {
errors.push(unsupported_rust_type_in_unique_ptr(ptr));
}
match Atom::from(ident) {
None | Some(CxxString) => continue,
_ => {}
}
}
errors.push(unsupported_unique_ptr_target(ptr));
}
_ => {}
}
}
for api in apis {
match api {
Api::Struct(strct) => {
for field in &strct.fields {
if is_unsized(&field.ty, types) {
errors.push(field_by_value(field, types));
}
}
}
Api::CxxFunction(efn) | Api::RustFunction(efn) => {
for arg in &efn.args {
if is_unsized(&arg.ty, types) {
errors.push(argument_by_value(arg, types));
}
}
if let Some(ty) = &efn.ret {
if is_unsized(ty, types) {
errors.push(return_by_value(ty, types));
}
}
}
_ => {}
}
}
for api in apis {
if let Api::CxxFunction(efn) = api {
errors.extend(check_mut_return_restriction(efn).err());
}
if let Api::CxxFunction(efn) | Api::RustFunction(efn) = api {
errors.extend(check_multiple_arg_lifetimes(efn).err());
}
}
ident::check_all(apis, &mut errors);
let mut iter = errors.into_iter();
let mut all_errors = match iter.next() {
Some(err) => err,
None => return Ok(()),
};
for err in iter {
all_errors.combine(err);
}
Err(all_errors)
}
fn is_unsized(ty: &Type, types: &Types) -> bool {
let ident = match ty {
Type::Ident(ident) => ident,
_ => return false,
};
ident == "CxxString" || types.cxx.contains(ident) || types.rust.contains(ident)
}
fn check_mut_return_restriction(efn: &ExternFn) -> Result<()> {
match &efn.ret {
Some(Type::Ref(ty)) if ty.mutability.is_some() => {}
_ => return Ok(()),
}
for arg in &efn.args {
if let Type::Ref(ty) = &arg.ty {
if ty.mutability.is_some() {
return Ok(());
}
}
}
Err(Error::new_spanned(
efn,
"&mut return type is not allowed unless there is a &mut argument",
))
}
fn check_multiple_arg_lifetimes(efn: &ExternFn) -> Result<()> {
match &efn.ret {
Some(Type::Ref(_)) => {}
_ => return Ok(()),
}
let mut reference_args = 0;
for arg in &efn.args {
if let Type::Ref(_) = &arg.ty {
reference_args += 1;
}
}
if reference_args == 1 {
Ok(())
} else {
Err(Error::new_spanned(
efn,
"functions that return a reference must take exactly one input reference",
))
}
}
fn describe(ty: &Type, types: &Types) -> String {
match ty {
Type::Ident(ident) => {
if types.structs.contains_key(ident) {
"struct".to_owned()
} else if types.cxx.contains(ident) {
"C++ type".to_owned()
} else if types.rust.contains(ident) {
"opaque Rust type".to_owned()
} else if Atom::from(ident) == Some(CxxString) {
"C++ string".to_owned()
} else {
ident.to_string()
}
}
Type::RustBox(_) => "Box".to_owned(),
Type::UniquePtr(_) => "unique_ptr".to_owned(),
Type::Ref(_) => "reference".to_owned(),
Type::Str(_) => "&str".to_owned(),
}
}
fn unsupported_type(ident: &Ident) -> Error {
Error::new(ident.span(), "unsupported type")
}
fn unsupported_cxx_type_in_box(unique_ptr: &Ty1) -> Error {
Error::new_spanned(unique_ptr, error::BOX_CXX_TYPE.msg)
}
fn unsupported_box_target(unique_ptr: &Ty1) -> Error {
Error::new_spanned(unique_ptr, "unsupported target type of Box")
}
fn unsupported_rust_type_in_unique_ptr(unique_ptr: &Ty1) -> Error {
Error::new_spanned(unique_ptr, "unique_ptr of a Rust type is not supported yet")
}
fn unsupported_unique_ptr_target(unique_ptr: &Ty1) -> Error {
Error::new_spanned(unique_ptr, "unsupported unique_ptr target type")
}
fn field_by_value(field: &Var, types: &Types) -> Error {
let desc = describe(&field.ty, types);
let message = format!("using {} by value is not supported", desc);
Error::new_spanned(field, message)
}
fn argument_by_value(arg: &Var, types: &Types) -> Error {
let desc = describe(&arg.ty, types);
let message = format!("passing {} by value is not supported", desc);
Error::new_spanned(arg, message)
}
fn return_by_value(ty: &Type, types: &Types) -> Error {
let desc = describe(ty, types);
let message = format!("returning {} by value is not supported", desc);
Error::new_spanned(ty, message)
}

37
syntax/doc.rs Normal file
View File

@ -0,0 +1,37 @@
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::LitStr;
pub struct Doc {
fragments: Vec<LitStr>,
}
impl Doc {
pub fn new() -> Self {
Doc {
fragments: Vec::new(),
}
}
pub fn push(&mut self, lit: LitStr) {
self.fragments.push(lit);
}
pub fn to_string(&self) -> String {
let mut doc = String::new();
for lit in &self.fragments {
doc += &lit.value();
doc.push('\n');
}
doc
}
}
impl ToTokens for Doc {
fn to_tokens(&self, tokens: &mut TokenStream) {
let fragments = &self.fragments;
tokens.extend(quote! {
#(#[doc = #fragments])*
});
}
}

70
syntax/error.rs Normal file
View File

@ -0,0 +1,70 @@
use std::fmt::{self, Display};
#[derive(Copy, Clone)]
pub struct Error {
pub msg: &'static str,
pub label: Option<&'static str>,
pub note: Option<&'static str>,
}
impl Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
self.msg.fmt(formatter)
}
}
pub static ERRORS: &[Error] = &[
BOX_CXX_TYPE,
CXXBRIDGE_RESERVED,
CXX_STRING_BY_VALUE,
CXX_TYPE_BY_VALUE,
DOUBLE_UNDERSCORE,
RUST_TYPE_BY_VALUE,
USE_NOT_ALLOWED,
];
pub static BOX_CXX_TYPE: Error = Error {
msg: "Box of a C++ type is not supported yet",
label: None,
note: Some("hint: use UniquePtr<>"),
};
pub static CXXBRIDGE_RESERVED: Error = Error {
msg: "identifiers starting with cxxbridge are reserved",
label: Some("reserved identifier"),
note: Some("identifiers starting with cxxbridge are reserved"),
};
pub static CXX_STRING_BY_VALUE: Error = Error {
msg: "C++ string by value is not supported",
label: None,
note: Some("hint: wrap it in a UniquePtr<>"),
};
pub static CXX_TYPE_BY_VALUE: Error = Error {
msg: "C++ type by value is not supported",
label: None,
note: Some("hint: wrap it in a Box<> or UniquePtr<>"),
};
pub static DOUBLE_UNDERSCORE: Error = Error {
msg: "identifiers containing double underscore are reserved in C++",
label: Some("reserved identifier"),
note: Some("identifiers containing double underscore are reserved in C++"),
};
pub static RUST_TYPE_BY_VALUE: Error = Error {
msg: "opaque Rust type by value is not supported",
label: None,
note: Some("hint: wrap it in a Box<>"),
};
pub static USE_NOT_ALLOWED: Error = Error {
msg: "`use` items are not allowed within cxx bridge",
label: Some("not allowed"),
note: Some(
"`use` items are not allowed within cxx bridge; only types defined\n\
within your bridge, primitive types, or types exported by the cxx\n\
crate may be used",
),
};

37
syntax/ident.rs Normal file
View File

@ -0,0 +1,37 @@
use crate::syntax::{error, Api};
use proc_macro2::Ident;
use syn::{Error, Result};
pub(crate) fn check(ident: &Ident) -> Result<()> {
let s = ident.to_string();
if s.contains("__") {
Err(Error::new(ident.span(), error::DOUBLE_UNDERSCORE.msg))
} else if s.starts_with("cxxbridge") {
Err(Error::new(ident.span(), error::CXXBRIDGE_RESERVED.msg))
} else {
Ok(())
}
}
pub(crate) fn check_all(apis: &[Api], errors: &mut Vec<Error>) {
for api in apis {
match api {
Api::Include(_) => {}
Api::Struct(strct) => {
errors.extend(check(&strct.ident).err());
for field in &strct.fields {
errors.extend(check(&field.ident).err());
}
}
Api::CxxType(ety) | Api::RustType(ety) => {
errors.extend(check(&ety.ident).err());
}
Api::CxxFunction(efn) | Api::RustFunction(efn) => {
errors.extend(check(&efn.ident).err());
for arg in &efn.args {
errors.extend(check(&arg.ident).err());
}
}
}
}
}

31
syntax/impls.rs Normal file
View File

@ -0,0 +1,31 @@
use crate::syntax::{Ref, Ty1};
use std::hash::{Hash, Hasher};
impl Eq for Ty1 {}
impl PartialEq for Ty1 {
fn eq(&self, other: &Ty1) -> bool {
self.name == other.name && self.inner == other.inner
}
}
impl Hash for Ty1 {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.inner.hash(state);
}
}
impl Eq for Ref {}
impl PartialEq for Ref {
fn eq(&self, other: &Ref) -> bool {
self.inner == other.inner
}
}
impl Hash for Ref {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash(state);
}
}

92
syntax/mod.rs Normal file
View File

@ -0,0 +1,92 @@
// Functionality that is shared between the cxxbridge macro and the cmd.
pub mod atom;
mod attrs;
pub mod check;
mod doc;
pub mod error;
pub mod ident;
mod impls;
mod parse;
pub mod set;
mod tokens;
pub mod types;
use proc_macro2::Ident;
use syn::{LitStr, Token};
pub use self::atom::Atom;
pub use self::doc::Doc;
pub use self::parse::parse_items;
pub use self::types::Types;
pub enum Api {
Include(LitStr),
Struct(Struct),
CxxType(ExternType),
CxxFunction(ExternFn),
RustType(ExternType),
RustFunction(ExternFn),
}
pub struct ExternType {
pub doc: Doc,
pub type_token: Token![type],
pub ident: Ident,
}
pub struct Struct {
pub doc: Doc,
pub derives: Vec<Ident>,
pub struct_token: Token![struct],
pub ident: Ident,
pub fields: Vec<Var>,
}
pub struct ExternFn {
pub doc: Doc,
pub fn_token: Token![fn],
pub ident: Ident,
pub receiver: Option<Receiver>,
pub args: Vec<Var>,
pub ret: Option<Type>,
pub semi_token: Token![;],
}
pub struct Var {
pub ident: Ident,
pub ty: Type,
}
pub struct Receiver {
pub mutability: Option<Token![mut]>,
pub ident: Ident,
}
#[derive(Hash, Eq, PartialEq)]
pub enum Type {
Ident(Ident),
RustBox(Box<Ty1>),
UniquePtr(Box<Ty1>),
Ref(Box<Ref>),
Str(Box<Ref>),
}
pub struct Ty1 {
pub name: Ident,
pub langle: Token![<],
pub inner: Type,
pub rangle: Token![>],
}
pub struct Ref {
pub ampersand: Token![&],
pub mutability: Option<Token![mut]>,
pub inner: Type,
}
#[derive(Copy, Clone, PartialEq)]
pub enum Derive {
Clone,
Copy,
}

265
syntax/parse.rs Normal file
View File

@ -0,0 +1,265 @@
use crate::syntax::{
attrs, error, Api, Atom, Doc, ExternFn, ExternType, Receiver, Ref, Struct, Ty1, Type, Var,
};
use proc_macro2::Ident;
use quote::quote;
use syn::{
Abi, Error, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType, GenericArgument, Item,
ItemForeignMod, ItemStruct, Pat, PathArguments, Result, ReturnType, Type as RustType,
};
#[derive(Copy, Clone)]
enum Lang {
Cxx,
Rust,
}
pub fn parse_items(items: Vec<Item>) -> Result<Vec<Api>> {
let mut apis = Vec::new();
for item in items {
match item {
Item::Struct(item) => {
let strct = parse_struct(item)?;
apis.push(strct);
}
Item::ForeignMod(foreign_mod) => {
let functions = parse_foreign_mod(foreign_mod)?;
apis.extend(functions);
}
Item::Use(item) => return Err(Error::new_spanned(item, error::USE_NOT_ALLOWED)),
_ => return Err(Error::new_spanned(item, "unsupported item")),
}
}
Ok(apis)
}
fn parse_struct(item: ItemStruct) -> Result<Api> {
let generics = &item.generics;
if !generics.params.is_empty() || generics.where_clause.is_some() {
let struct_token = item.struct_token;
let ident = &item.ident;
let where_clause = &generics.where_clause;
let span = quote!(#struct_token #ident #generics #where_clause);
return Err(Error::new_spanned(
span,
"struct with generic parameters is not supported yet",
));
}
let mut doc = Doc::new();
let mut derives = Vec::new();
attrs::parse(&item.attrs, &mut doc, Some(&mut derives))?;
check_reserved_name(&item.ident)?;
match item.fields {
Fields::Named(fields) => Ok(Api::Struct(Struct {
doc,
derives,
struct_token: item.struct_token,
ident: item.ident,
fields: fields
.named
.into_iter()
.map(|field| {
Ok(Var {
ident: field.ident.unwrap(),
ty: parse_type(&field.ty)?,
})
})
.collect::<Result<_>>()?,
})),
Fields::Unit => Err(Error::new_spanned(item, "unit structs are not supported")),
Fields::Unnamed(_) => Err(Error::new_spanned(item, "tuple structs are not supported")),
}
}
fn parse_foreign_mod(foreign_mod: ItemForeignMod) -> Result<Vec<Api>> {
let lang = parse_lang(foreign_mod.abi)?;
let api_type = match lang {
Lang::Cxx => Api::CxxType,
Lang::Rust => Api::RustType,
};
let api_function = match lang {
Lang::Cxx => Api::CxxFunction,
Lang::Rust => Api::RustFunction,
};
let mut items = Vec::new();
for foreign in &foreign_mod.items {
match foreign {
ForeignItem::Type(foreign) => {
check_reserved_name(&foreign.ident)?;
let ety = parse_extern_type(foreign)?;
items.push(api_type(ety));
}
ForeignItem::Fn(foreign) => {
let efn = parse_extern_fn(foreign)?;
items.push(api_function(efn));
}
ForeignItem::Macro(foreign) if foreign.mac.path.is_ident("include") => {
let include = foreign.mac.parse_body()?;
items.push(Api::Include(include));
}
_ => return Err(Error::new_spanned(foreign, "unsupported foreign item")),
}
}
Ok(items)
}
fn parse_lang(abi: Abi) -> Result<Lang> {
let name = match &abi.name {
Some(name) => name,
None => {
return Err(Error::new_spanned(
abi,
"ABI name is required, extern \"C\" or extern \"Rust\"",
));
}
};
match name.value().as_str() {
"C" => Ok(Lang::Cxx),
"Rust" => Ok(Lang::Rust),
_ => Err(Error::new_spanned(abi, "unrecognized ABI")),
}
}
fn parse_extern_type(foreign_type: &ForeignItemType) -> Result<ExternType> {
let doc = attrs::parse_doc(&foreign_type.attrs)?;
let type_token = foreign_type.type_token;
let ident = foreign_type.ident.clone();
Ok(ExternType {
doc,
type_token,
ident,
})
}
fn parse_extern_fn(foreign_fn: &ForeignItemFn) -> Result<ExternFn> {
let generics = &foreign_fn.sig.generics;
if !generics.params.is_empty() || generics.where_clause.is_some() {
return Err(Error::new_spanned(
foreign_fn,
"extern function with generic parameters is not supported yet",
));
}
if let Some(variadic) = &foreign_fn.sig.variadic {
return Err(Error::new_spanned(
variadic,
"variadic function is not supported yet",
));
}
let mut receiver = None;
let mut args = Vec::new();
for arg in &foreign_fn.sig.inputs {
match arg {
FnArg::Receiver(receiver) => {
return Err(Error::new_spanned(receiver, "unsupported signature"))
}
FnArg::Typed(arg) => {
let ident = match arg.pat.as_ref() {
Pat::Ident(pat) => pat.ident.clone(),
_ => return Err(Error::new_spanned(arg, "unsupported signature")),
};
let ty = parse_type(&arg.ty)?;
if ident != "self" {
args.push(Var { ident, ty });
continue;
}
if let Type::Ref(reference) = ty {
if let Type::Ident(ident) = reference.inner {
receiver = Some(Receiver {
mutability: reference.mutability,
ident,
});
continue;
}
}
return Err(Error::new_spanned(arg, "unsupported method receiver"));
}
}
}
let ret = match &foreign_fn.sig.output {
ReturnType::Default => None,
ReturnType::Type(_, ty) => Some(parse_type(ty)?),
};
let doc = attrs::parse_doc(&foreign_fn.attrs)?;
let fn_token = foreign_fn.sig.fn_token;
let ident = foreign_fn.sig.ident.clone();
let semi_token = foreign_fn.semi_token;
Ok(ExternFn {
doc,
fn_token,
ident,
receiver,
args,
ret,
semi_token,
})
}
fn parse_type(ty: &RustType) -> Result<Type> {
match &ty {
RustType::Reference(ty) => {
let inner = parse_type(&ty.elem)?;
let which = match &inner {
Type::Ident(ident) if ident == "str" => {
if ty.mutability.is_some() {
return Err(Error::new_spanned(ty, "unsupported type"));
} else {
Type::Str
}
}
_ => Type::Ref,
};
return Ok(which(Box::new(Ref {
ampersand: ty.and_token,
mutability: ty.mutability,
inner,
})));
}
RustType::Path(ty) => {
let path = &ty.path;
if ty.qself.is_none() && path.leading_colon.is_none() && path.segments.len() == 1 {
let segment = &path.segments[0];
let ident = segment.ident.clone();
match &segment.arguments {
PathArguments::None => return Ok(Type::Ident(ident)),
PathArguments::AngleBracketed(generic) => {
if ident == "UniquePtr" && generic.args.len() == 1 {
if let GenericArgument::Type(arg) = &generic.args[0] {
let inner = parse_type(arg)?;
return Ok(Type::UniquePtr(Box::new(Ty1 {
name: ident,
langle: generic.lt_token,
inner,
rangle: generic.gt_token,
})));
}
} else if ident == "Box" && generic.args.len() == 1 {
if let GenericArgument::Type(arg) = &generic.args[0] {
let inner = parse_type(arg)?;
return Ok(Type::RustBox(Box::new(Ty1 {
name: ident,
langle: generic.lt_token,
inner,
rangle: generic.gt_token,
})));
}
}
}
PathArguments::Parenthesized(_) => {}
}
}
}
_ => {}
}
Err(Error::new_spanned(ty, "unsupported type"))
}
fn check_reserved_name(ident: &Ident) -> Result<()> {
if ident == "Box" || ident == "UniquePtr" || Atom::from(ident).is_some() {
Err(Error::new(ident.span(), "reserved name"))
} else {
Ok(())
}
}

47
syntax/set.rs Normal file
View File

@ -0,0 +1,47 @@
use std::collections::HashSet;
use std::hash::Hash;
use std::slice;
pub struct OrderedSet<'a, T> {
set: HashSet<&'a T>,
vec: Vec<&'a T>,
}
impl<'a, T> OrderedSet<'a, T>
where
T: Hash + Eq,
{
pub fn new() -> Self {
OrderedSet {
set: HashSet::new(),
vec: Vec::new(),
}
}
pub fn insert(&mut self, value: &'a T) {
if self.set.insert(value) {
self.vec.push(value);
}
}
pub fn contains(&self, value: &T) -> bool {
self.set.contains(value)
}
}
impl<'s, 'a, T> IntoIterator for &'s OrderedSet<'a, T> {
type Item = &'a T;
type IntoIter = Iter<'s, 'a, T>;
fn into_iter(self) -> Self::IntoIter {
Iter(self.vec.iter())
}
}
pub struct Iter<'s, 'a, T>(slice::Iter<'s, &'a T>);
impl<'s, 'a, T> Iterator for Iter<'s, 'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().copied()
}
}

67
syntax/tokens.rs Normal file
View File

@ -0,0 +1,67 @@
use crate::syntax::{Derive, ExternFn, Ref, Ty1, Type, Var};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote_spanned, ToTokens};
use syn::Token;
impl ToTokens for Type {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Type::Ident(ident) => {
if ident == "CxxString" {
let span = ident.span();
tokens.extend(quote_spanned!(span=> ::cxx::));
}
ident.to_tokens(tokens);
}
Type::RustBox(ty) | Type::UniquePtr(ty) => ty.to_tokens(tokens),
Type::Ref(r) | Type::Str(r) => r.to_tokens(tokens),
}
}
}
impl ToTokens for Var {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.ident.to_tokens(tokens);
Token![:](self.ident.span()).to_tokens(tokens);
self.ty.to_tokens(tokens);
}
}
impl ToTokens for Ty1 {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.name == "UniquePtr" {
let span = self.name.span();
tokens.extend(quote_spanned!(span=> ::cxx::));
}
self.name.to_tokens(tokens);
self.langle.to_tokens(tokens);
self.inner.to_tokens(tokens);
self.rangle.to_tokens(tokens);
}
}
impl ToTokens for Ref {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.ampersand.to_tokens(tokens);
self.mutability.to_tokens(tokens);
self.inner.to_tokens(tokens);
}
}
impl ToTokens for Derive {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = match self {
Derive::Clone => "Clone",
Derive::Copy => "Copy",
};
Ident::new(name, Span::call_site()).to_tokens(tokens);
}
}
impl ToTokens for ExternFn {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.fn_token.to_tokens(tokens);
self.ident.to_tokens(tokens);
self.semi_token.to_tokens(tokens);
}
}

121
syntax/types.rs Normal file
View File

@ -0,0 +1,121 @@
use crate::syntax::atom::Atom::{self, *};
use crate::syntax::set::OrderedSet as Set;
use crate::syntax::{Api, Derive, ExternType, Struct, Type};
use proc_macro2::Ident;
use quote::quote;
use std::collections::BTreeMap as Map;
use syn::{Error, Result};
pub struct Types<'a> {
pub all: Set<'a, Type>,
pub structs: Map<Ident, &'a Struct>,
pub cxx: Set<'a, Ident>,
pub rust: Set<'a, Ident>,
}
impl<'a> Types<'a> {
pub fn collect(apis: &'a [Api]) -> Result<Self> {
let mut all = Set::new();
let mut structs = Map::new();
let mut cxx = Set::new();
let mut rust = Set::new();
fn visit<'a>(all: &mut Set<'a, Type>, ty: &'a Type) {
all.insert(ty);
match ty {
Type::Ident(_) | Type::Str(_) => {}
Type::RustBox(ty) | Type::UniquePtr(ty) => visit(all, &ty.inner),
Type::Ref(r) => visit(all, &r.inner),
}
}
for api in apis {
match api {
Api::Include(_) => {}
Api::Struct(strct) => {
let ident = &strct.ident;
if structs.contains_key(ident) || cxx.contains(ident) || rust.contains(ident) {
return Err(duplicate_struct(strct));
}
structs.insert(strct.ident.clone(), strct);
for field in &strct.fields {
visit(&mut all, &field.ty);
}
}
Api::CxxType(ety) => {
let ident = &ety.ident;
if structs.contains_key(ident) || cxx.contains(ident) || rust.contains(ident) {
return Err(duplicate_type(ety));
}
cxx.insert(ident);
}
Api::RustType(ety) => {
let ident = &ety.ident;
if structs.contains_key(ident) || cxx.contains(ident) || rust.contains(ident) {
return Err(duplicate_type(ety));
}
rust.insert(ident);
}
Api::CxxFunction(efn) | Api::RustFunction(efn) => {
for arg in &efn.args {
visit(&mut all, &arg.ty);
}
if let Some(ret) = &efn.ret {
visit(&mut all, ret);
}
}
}
}
Ok(Types {
all,
structs,
cxx,
rust,
})
}
pub fn needs_indirect_abi(&self, ty: &Type) -> bool {
match ty {
Type::Ident(ident) => {
if let Some(strct) = self.structs.get(ident) {
!self.is_pod(strct)
} else {
Atom::from(ident) == Some(RustString)
}
}
_ => false,
}
}
pub fn is_pod(&self, strct: &Struct) -> bool {
for derive in &strct.derives {
if *derive == Derive::Copy {
return true;
}
}
false
}
}
impl<'t, 'a> IntoIterator for &'t Types<'a> {
type Item = &'a Type;
type IntoIter = crate::syntax::set::Iter<'t, 'a, Type>;
fn into_iter(self) -> Self::IntoIter {
self.all.into_iter()
}
}
fn duplicate_struct(strct: &Struct) -> Error {
let struct_token = strct.struct_token;
let ident = &strct.ident;
let range = quote!(#struct_token #ident);
Error::new_spanned(range, "duplicate type")
}
fn duplicate_type(ety: &ExternType) -> Error {
let type_token = ety.type_token;
let ident = &ety.ident;
let range = quote!(#type_token #ident);
Error::new_spanned(range, "duplicate type")
}

6
tests/compiletest.rs Normal file
View File

@ -0,0 +1,6 @@
#[rustversion::attr(not(nightly), ignore)]
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}

View File

@ -0,0 +1,10 @@
#[cxx::bridge]
mod ffi {
extern "Rust" {
type TypeR;
}
}
struct TypeR(str);
fn main() {}

View File

@ -0,0 +1,12 @@
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> $DIR/opaque_not_sized.rs:1:1
|
1 | #[cxx::bridge]
| ^^^^^^^^^^^^^^
| |
| doesn't have a size known at compile-time
| required by this bound in `ffi::_::__assert_sized`
|
= help: within `TypeR`, the trait `std::marker::Sized` is not implemented for `str`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= note: required because it appears within the type `TypeR`