From 24b132b67ae207fc25a34c1a1a8d25e08e97554a Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Mon, 4 Dec 2023 16:03:17 +0000 Subject: [PATCH] Chromium: expanding CXX error handling section. (#1539) --- src/SUMMARY.md | 2 + .../error-handling-png.md | 43 ++++++++++++++++ .../error-handling-qr.md | 39 ++++++++++++++ .../error-handling.md | 51 ++++++------------- 4 files changed, 100 insertions(+), 35 deletions(-) create mode 100644 src/chromium/interoperability-with-cpp/error-handling-png.md create mode 100644 src/chromium/interoperability-with-cpp/error-handling-qr.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 84d674ac..302a860d 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -256,6 +256,8 @@ - [Example bindings](chromium/interoperability-with-cpp/example-bindings.md) - [Limitations of CXX](chromium/interoperability-with-cpp/limitations-of-cxx.md) - [CXX error handling](chromium/interoperability-with-cpp/error-handling.md) + - [Error handling: QR example](chromium/interoperability-with-cpp/error-handling-qr.md) + - [Error handling: PNG example](chromium/interoperability-with-cpp/error-handling-png.md) - [Using CXX in Chromium](chromium/interoperability-with-cpp/using-cxx-in-chromium.md) - [Exercise](exercises/chromium/interoperability-with-cpp.md) - [Adding third party crates](chromium/adding-third-party-crates.md) diff --git a/src/chromium/interoperability-with-cpp/error-handling-png.md b/src/chromium/interoperability-with-cpp/error-handling-png.md new file mode 100644 index 00000000..16d524c8 --- /dev/null +++ b/src/chromium/interoperability-with-cpp/error-handling-png.md @@ -0,0 +1,43 @@ +# CXX error handling: PNG example + +A prototype of a PNG decoder illustrates what can be done when the successful +result cannot be passed across the FFI boundary: + +```rust,ignore +#[cxx::bridge(namespace = "gfx::rust_bindings")] +mod ffi { + extern "Rust" { + /// This returns an FFI-friendly equivalent of `Result, ()>`. + fn new_png_reader<'a>(input: &'a [u8]) -> Box>; + + /// C++ bindings for the `crate::png::ResultOfPngReader` type. + type ResultOfPngReader<'a>; + fn is_err(self: &ResultOfPngReader) -> bool; + fn unwrap_as_mut<'a, 'b>( + self: &'b mut ResultOfPngReader<'a>, + ) -> &'b mut PngReader<'a>; + + /// C++ bindings for the `crate::png::PngReader` type. + type PngReader<'a>; + fn height(self: &PngReader) -> u32; + fn width(self: &PngReader) -> u32; + fn read_rgba8(self: &mut PngReader, output: &mut [u8]) -> bool; + } +} +``` + +
+ +`PngReader` and `ResultOfPngReader` are Rust types --- objects of these types +cannot cross the FFI boundary without indirection of a `Box`. We can't have +an `out_parameter: &mut PngReader`, because CXX doesn't allow C++ to store Rust +objects by value. + +This example illustrates that even though CXX doesn't support arbitrary generics +nor templates, we can still pass them across the FFI boundary by manually +specializing / monomorphizing them into a non-generic type. In the example +`ResultOfPngReader` is a non-generic type that forwards into appropriate methods +of `Result` (e.g. into `is_err`, `unwrap`, and/or `as_mut`). + +
+ diff --git a/src/chromium/interoperability-with-cpp/error-handling-qr.md b/src/chromium/interoperability-with-cpp/error-handling-qr.md new file mode 100644 index 00000000..30505d06 --- /dev/null +++ b/src/chromium/interoperability-with-cpp/error-handling-qr.md @@ -0,0 +1,39 @@ +# CXX error handling: QR example + +The QR code generator is [an example][0] where a boolean is used to communicate +success vs failure, and where the successful result can be passed across the FFI +boundary: + +```rust,ignore +#[cxx::bridge(namespace = "qr_code_generator")] +mod ffi { + extern "Rust" { + fn generate_qr_code_using_rust( + data: &[u8], + min_version: i16, + out_pixels: Pin<&mut CxxVector>, + out_qr_size: &mut usize, + ) -> bool; + } +} +``` + +
+ +Students may be curious about the semantics of the `out_qr_size` output. This +is not the size of the vector, but the size of the QR code (and admittedly it is +a bit redundant - this is the square root of the size of the vector). + +It may be worth pointing out the importance of initializing `out_qr_size` before +calling into the Rust function. Creation of a Rust reference that points to +uninitialized memory results in Undefined Behavior (unlike in C++, when only the +act of dereferencing such memory results in UB). + +If students ask about `Pin`, then explain why CXX needs it for mutable +references to C++ data: the answer is that C++ data can’t be moved around like +Rust data, because it may contain self-referential pointers. + +
+ +[0]: https://source.chromium.org/chromium/chromium/src/+/main:components/qr_code_generator/qr_code_generator_ffi_glue.rs;l=13-18;drc=7bf1b75b910ca430501b9c6a74c1d18a0223ecca + diff --git a/src/chromium/interoperability-with-cpp/error-handling.md b/src/chromium/interoperability-with-cpp/error-handling.md index a049bbff..0db811ab 100644 --- a/src/chromium/interoperability-with-cpp/error-handling.md +++ b/src/chromium/interoperability-with-cpp/error-handling.md @@ -1,41 +1,22 @@ # CXX error handling -CXX's support for `Result` relies on C++ exceptions, so we can't use that -in Chromium. Alternatives: +CXX's [support for `Result`][0] relies on C++ exceptions, so we can't use +that in Chromium. Alternatives: -* Where success can be represented as a simple Boolean, as done in our [QR code generator][1]: - Return a Boolean representing success, and record results using out-parameters: - ```rust,ignore - #[cxx::bridge(namespace = "qr_code_generator")] - mod ffi { - extern "Rust" { - fn generate_qr_code_using_rust( - data: &[u8], - min_version: i16, - out_pixels: Pin<&mut CxxVector>, - out_qr_size: &mut usize, - ) -> bool; - } - } - ``` -* Where success is more complex, provide a Rust - object which can be queried for details of success or failure: - ```rust,ignore - #[cxx::bridge] - mod ffi { - extern "Rust" { - type PngDecoder; - fn create_png_decoder() -> Box; - fn decode(self: &PngDecoder, png: &[u8]) -> bool; // whether successful - fn get_err_code(self: &PngDecoder) -> u32; // or some more complex error type - fn get_decoded_image(self: &PngDecoder) -> &[u8]; - // or some more complex success type - } - } - ``` +* The `T` part of `Result` can be: + - Returned via out parameters (e.g. via `&mut T`). This requires that `T` + can be passed across the FFI boundary - for example `T` has to be: + - A primitive type (like `u32` or `usize`) + - A type natively supported by `cxx` (like `UniquePtr`) that has a + suitable default value to use in a failure case (*unlike* `Box`). + - Retained on the Rust side, and exposed via reference. This may be needed + when `T` is a Rust type, which cannot be passed across the FFI boundary, + and cannot be stored in `UniquePtr`. - -The best way to learn CXX is by doing, so, another exercise! +* The `E` part of `Result` can be: + - Returned as a boolean (e.g. `true` representing success, and `false` + representing failure) + - Preserving error details is in theory possible, but so far hasn't been + needed in practice. [0]: https://cxx.rs/binding/result.html -[1]: https://source.chromium.org/chromium/chromium/src/+/main:components/qr_code_generator/qr_code_generator_ffi_glue.rs;l=10 \ No newline at end of file