-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: result_ffi_guarantees #3391
Conversation
Is there any particular downside to just making this guarantee for all enums similar to |
Yes that was mentioned in the Future Work portion. Basically it can probably become a semver hazard sort of thing if it's automatically done for all enums and people aren't opting in to the behavior. Keeping the RFC scoped small makes it more likely to be accepted and completed on the types people use most often. |
Ahh, I see; I completely missed that part. I guess that what I'm failing to understand is how you can actually run into that kind of hazard unless you use something like |
The examples need a bit more diversity. They are all of the form This needs some more discussion on which exact zero-sized types should have layout optimization guarantees, because it could be a semver hazard, particularly in FFI. A type I have doubts whether the layout guarantees would be useful in practice, though. The primary motivation in the RFC isn't memory usage but FFI ergonomics. With This means that a separate ABI-stable struct as the return type, with functions |
Tbh, I'm not particularly keen on having guarentees on |
@ChrisDenton Rust guarantees their specific behaviour, so this seems like a non-issue. On the FFI side none of those types would exist anyway, just like |
repr(transparent) newtypes of |
Why would you make it a newtype of struct ZST;
#[repr(transparent)]
struct ZST();
#[repr(transparent)]
struct ZST {}
#[repr(transparent)]
struct ZST<T>(PhantomData<T>); |
I had thought that a So sure, |
@rfcbot merge |
Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
Overall seems reasonable. @rfcbot concern needs-to-mention-non_exhaustive It's implied by the definition of |
Zero fields was FCPed in rust-lang/rust#77841 |
There's been some ideas of potentially modifying the calling convention to support Result more cheaply. Does this RFC interfere with that? |
This RFC requires that the combined type be treated as the primitive type, so I guess "yes, it could interfere with that". However, since the primitive types are generally single-register (except and wide pointers and ints bigger than a machine register), this doesn't seem like a strong problem. Other forms of Result (eg:
It seems that the reference didn't get an update.
Updated. |
one thing i'd specifically want to reserve space for is representing this may need a specific carve-out exception in the defined ABI for |
Hm, could you say more about what you'd expect to be changed within the RFC to have that carved out? Some mentioning in Future Possibilities? I'm completely unfamiliar with how unwinding interacts with the foreign ABI modes. |
text/0000-result_ffi_guarantees.md
Outdated
* `Result<T, E>` where either `T` or `E`: | ||
* Are a zero-sized type with alignment 1 (a "1-ZST"). | ||
* Either have no fields (eg: `()` or `struct Foo;`) or have `repr(transparent)` if there are fields. | ||
* Do not have the `#[non_exhaustive]` attribute. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I assume the nested list of requirements is a conjunction, but that should be made explicit.
- I am surprised to see
repr(transparent)
here. In the past we stated that arepr(transparent)
in the standard library does not mean this type will not change in the future -- basicallyrepr(transparent)
is a private attribute that the library may use, but not clients of the library. If this is intended to deal with issues like repr(transparent) should not consider non_exhaustive types as 1-ZSTs outside their crate rust#78586, I would have expected "has no private field" (which is then trivially true for fieldless types)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
yeah it's a "must meet all of these", i can clarify the wording.
-
I'd be fine changing to "must have no fields" if necessary. Since it's in FCP though maybe a T-lang member can make a clear call here before I touch that part?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is that it's very easy to add a private field and accidentally break the guaranteed layout. Imho there should be some explicit way to opt into such optimizations for ZSTs which are guaranteed to remain ZST. I don't think #[repr(transparent)]
is a good solution to the issue, but it's the closest approximation in current Rust, so it's at least something to consider.
Is the "private attribute" opinion documented somewhere? Afaik it isn't, and it's not uncommon to treat it as an API guarantee.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked Josh via Zulip and he said it would be fine to further restrict the rules here even while FCP is active.
I will make an update later today to simply require no fields at all (and no non_exhaustive). Then it will be the usual semver breaking change if a field is added. I believe that should satisfy things?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. The thing that really worries me is that, being a guaranteed optimization, breaking the layout won't produce any errors. The optimization will just silently stop applying. This will lead to UB on the FFI side, and I don't see any reliable way to check for such breakage.
An upstream crate makes a semver breaking change by adding some inner field (possibly even zero-sized one, but perhaps not in a guaranteed way). A downstream crate which used the changed type in extern fn() -> Result<&T, ZST>
now silently gets UB, and there is no way for them to avoid it other than tracking all changes in all dependencies.
Currently the solution would involve explicitly ABI-stable types, and conversions between them and native Rust types. Such conversions would either work correctly, fail to compile or fail at runtime (assuming they are properly written and verify their preconditions).
Also note that this isn't an issue for current types with guaranteed layout optimizations, since their number is very small and they are all provided by the language. There is only a finite number of non-generic NonZeroX
types, and they are all guaranteed by the language. If user-defined niches become a thing, I would be just as worried.
For this reason I would prefer to have an explicit way to specify that the layout optimization must apply, rather than deduce it from implicit properties of code, which may be changed by the programmer without even considering that someone could rely on them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the crate you're getting a type from puts some attribute on their type, can't they just remove that attribute (presumably as a semver break) in a future version? Then isn't it just as bad for the downstream crates relying on it?
Honestly I don't think people should do this in the first place with any type that's not their own type or ()
. However, I don't want to disallow user ZSTs entirely because it makes for much better internal error handling compared to having ()
as the error type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the point of having an attribute is that explicitly having to remove it makes people consider it a semver break since it's obvious, whereas having it completely implicit makes it much more likely people won't realize they need a semvar break to change their library type -- most people aren't going to think about Result
's ABI when they change their library type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An upstream crate makes a semver breaking change by adding some inner field (possibly even zero-sized one, but perhaps not in a guaranteed way). A downstream crate which used the changed type in
extern fn() -> Result<&T, ZST>
now silently gets UB, and there is no way for them to avoid it other than tracking all changes in all dependencies.
We do have improper_ctypes
warning though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
improper_ctypes
is far too noisy to be useful, so we shouldn't rely on a fairly substandard lint.
This RFC covers the FFI case, which wouldn't use that optimisation. And if it did then the future RFC proposing that would have a lot of specifying to do, and I think that specifying belongs there rather than here. |
I'm basically expecting that the list of requirements to have
this way we tell users they can't just just check the current list of requirements for any arbitrary type and be guaranteed to always forevermore have stable ABI when those requirements are met, future RFCs may introduce exceptions for types using new features. |
@rfcbot fcp reviewed This makes sense to me. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
Slight correction: function item types. Function pointers have sizes. |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon. |
This RFC was discussed in this week's lang team triage meeting. We (I) weren't sure whether the compiler already treats types like
If As far as I could tell, the former is the case, at least in my usual suspect ABI (32-bit x86). // cargo asm --target i686-unknown-linux-gnu
use std::num::NonZeroI32;
#[repr(C)]
pub struct Wrapper {
pub primitive: i32,
}
pub extern "C" fn primitive() -> i32 {
1
}
// different code than `primitive`
pub extern "C" fn wrapper() -> Wrapper {
Wrapper { primitive: 1 }
}
// same code as `primitive`
pub extern "C" fn result_nonzero() -> Result<NonZeroI32, ()> {
Ok(unsafe { NonZeroI32::new_unchecked(1) })
} |
The @rust-lang/lang team has decided to accept this RFC. To track further discussion, subscribe to the tracking issue here: |
Support Result<T, E> across FFI when niche optimization can be used Allow allow enums like `Result<T, E>` to be used across FFI if the T/E can be niche optimized and the non-niche-optimized type is FFI safe. Implementation of rust-lang/rfcs#3391 Tracking issue: rust-lang#110503 Additional ABI and codegen tests were added in rust-lang#115372
Support Result<T, E> across FFI when niche optimization can be used Allow allow enums like `Result<T, E>` to be used across FFI if the T/E can be niche optimized and the non-niche-optimized type is FFI safe. Implementation of rust-lang/rfcs#3391 Tracking issue: rust-lang#110503 Additional ABI and codegen tests were added in rust-lang#115372
Support Result<T, E> across FFI when niche optimization can be used Allow allow enums like `Result<T, E>` to be used across FFI if the T/E can be niche optimized and the non-niche-optimized type is FFI safe. Implementation of rust-lang/rfcs#3391 Tracking issue: rust-lang#110503 Additional ABI and codegen tests were added in rust-lang#115372
Support Result<T, E> across FFI when niche optimization can be used (v2) This PR is identical to rust-lang#122253, which was approved and merged but then removed from master by a force-push due to a [CI bug](https://rust-lang.zulipchat.com/#narrow/stream/242791-t-infra/topic/ci.20broken.3F). r? ghost Original PR description: --- Allow allow enums like `Result<T, E>` to be used across FFI if the T/E can be niche optimized and the non-niche-optimized type is FFI safe. Implementation of rust-lang/rfcs#3391 Tracking issue: rust-lang#110503 Additional ABI and codegen tests were added in rust-lang#115372
Support Result<T, E> across FFI when niche optimization can be used (v2) This PR is identical to rust-lang#122253, which was approved and merged but then removed from master by a force-push due to a [CI bug](https://rust-lang.zulipchat.com/#narrow/stream/242791-t-infra/topic/ci.20broken.3F). r? ghost Original PR description: --- Allow allow enums like `Result<T, E>` to be used across FFI if the T/E can be niche optimized and the non-niche-optimized type is FFI safe. Implementation of rust-lang/rfcs#3391 Tracking issue: rust-lang#110503 Additional ABI and codegen tests were added in rust-lang#115372
Support Result<T, E> across FFI when niche optimization can be used (v2) This PR is identical to rust-lang#122253, which was approved and merged but then removed from master by a force-push due to a [CI bug](https://rust-lang.zulipchat.com/#narrow/stream/242791-t-infra/topic/ci.20broken.3F). r? ghost Original PR description: --- Allow allow enums like `Result<T, E>` to be used across FFI if the T/E can be niche optimized and the non-niche-optimized type is FFI safe. Implementation of rust-lang/rfcs#3391 Tracking issue: rust-lang#110503 Additional ABI and codegen tests were added in rust-lang#115372
Support Result<T, E> across FFI when niche optimization can be used (v2) This PR is identical to #122253, which was approved and merged but then removed from master by a force-push due to a [CI bug](https://rust-lang.zulipchat.com/#narrow/stream/242791-t-infra/topic/ci.20broken.3F). r? ghost Original PR description: --- Allow allow enums like `Result<T, E>` to be used across FFI if the T/E can be niche optimized and the non-niche-optimized type is FFI safe. Implementation of rust-lang/rfcs#3391 Tracking issue: rust-lang/rust#110503 Additional ABI and codegen tests were added in rust-lang/rust#115372
Support Result<T, E> across FFI when niche optimization can be used (v2) This PR is identical to #122253, which was approved and merged but then removed from master by a force-push due to a [CI bug](https://rust-lang.zulipchat.com/#narrow/stream/242791-t-infra/topic/ci.20broken.3F). r? ghost Original PR description: --- Allow allow enums like `Result<T, E>` to be used across FFI if the T/E can be niche optimized and the non-niche-optimized type is FFI safe. Implementation of rust-lang/rfcs#3391 Tracking issue: rust-lang/rust#110503 Additional ABI and codegen tests were added in rust-lang/rust#115372
Support Result<T, E> across FFI when niche optimization can be used (v2) This PR is identical to #122253, which was approved and merged but then removed from master by a force-push due to a [CI bug](https://rust-lang.zulipchat.com/#narrow/stream/242791-t-infra/topic/ci.20broken.3F). r? ghost Original PR description: --- Allow allow enums like `Result<T, E>` to be used across FFI if the T/E can be niche optimized and the non-niche-optimized type is FFI safe. Implementation of rust-lang/rfcs#3391 Tracking issue: rust-lang/rust#110503 Additional ABI and codegen tests were added in rust-lang/rust#115372
Rendered