Provide Rust interface for libResult - #403
Conversation
|
The created documentation from the pull request is available at: docu-html |
0851ae1 to
d76d70c
Compare
d76d70c to
c7ddd74
Compare
c7ddd74 to
52318ae
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces Rust-side bindings to interoperate with the C++ score::Result<T> / score::result::Error API across the FFI boundary, including cxx integration helpers and supporting “ABI-aware” Rust representations of selected C++ standard library types.
Changes:
- Add a new
result_rsRust crate implementing an FFI-compatibleExpected<T, Error>/Result<T>wrapper plus cxx import macros. - Add a new
libcpp+libcpp_deriveRust crates to model ABI-dependent C++ stdlib types (currentlystd::string_viewandstd::variant) and provide layout tests. - Wire everything into Bazel and the Cargo workspace, including C++ test helpers and a cxx bridge example.
Reviewed changes
Copilot reviewed 25 out of 27 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| score/result/rust/result.rs | Core Rust FFI types for score::Result + cxx integration macros + tests. |
| score/result/rust/result_example.rs | cxx-bridge example demonstrating passing score::Result<T>-like values into Rust. |
| score/result/rust/result_example_cpp.h | C++ declarations for the cxx example. |
| score/result/rust/result_example_cpp.cpp | C++ implementation for the cxx example. |
| score/result/rust/result_cxx.h | cxx relocatability traits for score::Result with smart pointers. |
| score/result/rust/README.md | Documentation for the Rust result bindings and how to build/test them. |
| score/result/rust/plantuml/sequence.puml | Sequence diagram describing the ABI flow for returning Result across FFI. |
| score/result/rust/ffi_test_helpers.cpp | C++ helpers used by Rust tests to validate layout/behavior across FFI. |
| score/result/rust/Cargo.toml | Cargo crate definition for result_rs with ABI-layout feature flags. |
| score/result/rust/BUILD | Bazel targets for result_rs, docs, unit tests, and examples. |
| score/language/rust/libcpp/string_view.rs | ABI-dependent std::string_view representation + layout tests. |
| score/language/rust/libcpp/README.md | Rationale and strategy notes for ABI-dependent bindings. |
| score/language/rust/libcpp/libcpp_test.rs | Simple Rust-side usage tests for imported std::variant. |
| score/language/rust/libcpp/lib.rs | libcpp crate root exporting CStringView and the import macro. |
| score/language/rust/libcpp/ffi_test_helpers.cpp | C++ helpers for std::string_view layout validation tests. |
| score/language/rust/libcpp/Cargo.toml | Cargo crate definition for libcpp. |
| score/language/rust/libcpp/BUILD | Bazel targets for libcpp + C++ helpers + tests/docs. |
| score/language/rust/libcpp_derive/unit_test.rs | Rust tests validating libcpp_derive::import against C++ variants. |
| score/language/rust/libcpp_derive/unit_test_helpers.cpp | C++ helpers for libcpp_derive variant tests. |
| score/language/rust/libcpp_derive/lib.rs | Procedural macro generating Rust layouts/APIs for imported std::variant. |
| score/language/rust/libcpp_derive/Cargo.toml | Cargo crate definition for libcpp_derive. |
| score/language/rust/libcpp_derive/BUILD | Bazel build rules for proc-macro crate and tests. |
| MODULE.bazel | Adds/updates Bazel module deps needed for the Rust/cxx integration. |
| Cargo.toml | Adds new crates to the workspace members/default-members and deps. |
| Cargo.lock | Locks new Rust dependencies introduced by the new crates. |
| .bazelrc | Adjusts test config filters (notably around Rust doc tests). |
Suppressed comments (2)
score/result/rust/result.rs:482
- Same issue as above: fixed-size array +
transmutefor the error case is size/layout fragile. Copy bytes intoMaybeUninit<Expected<...>>instead.
let mut error_result_array = [0u8; MAX_SIZE];
error_result_array
.copy_from_slice(&cpp_error_result_slice[..std::mem::size_of::<Expected<i32, Error>>()]);
let rust_error_result: Expected<i32, Error> = std::mem::transmute(error_result_array);
score/result/rust/result.rs:759
- In
main_module_tests, the helper functions are also missing#[test], so none of these conversions/round-trip checks are executed in CI.
#[cfg(all(test, not(miri)))]
fn test_cpp_result_to_std_result_success() {
unsafe {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| std::size_t create_result_int32_error_for_layout_test(int32_t error_code, | ||
| score::Result<std::int32_t>* out_result, | ||
| std::size_t bufsize) | ||
| { |
| try | ||
| { | ||
| if (should_have_value) | ||
| { | ||
| if (!cpp_result->has_value()) | ||
| { | ||
| return false; | ||
| } | ||
| return cpp_result->value() == expected_value_or_error_code; | ||
| } | ||
| else | ||
| { | ||
| if (cpp_result->has_value()) | ||
| { | ||
| return false; | ||
| } | ||
| // For error case, just check if we can access the error and it matches | ||
| return *cpp_result->error() == expected_value_or_error_code; | ||
| } | ||
| } | ||
| catch (...) | ||
| { | ||
| // If any exception occurs during C++ access, the layout is incompatible | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Although the current repository does not use exceptions, the library can be included on other modules. We cannot control their environment, so it's foreseeable that some of them might have exceptions active. In that case, the lack of the try/catch would be a problem. So, although it's not extrictly necessary for this repository itself, it does not cause any harm for it, while also protecting potential future use-cases. Therefore, we choose to keep it.
| pub fn #fn_get(&self) -> &#generic { | ||
| assert_eq!(self.discriminant, #discriminant); | ||
| unsafe { &self.storage.#value_name } | ||
| } | ||
|
|
||
| pub fn #fn_get_mut(&mut self) ->&mut std::mem::ManuallyDrop<#generic> { | ||
| assert_eq!(self.discriminant, #discriminant); | ||
| unsafe { &mut self.storage.#value_name } | ||
| } |
There was a problem hiding this comment.
The code does compile. The gist is that C++ does the clean-up, therefore it's not a problem to have a ManuallyDrop<T> here. False positive.
| fn get_index(&self) -> usize { | ||
| self.discriminant as usize | ||
| } |
There was a problem hiding this comment.
Clean-up is performed on C++ side. False positive.
| //! This module provides FFI-compatible types and functions to interoperate with bmw::Result and | ||
| //! associated types. | ||
| //! | ||
| //! It also offers integration with cxx, allowing type aliases of bmw::Result to be used as types | ||
| //! of arguments or return values of C++ functions and methods. See the documentation of |
| #[cfg(all(test, not(miri)))] | ||
| fn test_binary_compatibility_sizes() { | ||
| unsafe { |
There was a problem hiding this comment.
False positive, it's marked as test, but not under miri.
| This directory contains Rust FFI bindings that enable seamless interoperability | ||
| between C++ and Rust components using BMW's result types. | ||
|
|
||
| ## Overview | ||
|
|
||
| The Rust bindings provide type-safe, zero-cost abstractions for working with: | ||
|
|
||
| - **`bmw::Result<T>`** - C++ result type with error handling | ||
| - **`bmw::result::Error`** - Error objects with domain and error codes | ||
|
|
| A detailed sequence diagram illustrating the complete FFI flow is available in [sequence.puml](plantuml/sequence.puml), and depicted bellow: | ||
|
|
||
|  |
| ```bash | ||
| # Basic Result example | ||
| bazel run --config=spp_host_gcc //platform/aas/lib/result/rust:result_rs_example | ||
|
|
||
| # SafeResult integration | ||
| bazel run --config=spp_host_gcc //platform/aas/lib/result/rust:safe_result_integration_example | ||
|
|
||
| # Type mismatch detection | ||
| bazel run --config=spp_host_gcc //platform/aas/lib/result/rust:safe_result_type_mismatch_example | ||
| ``` | ||
|
|
||
| ### Run Tests | ||
|
|
||
| ```bash | ||
| bazel test --config=spp_host_gcc //platform/aas/lib/result/rust:... | ||
| ``` |
| score::result::Error MakeError(ConversionErrorCode code, std::string_view user_message = {}) noexcept | ||
| { | ||
| return {static_cast<score::result::ErrorCode>(code), conversion_error_domain, user_message}; | ||
| } |
There was a problem hiding this comment.
False positive. This is needed by result.h. The reason why it's being provided on an example is that client code using he lib should provide it, as does the example.
With this interface, Rust code can convert C++ `score::Result` objects to Rust's own `std::Result<T, E>` transparently.
This type of target creates a shell script, intended to run on host. However, the target configuration tries to run the script on target, which fails to run. As agreed with component management, it's fine to excluse these tests from the target runs.
With this interface, Rust code can convert C++
score::Resultobjects to Rust's ownstd::Result<T, E>transparently.Note: The PR is on draft state because it's overriding
@score_cratesuntil it's officially released. Due to this overriding, also@score_toolinghad to be overridden.