Skip to content
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

repr(transparent) should not consider non_exhaustive types as 1-ZSTs outside their crate #78586

Open
scottmcm opened this issue Oct 30, 2020 · 22 comments · Fixed by #99020
Open
Labels
C-bug Category: This is a bug. E-help-wanted Call for participation: Help is requested to fix this issue. E-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@scottmcm
Copy link
Member

scottmcm commented Oct 30, 2020

In crate A:

#[non_exhaustive]
pub struct UnitType {}

In crate B:

#[repr(transparent)]
pub struct TransparentThing(i32, UnitType);

I expected that to fail in crate B, because the reason crate A put #[non_exhaustive] on the type was to allow it to get additional fields in the future, which would no longer allow it to work in repr(transparent).

But this actually compiles today, creating what I would consider an accidental semver hazard.

https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Stability.20of.201-ZSTness/near/215120408

@scottmcm scottmcm added C-bug Category: This is a bug. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Oct 30, 2020
@Mark-Simulacrum
Copy link
Member

I'm going to nominate for lang, because I think (perhaps with a future compat lint) we should not look at foreign types' size unless that type is repr(transparent) for the purposes of checking here.

@nagisa nagisa added F-non_exhaustive `#![feature(non_exhaustive)]` and removed F-non_exhaustive `#![feature(non_exhaustive)]` labels Oct 31, 2020
@scottmcm
Copy link
Member Author

scottmcm commented Nov 2, 2020

Note that this also applies to the way this was done before non_exhaustive:

pub struct UnitType(());

As that private field also keeps others from constructing or exhaustively matching it, but it would still be accepted as 1-ZST by repr(transparent).

We discussed this in the lang team meeting today. The consensus was that this is an accidental semver hazard that we'd like to do something about.

@rustbot modify labels: +E-help-wanted +E-medium

It'd be great to get a PR we could use to determine the impact of this. Ideally this isn't happening in the wild and can just be an error (maybe for one case or the other, if one is common and the other isn't) but it'll probably need at least a future imcompatibility lint.

@rustbot rustbot added E-help-wanted Call for participation: Help is requested to fix this issue. E-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate. labels Nov 2, 2020
@jsomedon
Copy link

jsomedon commented Dec 6, 2020

Hello folks, can I take this issue? I am kinda new to the community but I want to start contributing. :-)

@scottmcm
Copy link
Member Author

scottmcm commented Dec 7, 2020

@jsomedon Absolutely! Unfortunately I don't really know the code here, so don't have suggestions for where to start, but you could always try tracing through the code around non_exhaustive and dropping by zulip if you run into any trouble.

@jsomedon
Copy link

jsomedon commented Dec 8, 2020

