Skip to content

Commit d1df23c

Browse files
authored
Merge pull request #3391 from Lokathor/optional
RFC: result_ffi_guarantees
2 parents e66fda2 + 7320c01 commit d1df23c

File tree

1 file changed

+94
-0
lines changed

1 file changed

+94
-0
lines changed

text/3391-result_ffi_guarantees.md

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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

Comments
 (0)