mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-04-25 16:54:32 +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:
parent
1720b80e7e
commit
ca61ca4f57
77
Cargo.lock
generated
77
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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>
|
32
third_party/cxx/blobstore/Android.bp
vendored
Normal file
32
third_party/cxx/blobstore/Android.bp
vendored
Normal file
@ -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"],
|
||||
}
|
35
third_party/cxx/blobstore/BUILD
vendored
Normal file
35
third_party/cxx/blobstore/BUILD
vendored
Normal file
@ -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"],
|
||||
)
|
15
third_party/cxx/blobstore/Cargo.toml
vendored
Normal file
15
third_party/cxx/blobstore/Cargo.toml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "demo"
|
||||
version = "0.0.0"
|
||||
authors = ["David Tolnay <dtolnay@gmail.com>"]
|
||||
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"
|
12
third_party/cxx/blobstore/build.rs
vendored
Normal file
12
third_party/cxx/blobstore/build.rs
vendored
Normal file
@ -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");
|
||||
}
|
32
third_party/cxx/blobstore/include/blobstore.h
vendored
Normal file
32
third_party/cxx/blobstore/include/blobstore.h
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "rust/cxx.h"
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
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<std::string> tags;
|
||||
};
|
||||
std::unordered_map<uint64_t, Blob> blobs;
|
||||
};
|
||||
|
||||
std::unique_ptr<BlobstoreClient> new_blobstore_client();
|
||||
|
||||
} // namespace blobstore
|
||||
} // namespace org
|
55
third_party/cxx/blobstore/src/blobstore.cc
vendored
Normal file
55
third_party/cxx/blobstore/src/blobstore.cc
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
#include "include/blobstore.h"
|
||||
#include "main.rs.h"
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
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<const char *>(chunk.data()), chunk.size());
|
||||
}
|
||||
|
||||
// Insert into map and provide caller the handle.
|
||||
auto blobid = std::hash<std::string>{}(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<BlobstoreClient> new_blobstore_client() {
|
||||
return std::make_unique<BlobstoreClient>();
|
||||
}
|
||||
|
||||
} // namespace blobstore
|
||||
} // namespace org
|
69
third_party/cxx/blobstore/src/main.rs
vendored
Normal file
69
third_party/cxx/blobstore/src/main.rs
vendored
Normal file
@ -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<String>,
|
||||
}
|
||||
|
||||
// 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<BlobstoreClient>;
|
||||
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<Vec<u8>> 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<Vec<u8>>,
|
||||
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);
|
||||
}
|
11
third_party/cxx/book/snippets.cc
vendored
Normal file
11
third_party/cxx/book/snippets.cc
vendored
Normal file
@ -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
|
121
third_party/cxx/book/snippets.rs
vendored
Normal file
121
third_party/cxx/book/snippets.rs
vendored
Normal file
@ -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<MyType>; // Free function
|
||||
}
|
||||
}
|
||||
|
||||
struct MyType(i32);
|
||||
|
||||
impl MyType {
|
||||
fn foo(&self) {
|
||||
println!("{}", self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn bar() -> Box<MyType> {
|
||||
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<BlobstoreClient>;
|
||||
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<String>;
|
||||
}
|
||||
}
|
||||
|
||||
fn fallible(depth: usize) -> anyhow::Result<String> {
|
||||
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<String>;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = ffi::fallible(99) {
|
||||
eprintln!("Error: {}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: cpp_exception
|
Loading…
x
Reference in New Issue
Block a user