blob: 5179c34a669f1ff116fa3c5b4dbf42df5cbc1024 [file] [log] [blame] [view]
# 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).