1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2024-12-15 14:27:50 +02:00

Chromium: expanding CXX error handling section. (#1539)

This commit is contained in:
Lukasz Anforowicz 2023-12-04 16:03:17 +00:00 committed by GitHub
parent 1fa6b7438b
commit 24b132b67a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 35 deletions

View File

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

View File

@ -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<PngReader<'a>, ()>`.
fn new_png_reader<'a>(input: &'a [u8]) -> Box<ResultOfPngReader<'a>>;
/// 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;
}
}
```
<details>
`PngReader` and `ResultOfPngReader` are Rust types --- objects of these types
cannot cross the FFI boundary without indirection of a `Box<T>`. 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<T, E>` (e.g. into `is_err`, `unwrap`, and/or `as_mut`).
</details>

View File

@ -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<u8>>,
out_qr_size: &mut usize,
) -> bool;
}
}
```
<details>
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.
</details>
[0]: https://source.chromium.org/chromium/chromium/src/+/main:components/qr_code_generator/qr_code_generator_ffi_glue.rs;l=13-18;drc=7bf1b75b910ca430501b9c6a74c1d18a0223ecca

View File

@ -1,41 +1,22 @@
# CXX error handling
CXX's support for `Result<T,E>` relies on C++ exceptions, so we can't use that
in Chromium. Alternatives:
CXX's [support for `Result<T,E>`][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<u8>>,
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<PngDecoder>;
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<T, E>` 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<T>`) that has a
suitable default value to use in a failure case (*unlike* `Box<T>`).
- 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<T>`.
The best way to learn CXX is by doing, so, another exercise!
* The `E` part of `Result<T, E>` 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