| # Reference Safety |
| |
| The 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). |