diff --git a/Cargo.lock b/Cargo.lock index ccfabdf6..da32fbb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,6 +286,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -389,12 +399,64 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "cxx" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c390c123d671cc547244943ecad81bdaab756c6ea332d9ca9c1f48d952a24895" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d3d3ac9ffb900304edf51ca719187c779f4001bb544f26c4511d621de905cf" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.37", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94415827ecfea0f0c74c8cad7d1a86ddb3f05354d6a6ddeda0adee5e875d2939" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33dbbe9f5621c9247f97ec14213b04f350bff4b6cebefe834c60055db266ecf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "data-encoding" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +[[package]] +name = "demo" +version = "0.0.0" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -1027,6 +1089,15 @@ version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.4.8" @@ -1862,6 +1933,12 @@ dependencies = [ "tendril", ] +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + [[package]] name = "security-framework" version = "2.9.2" diff --git a/Cargo.toml b/Cargo.toml index 4869ef94..00ef3fb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,6 @@ members = [ "src/bare-metal/useful-crates/allocator-example", "src/bare-metal/useful-crates/zerocopy-example", "src/exercises/concurrency/chat-async", + "third_party/cxx/blobstore", ] resolver = "2" diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 522f3e8c..c06ce67d 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -207,7 +207,19 @@ - [With C](android/interoperability/with-c.md) - [Calling C with Bindgen](android/interoperability/with-c/bindgen.md) - [Calling Rust from C](android/interoperability/with-c/rust.md) - - [With C++](android/interoperability/cpp.md) + - [With C++](android/interoperability/cpp.md)) + - [The Bridge Module](android/interoperability/cpp/bridge.md) + - [Rust Bridge](android/interoperability/cpp/rust-bridge.md) + - [Generated C++](android/interoperability/cpp/generated-cpp.md) + - [C++ Bridge](android/interoperability/cpp/cpp-bridge.md) + - [Shared Types](android/interoperability/cpp/shared-types.md) + - [Shared Enums](android/interoperability/cpp/shared-enums.md) + - [Rust Error Handling](android/interoperability/cpp/rust-result.md) + - [C++ Error Handling](android/interoperability/cpp/cpp-exception.md) + - [Additional Types](android/interoperability/cpp/type-mapping.md) + - [Building for Android: C++](android/interoperability/cpp/android-build-cpp.md) + - [Building for Android: Genrules](android/interoperability/cpp/android-cpp-genrules.md) + - [Building for Android: Rust](android/interoperability/cpp/android-build-rust.md) - [With Java](android/interoperability/java.md) - [Exercises](exercises/android/morning.md) diff --git a/src/android/interoperability/cpp.md b/src/android/interoperability/cpp.md index 3a523db1..62a177c3 100644 --- a/src/android/interoperability/cpp.md +++ b/src/android/interoperability/cpp.md @@ -7,28 +7,4 @@ The overall approach looks like this: -See the [CXX tutorial][2] for an full example of using this. - -
- -- 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` becomes a - function which throws a `E` exception in C++ (and vice versa). - -
- [1]: https://cxx.rs/ -[2]: https://cxx.rs/tutorial.html diff --git a/src/android/interoperability/cpp/android-build-cpp.md b/src/android/interoperability/cpp/android-build-cpp.md new file mode 100644 index 00000000..e034bdf3 --- /dev/null +++ b/src/android/interoperability/cpp/android-build-cpp.md @@ -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"], +} +``` + +
+ +* 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 + +
diff --git a/src/android/interoperability/cpp/android-build-rust.md b/src/android/interoperability/cpp/android-build-rust.md new file mode 100644 index 00000000..22886982 --- /dev/null +++ b/src/android/interoperability/cpp/android-build-rust.md @@ -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"], +} +``` diff --git a/src/android/interoperability/cpp/android-cpp-genrules.md b/src/android/interoperability/cpp/android-cpp-genrules.md new file mode 100644 index 00000000..79f66939 --- /dev/null +++ b/src/android/interoperability/cpp/android-cpp-genrules.md @@ -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"], +} +``` + +
+ +* 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. + +
diff --git a/src/android/interoperability/cpp/bridge.md b/src/android/interoperability/cpp/bridge.md new file mode 100644 index 00000000..6cb2958e --- /dev/null +++ b/src/android/interoperability/cpp/bridge.md @@ -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}} +``` + +
+ +* 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 + +
diff --git a/src/android/interoperability/cpp/cpp-bridge.md b/src/android/interoperability/cpp/cpp-bridge.md new file mode 100644 index 00000000..50e902d0 --- /dev/null +++ b/src/android/interoperability/cpp/cpp-bridge.md @@ -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 { + 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) + } + } +} + +// ... +``` + +
+ +* 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. + +
diff --git a/src/android/interoperability/cpp/cpp-exception.md b/src/android/interoperability/cpp/cpp-exception.md new file mode 100644 index 00000000..12c2dc64 --- /dev/null +++ b/src/android/interoperability/cpp/cpp-exception.md @@ -0,0 +1,16 @@ +# C++ Error Handling + +```rust,ignore +{{#include ../../../../third_party/cxx/book/snippets.rs:cpp_exception}} +``` + +
+ +* 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. + +
diff --git a/src/android/interoperability/cpp/generated-cpp.md b/src/android/interoperability/cpp/generated-cpp.md new file mode 100644 index 00000000..0eee5ce9 --- /dev/null +++ b/src/android/interoperability/cpp/generated-cpp.md @@ -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; +``` diff --git a/src/android/interoperability/cpp/rust-bridge.md b/src/android/interoperability/cpp/rust-bridge.md new file mode 100644 index 00000000..7a55371d --- /dev/null +++ b/src/android/interoperability/cpp/rust-bridge.md @@ -0,0 +1,16 @@ +# Rust Bridge Declarations + +```rust,ignore +{{#include ../../../../third_party/cxx/book/snippets.rs:rust_bridge}} +``` + +
+ +* 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. + +
diff --git a/src/android/interoperability/cpp/rust-result.md b/src/android/interoperability/cpp/rust-result.md new file mode 100644 index 00000000..0631b955 --- /dev/null +++ b/src/android/interoperability/cpp/rust-result.md @@ -0,0 +1,17 @@ +# Rust Error Handling + +```rust,ignore +{{#include ../../../../third_party/cxx/book/snippets.rs:rust_result}} +``` + +
+ +* 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. + +
diff --git a/src/android/interoperability/cpp/shared-enums.md b/src/android/interoperability/cpp/shared-enums.md new file mode 100644 index 00000000..dbf17d6e --- /dev/null +++ b/src/android/interoperability/cpp/shared-enums.md @@ -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}} +``` + +
+ +* 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. + +
diff --git a/src/android/interoperability/cpp/shared-types.md b/src/android/interoperability/cpp/shared-types.md new file mode 100644 index 00000000..3ef7dfe4 --- /dev/null +++ b/src/android/interoperability/cpp/shared-types.md @@ -0,0 +1,15 @@ +# Shared Types + +```rust,ignore +{{#include ../../../../third_party/cxx/book/snippets.rs:shared_types}} +``` + +
+ +* 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. + +
diff --git a/src/android/interoperability/cpp/type-mapping.md b/src/android/interoperability/cpp/type-mapping.md new file mode 100644 index 00000000..22867c83 --- /dev/null +++ b/src/android/interoperability/cpp/type-mapping.md @@ -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` | `rust::Box` | +| `UniquePtr` | `std::unique_ptr` | +| `Vec` | `rust::Vec` | +| `CxxVector` | `std::vector` | + +
+ +* 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. + +
diff --git a/third_party/cxx/blobstore/Android.bp b/third_party/cxx/blobstore/Android.bp new file mode 100644 index 00000000..1e9809bf --- /dev/null +++ b/third_party/cxx/blobstore/Android.bp @@ -0,0 +1,32 @@ +cc_library_static { + name: "blobstore_cpp", + srcs: ["src/blobstore.cc"], + generated_headers: [ + "cxx-bridge-header", + "blobstore_bridge_header" + ], + generated_sources: ["blobstore_bridge_code"], +} + +genrule { + name: "blobstore_bridge_header", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) --header > $(out)", + srcs: ["src/main.rs"], + out: ["main.rs.h"], +} + +genrule { + name: "blobstore_bridge_code", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) > $(out)", + srcs: ["src/main.rs"], + out: ["main.rs.cc"], +} + +rust_binary { + name: "blobstore", + srcs: ["src/main.rs"], + rustlibs: ["libcxx"], + static_libs: ["blobstore_cpp"], +} diff --git a/third_party/cxx/blobstore/BUILD b/third_party/cxx/blobstore/BUILD new file mode 100644 index 00000000..3de1cce8 --- /dev/null +++ b/third_party/cxx/blobstore/BUILD @@ -0,0 +1,35 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//tools/bazel:rust_cxx_bridge.bzl", "rust_cxx_bridge") + +rust_binary( + name = "demo", + srcs = glob(["src/**/*.rs"]), + edition = "2021", + deps = [ + ":blobstore-sys", + ":bridge", + "//:cxx", + ], +) + +rust_cxx_bridge( + name = "bridge", + src = "src/main.rs", + deps = [":blobstore-include"], +) + +cc_library( + name = "blobstore-sys", + srcs = ["src/blobstore.cc"], + deps = [ + ":blobstore-include", + ":bridge/include", + ], +) + +cc_library( + name = "blobstore-include", + hdrs = ["include/blobstore.h"], + deps = ["//:core"], +) diff --git a/third_party/cxx/blobstore/Cargo.toml b/third_party/cxx/blobstore/Cargo.toml new file mode 100644 index 00000000..f125cf27 --- /dev/null +++ b/third_party/cxx/blobstore/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "demo" +version = "0.0.0" +authors = ["David Tolnay "] +description = "Toy project from https://github.com/dtolnay/cxx" +edition = "2021" +license = "MIT OR Apache-2.0" +publish = false +repository = "https://github.com/dtolnay/cxx" + +[dependencies] +cxx = "1.0" + +[build-dependencies] +cxx-build = "1.0" diff --git a/third_party/cxx/blobstore/build.rs b/third_party/cxx/blobstore/build.rs new file mode 100644 index 00000000..3761826f --- /dev/null +++ b/third_party/cxx/blobstore/build.rs @@ -0,0 +1,12 @@ +fn main() { + cxx_build::bridge("src/main.rs") + .file("src/blobstore.cc") + .flag_if_supported("-std=c++14") + .include(".") + .include("../../../target/cxxbridge/demo/src") + .compile("cxxbridge-demo"); + + println!("cargo:rerun-if-changed=src/main.rs"); + println!("cargo:rerun-if-changed=src/blobstore.cc"); + println!("cargo:rerun-if-changed=include/blobstore.h"); +} diff --git a/third_party/cxx/blobstore/include/blobstore.h b/third_party/cxx/blobstore/include/blobstore.h new file mode 100644 index 00000000..cd4215ae --- /dev/null +++ b/third_party/cxx/blobstore/include/blobstore.h @@ -0,0 +1,32 @@ +#pragma once +#include "rust/cxx.h" +#include +#include +#include +#include + +namespace org { +namespace blobstore { + +struct MultiBuf; +struct BlobMetadata; + +class BlobstoreClient { +public: + BlobstoreClient(); + uint64_t put(MultiBuf &buf); + void tag(uint64_t blobid, rust::Str tag); + BlobMetadata metadata(uint64_t blobid) const; + +private: + using Blob = struct { + std::string data; + std::set tags; + }; + std::unordered_map blobs; +}; + +std::unique_ptr new_blobstore_client(); + +} // namespace blobstore +} // namespace org diff --git a/third_party/cxx/blobstore/src/blobstore.cc b/third_party/cxx/blobstore/src/blobstore.cc new file mode 100644 index 00000000..d27f5aa5 --- /dev/null +++ b/third_party/cxx/blobstore/src/blobstore.cc @@ -0,0 +1,55 @@ +#include "include/blobstore.h" +#include "main.rs.h" +#include +#include + +namespace org { +namespace blobstore { + +BlobstoreClient::BlobstoreClient() {} + +// Upload a new blob and return a blobid that serves as a handle to the blob. +uint64_t BlobstoreClient::put(MultiBuf &buf) { + std::string contents; + + // Traverse the caller's chunk iterator. + // + // In reality there might be sophisticated batching of chunks and/or parallel + // upload implemented by the blobstore's C++ client. + while (true) { + auto chunk = next_chunk(buf); + if (chunk.size() == 0) { + break; + } + contents.append(reinterpret_cast(chunk.data()), chunk.size()); + } + + // Insert into map and provide caller the handle. + auto blobid = std::hash{}(contents); + blobs[blobid] = {std::move(contents), {}}; + return blobid; +} + +// Add tag to an existing blob. +void BlobstoreClient::tag(uint64_t blobid, rust::Str tag) { + blobs[blobid].tags.emplace(tag); +} + +// Retrieve metadata about a blob. +BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const { + BlobMetadata metadata{}; + auto blob = blobs.find(blobid); + if (blob != blobs.end()) { + metadata.size = blob->second.data.size(); + std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(), + [&](auto &t) { metadata.tags.emplace_back(t); }); + } + return metadata; +} + +std::unique_ptr new_blobstore_client() { + return std::make_unique(); +} + +} // namespace blobstore +} // namespace org diff --git a/third_party/cxx/blobstore/src/main.rs b/third_party/cxx/blobstore/src/main.rs new file mode 100644 index 00000000..6ccd0068 --- /dev/null +++ b/third_party/cxx/blobstore/src/main.rs @@ -0,0 +1,69 @@ +//! Example project demonstrating usage of CXX. +// ANCHOR: bridge +#[allow(unsafe_op_in_unsafe_fn)] +#[cxx::bridge(namespace = "org::blobstore")] +mod ffi { + // Shared structs with fields visible to both languages. + struct BlobMetadata { + size: usize, + tags: Vec, + } + + // ANCHOR: rust_bridge + // Rust types and signatures exposed to C++. + extern "Rust" { + type MultiBuf; + + fn next_chunk(buf: &mut MultiBuf) -> &[u8]; + } + // ANCHOR_END: rust_bridge + + // ANCHOR: cpp_bridge + // C++ types and signatures exposed to Rust. + unsafe extern "C++" { + include!("include/blobstore.h"); + + type BlobstoreClient; + + fn new_blobstore_client() -> UniquePtr; + fn put(self: Pin<&mut BlobstoreClient>, parts: &mut MultiBuf) -> u64; + fn tag(self: Pin<&mut BlobstoreClient>, blobid: u64, tag: &str); + fn metadata(&self, blobid: u64) -> BlobMetadata; + } + // ANCHOR_END: cpp_bridge +} +// ANCHOR_END: bridge + +/// An iterator over contiguous chunks of a discontiguous file object. +/// +/// Toy implementation uses a Vec> but in reality this might be iterating +/// over some more complex Rust data structure like a rope, or maybe loading +/// chunks lazily from somewhere. +pub struct MultiBuf { + chunks: Vec>, + pos: usize, +} + +/// Pulls the next chunk from the buffer. +pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] { + let next = buf.chunks.get(buf.pos); + buf.pos += 1; + next.map_or(&[], Vec::as_slice) +} + +fn main() { + let mut client = ffi::new_blobstore_client(); + + // Upload a blob. + let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; + let mut buf = MultiBuf { chunks, pos: 0 }; + let blobid = client.pin_mut().put(&mut buf); + println!("blobid = {}", blobid); + + // Add a tag. + client.pin_mut().tag(blobid, "rust"); + + // Read back the tags. + let metadata = client.metadata(blobid); + println!("tags = {:?}", metadata.tags); +} diff --git a/third_party/cxx/book/snippets.cc b/third_party/cxx/book/snippets.cc new file mode 100644 index 00000000..303713de --- /dev/null +++ b/third_party/cxx/book/snippets.cc @@ -0,0 +1,11 @@ +// This file contains various code snippets taken from the CXX book and +// tutorial. Some have been modified to fit the course better. + +// ANCHOR: shared_enums_cpp +enum class Suit : uint8_t { + Clubs = 0, + Diamonds = 1, + Hearts = 2, + Spades = 3, +}; +// ANCHOR_END: shared_enums_cpp diff --git a/third_party/cxx/book/snippets.rs b/third_party/cxx/book/snippets.rs new file mode 100644 index 00000000..8b7fd0a3 --- /dev/null +++ b/third_party/cxx/book/snippets.rs @@ -0,0 +1,121 @@ +//! This file contains various code snippets taken from the CXX book and +//! tutorial. Some have been modified to fit the course better. + +// ANCHOR: rust_bridge +#[cxx::bridge] +mod ffi { + extern "Rust" { + type MyType; // Opaque type + fn foo(&self); // Method on `MyType` + fn bar() -> Box; // Free function + } +} + +struct MyType(i32); + +impl MyType { + fn foo(&self) { + println!("{}", self.0); + } +} + +fn bar() -> Box { + Box::new(MyType(123)) +} +// ANCHOR_END: rust_bridge + +// ANCHOR: cpp_bridge +#[cxx::bridge] +mod ffi { + extern "C++" { + include!("demo/include/blobstore.h"); + type BlobstoreClient; + fn new_blobstore_client() -> UniquePtr; + fn put(&self, parts: &mut MultiBuf) -> u64; + } + + unsafe extern "C++" { + fn f(); // safe to call + } +} +// ANCHOR_END: cpp_bridge + +// ANCHOR: shared_types +#[cxx::bridge] +mod ffi { + #[derive(Clone, Debug, Hash)] + struct PlayingCard { + suit: Suit, + value: u8, // A=1, J=11, Q=12, K=13 + } + + enum Suit { + Clubs, + Diamonds, + Hearts, + Spades, + } +} +// ANCHOR_END: shared_types + +// ANCHOR: shared_enums_bridge +#[cxx::bridge] +mod ffi { + enum Suit { + Clubs, + Diamonds, + Hearts, + Spades, + } +} +// ANCHOR_END: shared_enums_bridge + +// ANCHOR: shared_enums_rust +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct Suit { + pub repr: u8, +} + +#[allow(non_upper_case_globals)] +impl Suit { + pub const Clubs: Self = Suit { repr: 0 }; + pub const Diamonds: Self = Suit { repr: 1 }; + pub const Hearts: Self = Suit { repr: 2 }; + pub const Spades: Self = Suit { repr: 3 }; +} +// ANCHOR_END: shared_enums_rust + +// ANCHOR: rust_result +#[cxx::bridge] +mod ffi { + extern "Rust" { + fn fallible(depth: usize) -> Result; + } +} + +fn fallible(depth: usize) -> anyhow::Result { + if depth == 0 { + return Err(anyhow::Error::msg("fallible1 requires depth > 0")); + } + + Ok("Success!".into()) +} +// ANCHOR_END: rust_result + +// ANCHOR: cpp_exception +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("example/include/example.h"); + fn fallible(depth: usize) -> Result; + } +} + +fn main() { + if let Err(err) = ffi::fallible(99) { + eprintln!("Error: {}", err); + process::exit(1); + } +} +// ANCHOR_END: cpp_exception