1
0
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:
Nicole L
2023-11-06 16:34:29 -08:00
committed by GitHub
parent 1720b80e7e
commit ca61ca4f57
25 changed files with 768 additions and 25 deletions

View File

@ -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

View 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>

View 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"],
}
```

View 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>

View 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>

View 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>

View 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>

View 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;
```

View 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>

View 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>

View 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>

View 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>

View 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>