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