You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-07-03 13:29:52 +02:00
Add CXX tutorial (#1392)
Add a number of slides that cover most of CXX's functionality and demonstrate how it can be used. Fixes #823. --------- Co-authored-by: Martin Geisler <mgeisler@google.com>
This commit is contained in:
@ -7,28 +7,4 @@ The overall approach looks like this:
|
||||
|
||||
<img src="cpp/overview.svg">
|
||||
|
||||
See the [CXX tutorial][2] for an full example of using this.
|
||||
|
||||
<details>
|
||||
|
||||
- At this point, the instructor should switch to the [CXX tutorial][2].
|
||||
|
||||
- Walk the students through the tutorial step by step.
|
||||
|
||||
- Highlight how CXX presents a clean interface without unsafe code in _both languages_.
|
||||
|
||||
- Show the correspondence between [Rust and C++ types](https://cxx.rs/bindings.html):
|
||||
|
||||
- Explain how a Rust `String` cannot map to a C++ `std::string`
|
||||
(the latter does not uphold the UTF-8 invariant). Show that
|
||||
despite being different types, `rust::String` in C++ can be
|
||||
easily constructed from a C++ `std::string`, making it very
|
||||
ergonomic to use.
|
||||
|
||||
- Explain that a Rust function returning `Result<T, E>` becomes a
|
||||
function which throws a `E` exception in C++ (and vice versa).
|
||||
|
||||
</details>
|
||||
|
||||
[1]: https://cxx.rs/
|
||||
[2]: https://cxx.rs/tutorial.html
|
||||
|
31
src/android/interoperability/cpp/android-build-cpp.md
Normal file
31
src/android/interoperability/cpp/android-build-cpp.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Building in Android
|
||||
|
||||
Create a `cc_library_static` to build the C++ library, including the CXX
|
||||
generated header and source file.
|
||||
|
||||
```javascript
|
||||
cc_library_static {
|
||||
name: "libcxx_test_cpp",
|
||||
srcs: ["cxx_test.cpp"],
|
||||
generated_headers: [
|
||||
"cxx-bridge-header",
|
||||
"libcxx_test_bridge_header"
|
||||
],
|
||||
generated_sources: ["libcxx_test_bridge_code"],
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Point out that `libcxx_test_bridge_header` and `libcxx_test_bridge_code` are
|
||||
the dependencies for the CXX-generated C++ bindings. We'll show how these are
|
||||
setup on the next slide.
|
||||
* Note that you also need to depend on the `cxx-bridge-header` library in order
|
||||
to pull in common CXX definitions.
|
||||
* Full docs for using CXX in Android can be found in [the Android docs]. You may
|
||||
want to share that link with the class so that students know where they can
|
||||
find these instructions again in the future.
|
||||
|
||||
[the Android docs]: https://source.android.com/docs/setup/build/rust/building-rust-modules/android-rust-patterns#rust-cpp-interop-using-cxx
|
||||
|
||||
</details>
|
12
src/android/interoperability/cpp/android-build-rust.md
Normal file
12
src/android/interoperability/cpp/android-build-rust.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Building in Android
|
||||
|
||||
Create a `rust_binary` that depends on `libcxx` and your `cc_library_static`.
|
||||
|
||||
```javascript
|
||||
rust_binary {
|
||||
name: "cxx_test",
|
||||
srcs: ["lib.rs"],
|
||||
rustlibs: ["libcxx"],
|
||||
static_libs: ["libcxx_test_cpp"],
|
||||
}
|
||||
```
|
35
src/android/interoperability/cpp/android-cpp-genrules.md
Normal file
35
src/android/interoperability/cpp/android-cpp-genrules.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Building in Android
|
||||
|
||||
Create two genrules: One to generate the CXX header, and one to generate the CXX
|
||||
source file. These are then used as inputs to the `cc_library_static`.
|
||||
|
||||
```javascript
|
||||
// Generate a C++ header containing the C++ bindings
|
||||
// to the Rust exported functions in lib.rs.
|
||||
genrule {
|
||||
name: "libcxx_test_bridge_header",
|
||||
tools: ["cxxbridge"],
|
||||
cmd: "$(location cxxbridge) $(in) --header > $(out)",
|
||||
srcs: ["lib.rs"],
|
||||
out: ["lib.rs.h"],
|
||||
}
|
||||
|
||||
// Generate the C++ code that Rust calls into.
|
||||
genrule {
|
||||
name: "libcxx_test_bridge_code",
|
||||
tools: ["cxxbridge"],
|
||||
cmd: "$(location cxxbridge) $(in) > $(out)",
|
||||
srcs: ["lib.rs"],
|
||||
out: ["lib.rs.cc"],
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* The `cxxbridge` tool is a standalone tool that generates the C++ side of the
|
||||
bridge module. It is included in Android and available as a Soong tool.
|
||||
* By convention, if your Rust source file is `lib.rs` your header file will be
|
||||
named `lib.rs.h` and your source file will be named `lib.rs.cc`. This naming
|
||||
convention isn't enforced, though.
|
||||
|
||||
</details>
|
24
src/android/interoperability/cpp/bridge.md
Normal file
24
src/android/interoperability/cpp/bridge.md
Normal file
@ -0,0 +1,24 @@
|
||||
# The Bridge Module
|
||||
|
||||
CXX relies on a description of the function signatures that will be exposed from
|
||||
each language to the other. You provide this description using extern blocks in
|
||||
a Rust module annotated with the `#[cxx::bridge]` attribute macro.
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../../third_party/cxx/blobstore/src/main.rs:bridge}}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* The bridge is generally declared in an `ffi` module within your crate.
|
||||
* From the declarations made in the bridge module, CXX will generate matching
|
||||
Rust and C++ type/function definitions in order to expose those items to both
|
||||
languages.
|
||||
* To view the generated Rust code, use [cargo-expand] to view the expanded proc
|
||||
macro. For most of the examples you would use `cargo expand ::ffi` to expand
|
||||
just the `ffi` module (though this doesn't apply for Android projects).
|
||||
* To view the generated C++ code, look in `target/cxxbridge`.
|
||||
|
||||
[cargo-expand]: https://github.com/dtolnay/cargo-expand
|
||||
|
||||
</details>
|
52
src/android/interoperability/cpp/cpp-bridge.md
Normal file
52
src/android/interoperability/cpp/cpp-bridge.md
Normal file
@ -0,0 +1,52 @@
|
||||
# C++ Bridge Declarations
|
||||
|
||||
```rust,ignore
|
||||
#[cxx::bridge]
|
||||
mod ffi {
|
||||
{{#include ../../../../third_party/cxx/blobstore/src/main.rs:cpp_bridge}}
|
||||
}
|
||||
```
|
||||
|
||||
Results in (roughly) the following Rust:
|
||||
|
||||
```rust,ignore
|
||||
#[repr(C)]
|
||||
pub struct BlobstoreClient {
|
||||
_private: ::cxx::private::Opaque,
|
||||
}
|
||||
|
||||
pub fn new_blobstore_client() -> ::cxx::UniquePtr<BlobstoreClient> {
|
||||
extern "C" {
|
||||
#[link_name = "org$blobstore$cxxbridge1$new_blobstore_client"]
|
||||
fn __new_blobstore_client() -> *mut BlobstoreClient;
|
||||
}
|
||||
unsafe { ::cxx::UniquePtr::from_raw(__new_blobstore_client()) }
|
||||
}
|
||||
|
||||
impl BlobstoreClient {
|
||||
pub fn put(&self, parts: &mut MultiBuf) -> u64 {
|
||||
extern "C" {
|
||||
#[link_name = "org$blobstore$cxxbridge1$BlobstoreClient$put"]
|
||||
fn __put(
|
||||
_: &BlobstoreClient,
|
||||
parts: *mut ::cxx::core::ffi::c_void,
|
||||
) -> u64;
|
||||
}
|
||||
unsafe {
|
||||
__put(self, parts as *mut MultiBuf as *mut ::cxx::core::ffi::c_void)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* The programmer does not need to promise that the signatures they have typed in
|
||||
are accurate. CXX performs static assertions that the signatures exactly
|
||||
correspond with what is declared in C++.
|
||||
* `unsafe extern` blocks allow you to declare C++ functions that are safe to
|
||||
call from Rust.
|
||||
|
||||
</details>
|
16
src/android/interoperability/cpp/cpp-exception.md
Normal file
16
src/android/interoperability/cpp/cpp-exception.md
Normal file
@ -0,0 +1,16 @@
|
||||
# C++ Error Handling
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../../third_party/cxx/book/snippets.rs:cpp_exception}}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* C++ functions declared to return a `Result` will catch any thrown exception on
|
||||
the C++ side and return it as an `Err` value to the calling Rust function.
|
||||
* If an exception is thrown from an extern "C++" function that is not declared
|
||||
by the CXX bridge to return `Result`, the program calls C++'s
|
||||
`std::terminate`. The behavior is equivalent to the same exception being
|
||||
thrown through a `noexcept` C++ function.
|
||||
|
||||
</details>
|
25
src/android/interoperability/cpp/generated-cpp.md
Normal file
25
src/android/interoperability/cpp/generated-cpp.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Generated C++
|
||||
|
||||
```rust,ignore
|
||||
#[cxx::bridge]
|
||||
mod ffi {
|
||||
{{#include ../../../../third_party/cxx/blobstore/src/main.rs:rust_bridge}}
|
||||
}
|
||||
```
|
||||
|
||||
Results in (roughly) the following C++:
|
||||
|
||||
```cpp
|
||||
struct MultiBuf final : public ::rust::Opaque {
|
||||
~MultiBuf() = delete;
|
||||
|
||||
private:
|
||||
friend ::rust::layout;
|
||||
struct layout {
|
||||
static ::std::size_t size() noexcept;
|
||||
static ::std::size_t align() noexcept;
|
||||
};
|
||||
};
|
||||
|
||||
::rust::Slice<::std::uint8_t const> next_chunk(::org::blobstore::MultiBuf &buf) noexcept;
|
||||
```
|
16
src/android/interoperability/cpp/rust-bridge.md
Normal file
16
src/android/interoperability/cpp/rust-bridge.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Rust Bridge Declarations
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../../third_party/cxx/book/snippets.rs:rust_bridge}}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Items declared in the `extern "Rust"` reference items that are in scope in the
|
||||
parent module.
|
||||
* The CXX code generator uses your `extern "Rust"` section(s) to produce a C++
|
||||
header file containing the corresponding C++ declarations. The generated
|
||||
header has the same path as the Rust source file containing the bridge, except
|
||||
with a .rs.h file extension.
|
||||
|
||||
</details>
|
17
src/android/interoperability/cpp/rust-result.md
Normal file
17
src/android/interoperability/cpp/rust-result.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Rust Error Handling
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../../third_party/cxx/book/snippets.rs:rust_result}}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Rust functions that return `Result` are translated to exceptions on the C++
|
||||
side.
|
||||
* The exception thrown will always be of type `rust::Error`, which primarily
|
||||
exposes a way to get the error message string. The error message will come
|
||||
from the error type's `Display` impl.
|
||||
* A panic unwinding from Rust to C++ will always cause the process to
|
||||
immediately terminate.
|
||||
|
||||
</details>
|
26
src/android/interoperability/cpp/shared-enums.md
Normal file
26
src/android/interoperability/cpp/shared-enums.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Shared Enums
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../../third_party/cxx/book/snippets.rs:shared_enums_bridge}}
|
||||
```
|
||||
|
||||
Generated Rust:
|
||||
|
||||
```rust
|
||||
{{#include ../../../../third_party/cxx/book/snippets.rs:shared_enums_rust}}
|
||||
```
|
||||
|
||||
Generated C++:
|
||||
|
||||
```c++
|
||||
{{#include ../../../../third_party/cxx/book/snippets.cc:shared_enums_cpp}}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* On the Rust side, the code generated for shared enums is actually a struct
|
||||
wrapping a numeric value. This is because it is not UB in C++ for an enum
|
||||
class to hold a value different from all of the listed variants, and our Rust
|
||||
representation needs to have the same behavior.
|
||||
|
||||
</details>
|
15
src/android/interoperability/cpp/shared-types.md
Normal file
15
src/android/interoperability/cpp/shared-types.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Shared Types
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../../../../third_party/cxx/book/snippets.rs:shared_types}}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Only C-like (unit) enums are supported.
|
||||
* A limited number of traits are supported for `#[derive()]` on shared types.
|
||||
Corresponding functionality is also generated for the C++ code, e.g. if you
|
||||
derive `Hash` also generates an implementation of `std::hash` for the
|
||||
corresponding C++ type.
|
||||
|
||||
</details>
|
26
src/android/interoperability/cpp/type-mapping.md
Normal file
26
src/android/interoperability/cpp/type-mapping.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Additional Types
|
||||
|
||||
| Rust Type | C++ Type |
|
||||
|-------------------|----------------------|
|
||||
| `String` | `rust::String` |
|
||||
| `&str` | `rust::Str` |
|
||||
| `CxxString` | `std::string` |
|
||||
| `&[T]`/`&mut [T]` | `rust::Slice` |
|
||||
| `Box<T>` | `rust::Box<T>` |
|
||||
| `UniquePtr<T>` | `std::unique_ptr<T>` |
|
||||
| `Vec<T>` | `rust::Vec<T>` |
|
||||
| `CxxVector<T>` | `std::vector<T>` |
|
||||
|
||||
<details>
|
||||
|
||||
* These types can be used in the fields of shared structs and the arguments and
|
||||
returns of extern functions.
|
||||
* Note that Rust's `String` does not map directly to `std::string`. There are a
|
||||
few reasons for this:
|
||||
* `std::string` does not uphold the UTF-8 invariant that `String` requires.
|
||||
* The two types have different layouts in memory and so can't be passed
|
||||
directly between languages.
|
||||
* `std::string` requires move constructors that don't match Rust's move
|
||||
semantics, so a `std::string` can't be passed by value to Rust.
|
||||
|
||||
</details>
|
Reference in New Issue
Block a user