Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Safe interop between Rust and C++

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT
NotificationsYou must be signed in to change notification settings

dtolnay/cxx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

githubcrates.iodocs.rsbuild status

This library provides asafe mechanism for calling C++ code from Rust andRust code from C++, not subject to the many ways that things can go wrong whenusing bindgen or cbindgen to generate unsafe C-style bindings.

This doesn't change the fact that 100% of C++ code is unsafe. When auditing aproject, you would be on the hook for auditing all the unsafe Rust code andall the C++ code. The core safety claim under this new model is that auditingjust the C++ side would be sufficient to catch all problems, i.e. the Rust sidecan be 100% safe.

[dependencies]cxx ="1.0"[build-dependencies]cxx-build ="1.0"

Compiler support: requires rustc 1.73+ and c++11 or newer
Release notes


Guide

Please seehttps://cxx.rs for a tutorial, reference material, and examplecode.


Overview

The idea is that we define the signatures of both sides of our FFI boundaryembedded together in one Rust module (the next section shows an example). Fromthis, CXX receives a complete picture of the boundary to perform static analysesagainst the types and function signatures to uphold both Rust's and C++'sinvariants and requirements.

If everything checks out statically, then CXX uses a pair of code generators toemit the relevantextern "C" signatures on both sides together with anynecessary static assertions for later in the build process to verifycorrectness. On the Rust side this code generator is simply an attributeprocedural macro. On the C++ side it can be a small Cargo build script if yourbuild is managed by Cargo, or for other build systems like Bazel or Buck weprovide a command line tool which generates the header and source file andshould be easy to integrate.

The resulting FFI bridge operates at zero or negligible overhead, i.e. nocopying, 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'sString or C++'sstd::string, Rust'sBox or C++'sstd::unique_ptr, Rust'sVec or C++'sstd::vector, etc in any combination.CXX guarantees an ABI-compatible signature that both sides understand, based onbuiltin bindings for key standard library types to expose an idiomatic API onthose types to the other language. For example when manipulating a C++ stringfrom Rust, itslen() method becomes a call of thesize() member functiondefined by C++; when manipulating a Rust string from C++, itssize() memberfunction calls Rust'slen().


Example

In this example we are writing a Rust application that wishes to take advantageof an existing C++ client for a large-file blobstore service. The blobstoresupports aput operation for a discontiguous buffer upload. For example wemight be uploading snapshots of a circular buffer which would tend to consist of2 chunks, or fragments of a file spread across memory for some other reason.

A runnable version of this example is provided under thedemo directory ofthis repo. To try it out, runcargo run from that directory.

#[cxx::bridge]mod ffi{// Any shared structs, whose fields will be visible to both languages.structBlobMetadata{size:usize,tags:Vec<String>,}extern"Rust"{// Zero or more opaque types which both languages can pass around but// only Rust can see the fields.typeMultiBuf;// Functions implemented in Rust.fnnext_chunk(buf:&mutMultiBuf) ->&[u8];}unsafeextern"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/include/blobstore.h");// Zero or more opaque types which both languages can pass around but// only C++ can see the fields.typeBlobstoreClient;// Functions implemented in C++.fnnew_blobstore_client() ->UniquePtr<BlobstoreClient>;fnput(&self,parts:&mutMultiBuf) ->u64;fntag(&self,blobid:u64,tag:&str);fnmetadata(&self,blobid:u64) ->BlobMetadata;}}

Now we simply provide Rust definitions of all the things in theextern "Rust"block and C++ definitions of all the things in theextern "C++" block, and getto call back and forth safely.

Here are links to the complete set of source files involved in the demo:

To look at the code generated in both languages for the example by the CXX codegenerators:

   # run Rust code generator and print to stdout   # (requires https://github.com/dtolnay/cargo-expand)$cargo expand --manifest-path demo/Cargo.toml   # run C++ code generator and print to stdout$cargo run --manifest-path gen/cmd/Cargo.toml -- demo/src/main.rs

Details

As seen in the example, the language of the FFI boundary involves 3 kinds ofitems:

  • Shared structs — their fields are made visible to both languages.The definition written within cxx::bridge is the single source of truth.

  • Opaque types — 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 RustBox, or aUniquePtr. Can be a type aliasfor an arbitrarily complicated generic language-specific type depending onyour use case.

  • Functions — implemented in either language, callable from the otherlanguage.

Within theextern "Rust" part of the CXX bridge we list the types andfunctions for which Rust is the source of truth. These all implicitly refer tothesuper module, the parent module of the CXX bridge. You can think of thetwo items listed in the example above as being likeuse super::MultiBuf anduse super::next_chunk except re-exported to C++. The parent module will eithercontain the definitions directly for simple things, or contain the relevantuse statements to bring them into scope from elsewhere.

Within theextern "C++" part, we list types and functions for which C++ is thesource of truth, as well as the header(s) that declare those APIs. In the futureit's possible that this section could be generated bindgen-style from theheaders but for now we need the signatures written out; static assertions willverify that they are accurate.

Your function implementations themselves, whether in C++ or Rust,do not needto be defined asextern "C" ABI or no_mangle. CXX will put in the right shimswhere necessary to make it all work.


Comparison vs bindgen and cbindgen

Notice that with CXX there is repetition of all the function signatures: theyare typed out once where the implementation is defined (in C++ or Rust) andagain inside the cxx::bridge module, though compile-time assertions guaranteethese are kept in sync. This is different frombindgen andcbindgen wherefunction signatures are typed by a human once and the tool consumes them in onelanguage and emits them in the other language.

This is because CXX fills a somewhat different role. It is a lower level toolthan bindgen or cbindgen in a sense; you can think of it as being a replacementfor the concept ofextern "C" signatures as we know them, rather than areplacement for a bindgen. It would be reasonable to build a higher levelbindgen-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 safetyguarantees of CXX.