Should a non-exhaustive struct be fine to stay in repr(transparent) in its own crate? (My understanding is that it's okay? As you can do whatever u want with the non-exhaustive in its own defining crate?)

@scottmcm
Copy link
Member Author

scottmcm commented Dec 8, 2020

@jsomedon Yes, I think that's ok, like how one can exhaustively match it inside its own defining crate.

@basil-cow
Copy link
Contributor

@jsomedon do you plan to continue working on this?

@jsomedon
Copy link

No, probably too overwhelming to my rust level. I would rather see someone else fixing this and read their code instead.

@basil-cow
Copy link
Contributor

@rustbot claim

@TheWastl
Copy link
Contributor

TheWastl commented Mar 30, 2021

If we should not look at foreign types' sizes, should it be allowed to use an opaque zero-sized type as the single non-zero-sized field in a repr(transparent) struct?

Crate A:

#[non_exhaustive]
struct Unit;

Crate B:

#[repr(transparent)]
struct Transparent(Unit, ());

This is currently rejected, but I think the zero-sizedness of opaque types (i.e. types with non_exhaustive or private fields) should be considered an implementation detail of crate A.

Edit: Note that this is also a potential semver hazard: If a crate contains a struct with only private fields, removing all fields should not be an API-breaking change.

@TheWastl
Copy link
Contributor

@Areredify Are you still working on this?

@RalfJung
Copy link
Member

Btw, I just noticed we have a somewhat similar restricting in repr(packed), which shows an error if it transitively contains a repr(align). This seems related in terms of being a semver hazard, doesn't it?

@TheWastl
Copy link
Contributor

@RalfJung

If adding repr(align) is not a breaking API change, then we run into another issue: repr(transparent) requires that the contained ZSTs have an alignment of 1. But if libraries can make their ZSTs repr(align(2)) (for example), then external types may not be used as ZSTs in repr(transparent) types at all! Unfortunately, PhantomData is such an external type ...

Granted, adding repr(align) to ZSTs is probably not done very often. But I think this should at least be documented somewhere.

@basil-cow
Copy link
Contributor

@TheWastl I can't work on this, but I was close to completing it if you want to pick it up. The issue was that I was traversing type contents incorrectly, iirc. It's at transparent_nonexhaustive branch.

@mati865
Copy link
Contributor

mati865 commented Dec 7, 2021

@rustbot release-assignment

@fee1-dead fee1-dead assigned fee1-dead and unassigned basil-cow Jul 7, 2022
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Jul 11, 2022
…non_exhaustive, r=oli-obk

check non_exhaustive attr and private fields for transparent types

Fixes rust-lang#78586.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jul 11, 2022
…non_exhaustive, r=oli-obk

check non_exhaustive attr and private fields for transparent types

Fixes rust-lang#78586.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jul 11, 2022
…non_exhaustive, r=oli-obk

check non_exhaustive attr and private fields for transparent types

Fixes rust-lang#78586.
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Jul 12, 2022
…non_exhaustive, r=oli-obk

check non_exhaustive attr and private fields for transparent types

Fixes rust-lang#78586.
@bors bors closed this as completed in 8a48557 Jul 13, 2022
@fee1-dead
Copy link
Member

This is probably not complete; we need to track it becoming a hard error in the future.

@fee1-dead fee1-dead reopened this Jul 13, 2022
@obi1kenobi

This comment was marked as resolved.

taiki-e added a commit to openrr/openrr that referenced this issue Sep 26, 2022
```
error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
  --> openrr-plugin/src/gen/proxy.rs:10:1
   |
10 | #[sabi_trait]
   | ^^^^^^^^^^^^^
   |
   = note: `-D repr-transparent-external-private-fields` implied by `-D warnings`
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #78586 <rust-lang/rust#78586>
   = note: this struct contains `UnsafeIgnoredType<SyncSend>`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future.
   = note: this error originates in the attribute macro `sabi_trait` (in Nightly builds, run with -Z macro-backtrace for more info)
```
taiki-e added a commit to openrr/openrr that referenced this issue Sep 26, 2022
```
error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
  --> openrr-plugin/src/gen/proxy.rs:10:1
   |
10 | #[sabi_trait]
   | ^^^^^^^^^^^^^
   |
   = note: `-D repr-transparent-external-private-fields` implied by `-D warnings`
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #78586 <rust-lang/rust#78586>
   = note: this struct contains `UnsafeIgnoredType<SyncSend>`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future.
   = note: this error originates in the attribute macro `sabi_trait` (in Nightly builds, run with -Z macro-backtrace for more info)
```
@fee1-dead fee1-dead removed their assignment Nov 21, 2022
@mystise
Copy link

mystise commented Apr 24, 2023

Is there a way for types to opt-in to a semver guarantee that they will remain ZSTs?

As a basic example:

// Crate A
#[repr(transparent)]
pub struct Token(());

impl Token {
	pub unsafe fn new() -> Self {
		Self(())
	}
}

OR

// Crate A
#[repr(transparent)]
pub struct Token(std::marker::PhantomData<()>);

impl Token {
	pub unsafe fn new() -> Self {
		Self(std::marker::PhantomData)
	}
}
// Crate B
#[repr(transparent)]
pub struct NeedsToken(u32, crate_a::Token);

Currently this hits this warning, but if Token is expected to only be handed out in certain ways by the library, say for generative lifetimes or ghost cells, then it being unsafe to construct could be relied upon in unsafe code, which means that making its elements public would present a soundness issue.

Is there a way to mark Token as "always zero sized barring a semver change"?

@fee1-dead
Copy link
Member

I think we could make it accept repr(transparent) types that have private fields? But I'm not sure if repr(transparent) over a ZST makes it a guarantee that its layout cannot be changed.

bors added a commit to rust-lang/cargo that referenced this issue May 1, 2023
Document that adding `#[non_exhaustive]` on existing items is breaking.

### What does this PR try to resolve?

Adding `#[non_exhaustive]` to an existing struct, enum, or variant is almost always a breaking change and requires a major version bump for semver purposes. This PR adds a section to the semver reference page that describes this and provides examples showing how `#[non_exhaustive]` can break code.

### Additional information

Adding `#[non_exhaustive]` to a unit struct currently has no effect on whether that struct can be constructed in downstream crates. This is inconsistent with the behavior of `#[non_exhaustive]` on unit enum variants, which may not be constructed outside their own crate. This might be due to a similar underlying cause as: rust-lang/rust#78586

The confusing "variant is private" error messages for non-exhaustive unit and tuple variants are a known issue tracked in: rust-lang/rust#82788

Checking for the struct portion of this semver rule is done in: obi1kenobi/cargo-semver-checks#4
@Jules-Bertholet
Copy link
Contributor

The below struct definitions trip this lint. I see no reason why they must be illegal, and the first one could even be useful:

#[repr(transparent)]
pub struct Foo(usize, core::mem::ManuallyDrop<()>);

#[repr(transparent)]
pub struct Bar(usize, std::cell::UnsafeCell<()>);

#[repr(transparent)]
pub struct Baz(usize, core::mem::MaybeUninit<()>);

I think we could make it accept repr(transparent) types that have private fields?

I think that is the right choice.

@dtolnay

This comment was marked as resolved.

@dtolnay dtolnay added the regression-from-stable-to-nightly Performance or correctness regression from stable to nightly. label Sep 18, 2023
@rustbot rustbot added the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Sep 18, 2023
dtolnay added a commit to dtolnay/syn that referenced this issue Sep 18, 2023
dtolnay added a commit to dtolnay/syn that referenced this issue Sep 18, 2023
    warning: zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
       --> src/token.rs:378:17
        |
    378 |                   pub spans: [Span; $len],
        |                   ^^^^^^^^^^^^^^^^^^^^^^^
    ...
    560 | / define_punctuation_structs! {
    561 | |     "_" pub struct Underscore/1 /// wildcard patterns, inferred types, unnamed items in constants, extern c...
    562 | | }
        | |_- in this macro invocation
        |
        = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
        = note: for more information, see issue #78586 <rust-lang/rust#78586>
        = note: this struct contains `proc_macro2::Span`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future.
        = note: `#[warn(repr_transparent_external_private_fields)]` on by default
        = note: this warning originates in the macro `define_punctuation_structs` (in Nightly builds, run with -Z macro-backtrace for more info)

    warning: zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
       --> src/token.rs:378:17
        |
    378 |                   pub spans: [Span; $len],
        |                   ^^^^^^^^^^^^^^^^^^^^^^^
    ...
    785 | / define_punctuation! {
    786 | |     "&"           pub struct And/1        /// bitwise and logical AND, borrow, references, reference patterns
    787 | |     "&&"          pub struct AndAnd/2     /// lazy AND, borrow, references, reference patterns
    788 | |     "&="          pub struct AndEq/2      /// bitwise AND assignment
    ...   |
    831 | |     "~"           pub struct Tilde/1      /// unused since before Rust 1.0
    832 | | }
        | |_- in this macro invocation
        |
        = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
        = note: for more information, see issue #78586 <rust-lang/rust#78586>
        = note: this struct contains `proc_macro2::Span`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future.
        = note: this warning originates in the macro `define_punctuation_structs` which comes from the expansion of the macro `define_punctuation` (in Nightly builds, run with -Z macro-backtrace for more info)

    warning: zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
       --> src/token.rs:147:9
        |
    147 |         pub span: Span,
        |         ^^^^^^^^^^^^^^
        |
        = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
        = note: for more information, see issue #78586 <rust-lang/rust#78586>
        = note: this struct contains `proc_macro2::Span`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future.
@compiler-errors

This comment was marked as resolved.

@compiler-errors compiler-errors removed regression-from-stable-to-nightly Performance or correctness regression from stable to nightly. I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Sep 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. E-help-wanted Call for participation: Help is requested to fix this issue. E-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.