| # Bindings for pointer and reference types |
| |
| Here we describe how Crubit maps pointer and reference types. |
| |
| ## Rust bindings for C++ APIs |
| |
| Rust bindings for lifetime-annotated C++ pointers and references look as |
| follows: |
| |
| <!-- The examples in the table below are based on |
| `FunctionTakingPointersAndReferences` from |
| `rs_bindings_from_cc/test/golden/types_rs_api.rs`. Note that |
| `FieldTypeTestStruct` can't be used because its fields are not |
| lifetime-annotated (lifetime elision doesn't work with structs). --> |
| |
| C++ API | Rust bindings |
| ------------- | ------------------- |
| `const T& $a` | `&'a T` |
| `T& $a` | `&'a mut T` |
| `const T* $a` | `Option<&'a T>` |
| `T* $a` | `Option<&'a mut T>` |
| |
| TODO: Document how explicit lifetime annotations work. (A prerequisite might be |
| defining `$a` macros via a new header under `crubit/support`.) |
| |
| TODO: Document how `#pragma clang lifetime_elision` works. |
| |
| C++ pointers and references that are *not* annotated with lifetimes look as |
| follows: |
| |
| C++ API | Rust bindings |
| ---------- | ------------- |
| `const T&` | `*const T` |
| `T&` | `*mut T` |
| `const T*` | `*const T` |
| `T*` | `*mut T` |
| |
| TODO: Document what happens for `void*`. |
| |
| ## C++ bindings for Rust APIs |
| |
| Rust bindings for lifetime-annotated C++ pointers look as follows: |
| |
| <!-- The contents of the table below are somewhat based on |
| `test_format_ty_for_cc_successes` from `cc_bindings_from_rs/bindings.rs` --> |
| |
| Rust API | C++ bindings |
| ---------- | ------------ |
| `*const T` | `const T*` |
| `*mut T` | `T*` |
| |
| When used as function parameter types or function return types, Rust references |
| map into the corresponding C++ types as follows: |
| |
| <!-- The contents of the table below are somewhat based on |
| `test_format_ty_for_cc_successes` from `cc_bindings_from_rs/bindings.rs` --> |
| |
| Rust API | C++ bindings |
| ----------- | ---------------------------------------------------------------- |
| `&'a T` | `const std::int32_t & [[clang::annotate_type("lifetime", "a")]]` |
| `&'a mut T` | `std::int32_t & [[clang::annotate_type("lifetime", "a")]]` |
| `&str` | TODO(b/262580415): Not supported yet. |
| `&mut str` | TODO(b/262580415): Not supported yet. |
| `&[T]` | TODO(b/271016831): Not supported yet. |
| `&mut[T]` | TODO(b/271016831): Not supported yet. |
| |
| TODO(b/286299326): Use shorter `$a` syntax in the generated C++. |
| |
| TODO(b/279913786): Generate `ABSL_ATTRIBUTE_LIFETIME_BOUND` when appropriate. |
| |
| TODO(b/286256327): Support Rust references in fields and nested types. |
| |
| ## Safety notes |
| |
| Rust reference |
| [documents Undefined Behavior (UB)](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) |
| and says that "it is the programmer's responsibility when writing unsafe code to |
| ensure that any safe code interacting with the unsafe code cannot trigger these |
| [undefined] behaviors". A programmer using Crubit bindings has the same |
| responsibility: Crubit is implicitly "unsafe", and incorrect usage can cause UB. |
| The sections below document requirements for safely using Crubit when working |
| with Rust and C++ references and pointers. The requirements are the same as the |
| ones |
| [documented in the Rust reference](https://doc.rust-lang.org/reference/behavior-considered-undefined.html), |
| but are rephrased below from Crubit perspective. |
| |
| The safety requirements below focus on avoiding UB related to Rust references |
| and therefore matter in scenarios where Crubit-generated code may create Rust |
| references out of C++ references or C++ pointers: |
| |
| - Today: Using Crubit-generated Rust bindings for C++ APIs if the bindings |
| accept, store, or return Rust references. TODO: In the future Crubit should |
| use `CppRef<T>` in the generated bindings (and this should mitigate some of |
| the memory safety concerns). |
| - Using Crubit-generated C++ bindings for Rust APIs if the |
| bindings wrap Rust APIs that accept, store, or return Rust references. |
| |
| ### Incorrect C++ lifetime annotations |
| |
| Incorrect lifetime annotations may lead to UB. Rust's borrow checker prevents |
| incorrect lifetime annotations, but lifetime annotations of C++ APIs are not |
| verified by the C++ compiler and Crubit's optional lifetime analysis can't |
| detect all incorrect annotations. Note that Crubit assumes that lifetime |
| annotations are correct both for explicit annotations (e.g. `int& $a f2(int& |
| $a);`) as well as for annotations provided by `#pragma clang lifetime_elision`. |
| |
| ### C++ mutating values referenced by Rust |
| |
| Mutating a value in C++ is UB if the mutation happens while Rust holds a |
| references to that value. This applies to Rust shared references (e.g. `&T`) and |
| to exclusive references (e.g. `&mut T`). |
| |
| Examples of C++ features that may mutate a value that Rust holds a reference to: |
| |
| * Using copy or move assignment operator of C++ value that Rust has a |
| reference to. |
| * Mutating public fields of a C++ struct that Rust has a reference to. |
| |
| TODO: Try to succintly mention the idea that short-lived / non-retained |
| references are safe from the mutation risk. |
| |
| ### Dangling or null references |
| |
| All references and |
| [`NonNull` pointers](https://doc.rust-lang.org/std/ptr/struct.NonNull.html) must |
| not be null, and if they point to a nonzero span of memory, must not be |
| dangling. (The behavior of a program which violates these rules is undefined.) |
| |
| C++ doesn't share these rules, and care must be taken when converting Rust |
| references to and from C++ pointers. For example, spans/slices are particularly |
| error-prone: a Rust empty slice uses a dangling pointer (which produces UB in |
| C++ when used in pointer arithmetic), and a C++ empty span (often) uses nullptr |
| (which is UB in Rust). To effectively use spans in FFI, one must either use |
| non-native types, or perform a conversion operation which rewrites the pointer |
| values. For that, we recommend using the conversion routines provided by Crubit |
| support library (e.g. `impl From<string_view> for &[u8]`). |
| |
| TODO(b/271016831, b/262580415): Cover `rs_std::Slice<T>` and/or `rs_std::str` |
| above once these types are provided by Crubit. |
| |
| ### References to uninitialized memory or invalid values |
| |
| Creating a Rust reference that points to uninitialized memory is UB. |
| |
| Care should be taken when passing C++ references across the FFI boundary to |
| avoid creating Rust references that point to uninitialized memory. This is |
| especially important for references to types that don't enforce proper |
| initialization through their constructors or other factory APIs - primitive |
| types like integers are one example. |
| |
| Note that Crubit-generated C++ bindings for Rust code won't create C++ |
| references to uninitialized memory for: |
| |
| * Rust `struct`s, `enum`s, and `union`s - all the constructors generated by |
| Crubit guarantee proper initialization |
| * `rs_std::rs_char` when constructed in C++ via `rs_char::from_u32` or when |
| passed from Rust. (`rs_char::from_u32_unchecked` is unsafe in Rust sense) |
| |
| TODO: b/296287315: Support `MaybeUninit<T>`. |
| |
| ### Breaking Rust aliasing rules |
| |
| Constructing a Rust references that aliases the same address as an already |
| existing exclusive reference `&mut T` is UB. |
| |
| TODO: Provide FFI-related examples. |
| |
| TODO: Document what runtime checks are provided by Crubit (and link to a |
| separate md document that explains why general checks are infeasible). |