But note in other ways CXX is higher level than the bindgens, with rich supportfor common standard library types. Frequently with bindgen when we are dealingwith an idiomatic C++ API we would end up manually wrapping that API in C-styleraw pointer functions, applying bindgen to get unsafe raw pointer Rustfunctions, 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, ratherthanextern "C" C-style signatures as the shared understanding, common FFI usecases become expressible using 100% safe code.

It would also be reasonable to mix and match, using CXX bridge for the 95% ofyour FFI that is straightforward and doing the remaining few oddball signaturesthe old fashioned way with bindgen and cbindgen, if for some reason CXX's staticrestrictions get in the way. Please file an issue if you end up taking thisapproach so that we know what ways it would be worthwhile to make the tool moreexpressive.


Cargo-based setup

For builds that are orchestrated by Cargo, you will use a build script that runsCXX's C++ code generator and compiles the resulting C++ code along with anyother C++ code for your crate.

The canonical build script is as follows. The indicated line returns acc::Build instance (from the usual widely usedcc crate) on which you canset up any additional source files and compiler flags as normal.

# Cargo.toml[build-dependencies]cxx-build ="1.0"
// build.rsfnmain(){    cxx_build::bridge("src/main.rs")// returns a cc::Build.file("src/demo.cc").std("c++11").compile("cxxbridge-demo");println!("cargo:rerun-if-changed=src/main.rs");println!("cargo:rerun-if-changed=src/demo.cc");println!("cargo:rerun-if-changed=include/demo.h");}

Non-Cargo setup

For use in non-Cargo builds like Bazel or Buck, CXX provides an alternate way ofinvoking the C++ code generator as a standalone command line tool. The tool ispackaged as thecxxbridge-cmd crate on crates.io or can be built from thegen/cmd directory of this repo.

$ cargo install cxxbridge-cmd$ cxxbridge src/main.rs --header> path/to/mybridge.h$ cxxbridge src/main.rs> path/to/mybridge.cc

Safety

Be aware that the design of this library is intentionally restrictive andopinionated! It isn't a goal to be powerful enough to handle arbitrarysignatures in either language. Instead this project is about carving out areasonably expressive set of functionality about which we can make useful safetyguarantees today and maybe extend over time. You may find that it takes somepractice to use CXX bridge effectively as it won't work in all the ways that youare used to.

Some of the considerations that go into ensuring safety are:

  • By design, our paired code generators work together to control both sides ofthe FFI boundary. Ordinarily in Rust writing your ownextern "C" blocks isunsafe because the Rust compiler has no way to know whether the signaturesyou've written actually match the signatures implemented in the otherlanguage. With CXX we achieve that visibility and know what's on the otherside.

  • Our static analysis detects and prevents passing types by value that shouldn'tbe passed by value from C++ to Rust, for example because they may containinternal 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 astruct in C++ with exactly the same layout / fields / alignment / everything,and still not the same ABI when passed by value. This is a longstandingbindgen bug that leads to segfaults in absolutely correct-looking code(rust-lang/rust-bindgen#778). CXX knows about this and can insert thenecessary zero-cost workaround transparently where needed, so go ahead andpass your structs by value without worries. This is made possible by owningboth sides of the boundary rather than just one.

  • Template instantiations: for example in order to expose a UniquePtr<T> typein Rust backed by a real C++ unique_ptr, we have a way of using a Rust traitto connect the behavior back to the template instantiations performed by theother language.


Builtin types

In addition to all the primitive types (i32 <=> int32_t), the followingcommon types may be used in the fields of shared structs and the arguments andreturns of functions.

name in Rustname in C++restrictions
Stringrust::String
&strrust::Str
&[T]rust::Slice<const T>cannot hold opaque C++ type
&mut [T]rust::Slice<T>cannot hold opaque C++ type
CxxStringstd::stringcannot be passed by value
Box<T>rust::Box<T>cannot hold opaque C++ type
UniquePtr<T>std::unique_ptr<T>cannot hold opaque Rust type
SharedPtr<T>std::shared_ptr<T>cannot hold opaque Rust type
[T; N]std::array<T, N>cannot hold opaque C++ type
Vec<T>rust::Vec<T>cannot hold opaque C++ type
CxxVector<T>std::vector<T>cannot be passed by value, cannot hold opaque Rust type
*mut T, *const TT*, const T*fn with a raw pointer argument must be declared unsafe to call
fn(T, U) -> Vrust::Fn<V(T, U)>only passing from Rust to C++ is implemented so far
Result<T>throw/catchallowed as return type only

The C++ API of therust namespace is defined by theinclude/cxx.h file inthis repo. You will need to include this header in your C++ code when workingwith those types.

The following types are intended to be supported "soon" but are just notimplemented yet. I don't expect any of these to be hard to make work but it's amatter of designing a nice API for each in its non-native language.

name in Rustname in C++
BTreeMap<K, V>tbd
HashMap<K, V>tbd
Arc<T>tbd
Option<T>tbd
tbdstd::map<K, V>
tbdstd::unordered_map<K, V>

Remaining work

This is still early days for CXX; I am releasing it as a minimum viable productto collect feedback on the direction and invite collaborators. Please check theopen issues.

Especially please report issues if you run into trouble building or linking anyof this stuff. I'm sure there are ways to make the build aspects friendlier ormore robust.

Finally, I know more about Rust library design than C++ library design so Iwould appreciate help making the C++ APIs in this project more idiomatic whereanyone has suggestions.


License

Licensed under either ofApache License, Version2.0 orMIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submittedfor 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.

[8]ページ先頭

©2009-2025 Movatter.jp