|
| 1 | +# RFC: result_ffi_guarantees |
| 2 | + |
| 3 | +- Feature Name: `result_ffi_guarantees` |
| 4 | +- Start Date: 2023-02-15 |
| 5 | +- RFC PR: [rust-lang/rfcs#3391](https://github.com/rust-lang/rfcs/pull/3391) |
| 6 | +- Rust Issue: [rust-lang/rust#110503](https://github.com/rust-lang/rust/issues/110503) |
| 7 | + |
| 8 | +# Summary |
| 9 | +[summary]: #summary |
| 10 | + |
| 11 | +This RFC gives specific layout and ABI guarantees when wrapping "non-zero" data types from `core` in `Option` or `Result`. This allows those data types to be used directly in FFI, in place of the primitive form of the data (eg: `Result<(), NonZeroI32>` instead of `i32`). |
| 12 | + |
| 13 | +# Motivation |
| 14 | +[motivation]: #motivation |
| 15 | + |
| 16 | +Rust often needs to interact with foreign code. However, foreign function type signatures don't normally support any of Rust's rich type system. Particular function inputs and outputs will simply use 0 (or null) as a sentinel value and the programmer has to remember when that's happening. |
| 17 | + |
| 18 | +Though it's common for "raw bindings" crates to also have "high level wrapper" crates that go with them (eg: `windows-sys`/`windows`, or `sdl2-sys`/`sdl2`, etc), someone still has to write those wrapper crates which use the foreign functions directly. Allowing Rust programmers to use more detailed types with foreign functions makes their work easier. |
| 19 | + |
| 20 | +# Guide-level explanation |
| 21 | +[guide-level-explanation]: #guide-level-explanation |
| 22 | + |
| 23 | +I'm not sure how to write a "guide" portion of this that's any simpler than the "reference" portion, which is already quite short. |
| 24 | + |
| 25 | +# Reference-level explanation |
| 26 | +[reference-level-explanation]: #reference-level-explanation |
| 27 | + |
| 28 | +When either of these two `core` types: |
| 29 | + |
| 30 | +* `Option<T>` |
| 31 | +* `Result<T, E>` where either `T` or `E` meet all of the following conditions: |
| 32 | + * Is a zero-sized type with alignment 1 (a "1-ZST"). |
| 33 | + * Has no fields. |
| 34 | + * Does not have the `#[non_exhaustive]` attribute. |
| 35 | + |
| 36 | +Is combined with a non-zero or non-null type (see the chart), the combination has the same layout (size and alignment) and the same ABI as the primitive form of the data. |
| 37 | + |
| 38 | +| Example combined Type | Primitive Type | |
| 39 | +|:-|:-| |
| 40 | +| `Result<NonNull<T>, ()>` | `*mut T` | |
| 41 | +| `Result<&T, ()>` | `&T` | |
| 42 | +| `Result<&mut T, ()>` | `&mut T` | |
| 43 | +| `Result<fn(), ()>` | `fn()` | |
| 44 | +| `Result<NonZeroI8, ()>` | `i8` | |
| 45 | +| `Result<NonZeroI16, ()>` | `i16` | |
| 46 | +| `Result<NonZeroI32, ()>` | `i32` | |
| 47 | +| `Result<NonZeroI64, ()>` | `i64` | |
| 48 | +| `Result<NonZeroI128, ()>` | `i128` | |
| 49 | +| `Result<NonZeroIsize, ()>` | `isize` | |
| 50 | +| `Result<NonZeroU8, ()>` | `u8` | |
| 51 | +| `Result<NonZeroU16, ()>` | `u16` | |
| 52 | +| `Result<NonZeroU32, ()>` | `u32` | |
| 53 | +| `Result<NonZeroU64, ()>` | `u64` | |
| 54 | +| `Result<NonZeroU128, ()>` | `u128` | |
| 55 | +| `Result<NonZeroUsize, ()>` | `usize` | |
| 56 | + |
| 57 | +* While `fn()` is listed just once in the above table, this rule applies to all `fn` types (regardless of ABI, arguments, and return type). |
| 58 | + |
| 59 | +For simplicity the table listing only uses `Result<_, ()>`, but swapping the `T` and `E` types, or using `Option<T>`, is also valid. |
| 60 | +What changes are the implied semantics: |
| 61 | +* `Result<NonZeroI32, ()>` is "a non-zero success value" |
| 62 | +* `Result<(), NonZeroI32>` is "a non-zero error value" |
| 63 | +* `Option<NonZeroI32>` is "a non-zero value is present" |
| 64 | +* they all pass over FFI as if they were an `i32`. |
| 65 | + |
| 66 | +Which type you should use with a particular FFI function signature still depends on the function. |
| 67 | +Rust can't solve that part for you. |
| 68 | +However, once you've decided on the type you want to use, the compiler's normal type checks can guide you everywhere else in the code. |
| 69 | + |
| 70 | +# Drawbacks |
| 71 | +[drawbacks]: #drawbacks |
| 72 | + |
| 73 | +* The compiler has less flexibility with respect to discriminant computation and pattern matching optimizations when a type is niche-optimized. |
| 74 | + |
| 75 | +# Rationale and alternatives |
| 76 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 77 | + |
| 78 | +It's always possible to *not* strengthen the guarantees of the language. |
| 79 | + |
| 80 | +# Prior art |
| 81 | +[prior-art]: #prior-art |
| 82 | + |
| 83 | +The compiler already suports `Option` being combined with specific non-zero types, this RFC mostly expands the list of guaranteed support. |
| 84 | + |
| 85 | +# Unresolved questions |
| 86 | +[unresolved-questions]: #unresolved-questions |
| 87 | + |
| 88 | +None at this time. |
| 89 | + |
| 90 | +# Future possibilities |
| 91 | +[future-possibilities]: #future-possibilities |
| 92 | + |
| 93 | +* This could be expanded to include [ControlFlow](https://doc.rust-lang.org/nightly/core/ops/enum.ControlFlow.html) and [Poll](https://doc.rust-lang.org/nightly/core/task/enum.Poll.html). |
| 94 | +* This could be extended to *all* similar enums in the future. However, without a way to opt-in to the special layout and ABI guarantees (eg: a trait or attribute) it becomes yet another semver hazard for library authors. The RFC is deliberately limited in scope to avoid bikesheding. |
0 commit comments