Skip to content

Distinguish repr(C) ZSTs from others in ABI compatibility rules - #157973

Merged
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
Jules-Bertholet:distinguish-c-zst-docs
Oct 3, 2026
Merged

rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
Jules-Bertholet:distinguish-c-zst-docs

Conversation

@Jules-Bertholet

@Jules-Bertholet Jules-Bertholet commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

View all comments

FCP: #157973 (comment)

(Split out from compiler implementation in #156112)

Some C ABIs pass and return ZSTs by pointer. But () should never be returned by pointer, as it must match void. To account for this, we have to weaken the present guarantee of "any two types with size 0 and alignment 1 are ABI-compatible" to exclude repr(C).

t-lang nomination summary comment

Fixes rust-lang/unsafe-code-guidelines#552; see also #78586, #155299.
Also related to #155984.

@rustbot label T-lang A-ABI needs-fcp

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jun 16, 2026
@rustbot

rustbot commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

r? @Mark-Simulacrum

rustbot has assigned @Mark-Simulacrum.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: libs
  • libs expanded to 11 candidates
  • Random selection from 6 candidates

@rustbot rustbot added A-ABI Area: Concerning the application binary interface (ABI) needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. T-lang Relevant to the language team labels Jun 16, 2026
Comment thread library/core/src/primitive_docs.rs Outdated
/// - Alignment 1
/// - Not `repr(C)`
/// - Not a `repr(transparent)` wrapper around a type that fails to satisfy these conditions
/// - Not an array whose element type fails to satisfy these conditions

@Jules-Bertholet Jules-Bertholet Jun 19, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We exempt arrays for 2 reasons:

  • A future version of C, or some other language we would like to do FFI with, might allow passing arrays to functions directly by value, in a way that would conflict with these guarantees
  • It would be nice to also use "trivial ABI" in the specification of repr(C), and we need this clause for that. See discussion at repr(ordered_fields) rfcs#3845 (comment)

We could also simplify this clause by saying merely:

Suggested change
/// - Not an array whose element type fails to satisfy these conditions
/// - Not an array

The downside would be a larger breaking change.

Note that repr(transparent) will need to be adjusted to account for this change (by rejecting non-trivial arrays as "additional" fields).

View changes since the review

@RalfJung RalfJung Jul 2, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with saying that "all arrays with trivial-ABI element type have trivial ABI".

However, even that less-breaking version breaks ghost again, right? Similar to dtolnay/ghost#41. ghost uses a zero-length array of *const T, which definitely does not have trivial ABI. I don't know why they do that...

EDIT: Ah, ghost is saved by having a repr(Rust) type around the array, under the rules discussed here.

@Mark-Simulacrum Mark-Simulacrum left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably mostly an opsem question in my eyes, at least to figure out the right shape. I see there's already been some discussion but I think nailing the language is probably better delegated too.

r? opsem

View changes since this review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some C ABIs pass and return ZSTs by pointer. But () should never be returned by pointer, as it must match void. To account for this, we have to weaken the present guarantee of "any two types with size 0 and alignment 1 are ABI-compatible" to exclude repr(C).

I think the implication here is that () is repr(C)? Am I reading that right? Where do we make that guarantee? Or is the thinking that the language here would make () and #[repr(C)] struct Foo; not ABI compatible?

One callout is that ZSTs aren't (I think?) standardized -- C and C++ without extensions both require types to be non-ZST if I remember right (e.g., see https://stackoverflow.com/a/2632075). Maybe that has changed since then though?

It seems like at minimum, it would be nice to avoid weakening this guarantee for Rust ABI even if we do so for C ABIs as a result of the weird platforms.

@Jules-Bertholet Jules-Bertholet Jun 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is the thinking that the language here would make () and #[repr(C)] struct Foo; not ABI compatible?

Yes, this.

One callout is that ZSTs aren't (I think?) standardized

Correct.

Comment thread library/core/src/primitive_docs.rs Outdated
Comment thread library/core/src/primitive_docs.rs Outdated
Comment thread library/core/src/primitive_docs.rs Outdated
@RalfJung

RalfJung commented Jul 2, 2026 •

Copy link
Copy Markdown
Member

@rust-lang/opsem @rust-lang/lang what do you think? I think we do have to take back a bit of what we promised here, since we did promise too much. The current docs say:

  • Any two types with size 0 and alignment 1 are ABI-compatible.

This is plain wrong for extern "C" on Windows where the following types do not have the same ABI when used in return position:

// This is returned by-ptr.
#[repr(C)]
struct T1 {}

// This matches a C function returning `void`.
type T2 = ();

T1 is passed and returned by-ptr by MSVC so we have to do the same. But () means void so it cannot be returned by-ptr.
In argument position they do have the same ABI, but the way we achieve this is silly: we pass a pointer for () arguments.

So the proposal is to restrict the above rule as follows:

  • Any two types fulfilling all the following conditions are ABI-compatible; such types are said to have "trivial ABI":
    • It has size 0.
    • It has alignment 1.
    • One of the following apply:
      • It is a repr(Rust) (implicitly or explicitly, possibly with additional flags such as packed) struct, enum, union (regardless of its fields).
      • It is a tuple (regardless of its fields, and including ().
      • It is a repr(transparent) struct, enum, or union, and all fields have trivial ABI.
      • It is an array, and its element type has trivial ABI. (This requirement applies even to arrays of length 0.)
      • It is the never type !.
      • It is a function item type or closure type.

Under the old rules, we effectively said "All 1-ZST have trivial ABI". Now we have some further restrictions. I think the only types that used to have trivial ABI but don't any more are repr(C) 1-ZST and zero-length arrays of non-zero-sized types such as [u8; 0], as well as repr(transparent) wrappers around such types.

Note that we also have to adjust the logic for repr(transparent) to ensure that all the types it ignores have "trivial ABI" according to these rules. So there's two breaking changes here:

  1. Code that relied on the ABI compatibility we are taking away now has UB.
  2. Code that relied on repr(transparent) ignoring a 1-ZST that we no longer consider to be "trivial" will stop compiling.

I cratered the 2nd part and found 0 regressions.

I don't know how to measure the fallout from the 1st breakage. We should implement this in Miri, but even that will just give us a very incomplete partial picture.

That said, given that the current docs are wrong, we have to do something. The only actually open question (in my eyes) is how bespoke we want the rules to be with the aim of breaking less code.

  • We could have different ABI compatibility rules for the "Rust" ABI and the "C" ABI, and say that all 1-ZST have "trivial Rust ABI". This would theoretically reduce the amount of case 1 breakage (but we don't know anything about that breakage so it's unclear if there even exists code that would be helped by this). Currently our ABI compatibility notion is independent of the ABI used by the function, so this adds non-trivial extra complexity on the docs side. I don't think we should do this.
  • The rules in this PR say that all repr(Rust) 1-ZST have trivial ABI, without recursing further. This means that adding repr(transparent) to a type can make it lose its "trivial ABI" property! The way to intuitively understand this is that by adding repr(transparent) you are forcing the ABI to be compatible with at least one field, and if that field has an ABI that's determined by the C platform then we can't say that it has trivial ABI. The reason we do this is two-fold: this means more types have "trivial ABI", so we reduce the amount of type 1 breakage. And it means we can keep a pattern used by the ghost crate working (specifically, this allows us to consider a ghost-declared phantom type as being "trivial" for repr(transparent)).

Comment thread library/core/src/primitive_docs.rs Outdated
@traviscross traviscross added P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang needs-reference-pr This language change needs an approved Reference PR to proceed. labels Jul 8, 2026
@traviscross

Copy link
Copy Markdown
Contributor

We talked about this briefly in the lang call last week. We had wanted to see more discussion, if possible, particularly from those on opsem, to inform the decision.

Looking at it now, it occurs to me that this would also benefit from a Reference PR, and that such a PR would help facilitate the lang discussion.

@traviscross traviscross added P-lang-drag-2 Lang team prioritization drag level 2.https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang. and removed P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang labels Jul 14, 2026
@RalfJung

Copy link
Copy Markdown
Member

Does the Reference have any discussion of ABI compatibility currently? Hard to make a PR with a diff to something that's not even in the Reference yet.

@traviscross

Copy link
Copy Markdown
Contributor

What do you think needs to be done in the Reference at this point?

I had in mind defining trivial ABI and documenting this new rule in the Reference.

Where the Reference doesn't fully cover something, we've in the past then not added new or changed language rules in that area to the Reference (because we don't expect people to fully document an area in order to document one change). I now think that's less good than simply accepting (and documenting) that the Reference is incomplete in that area and still adding the rule for the thing we're changing in the language. So that's what we've been asking for on other changes: documenting the one rule being added.

But, here, I'll take care of it. I'm working on adding all of the ABI rules to the Reference. I'll roll this up in that.

@traviscross traviscross added waived-reference-pr This language change does not need a Reference PR. and removed needs-reference-pr This language change needs an approved Reference PR to proceed. labels Oct 1, 2026
@Jules-Bertholet

Copy link
Copy Markdown
Contributor Author

Note that once this merges, I'll propose a follow-up to change what we accept in repr(transparent). That will need a Reference PR, and I think it makes sense to do the move at that point.

Co-Authored-By: Brian Smith <brian@briansmith.org>
Co-Authored-By: Ralf Jung <post@ralfj.de>
@RalfJung

RalfJung commented Oct 2, 2026

Copy link
Copy Markdown
Member

Finally -- thanks @Jules-Bertholet and everyone else who was involved. :)
@bors r+ rollup

@rust-bors

rust-bors Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

📌 Commit fa05b78 has been tentatively approved by RalfJung

It will be put into the queue for this repository once PR CI succeeds.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 2, 2026
rust-bors Bot pushed a commit that referenced this pull request Oct 2, 2026
…uwer

Rollup of 9 pull requests

Successful merges:

 - #163655 (explicitly handle tests that pass with -Znext-solver)
 - #159924 (Send -fno-lto when linker plugin LTO is not requested to avoid having GCC do LTO when using rustc_codegen_gcc)
 - #163572 (Update the minimum external LLVM to 22)
 - #129822 (Docs - type guarantees update)
 - #157973 (Distinguish `repr(C)` ZSTs from others in ABI compatibility rules)
 - #162332 (preserve overflow in builtin Field candidates)
 - #163574 (intrinsics: Rename `abort` to `abort_immediate`)
 - #163638 (avoid trivial `fn map_bound` validations)
 - #163660 (yeet compare-mode-coherence)
rust-bors Bot pushed a commit that referenced this pull request Oct 3, 2026
…uwer

Rollup of 9 pull requests

Successful merges:

 - #163655 (explicitly handle tests that pass with -Znext-solver)
 - #159924 (Send -fno-lto when linker plugin LTO is not requested to avoid having GCC do LTO when using rustc_codegen_gcc)
 - #163572 (Update the minimum external LLVM to 22)
 - #129822 (Docs - type guarantees update)
 - #157973 (Distinguish `repr(C)` ZSTs from others in ABI compatibility rules)
 - #162332 (preserve overflow in builtin Field candidates)
 - #163574 (intrinsics: Rename `abort` to `abort_immediate`)
 - #163638 (avoid trivial `fn map_bound` validations)
 - #163660 (yeet compare-mode-coherence)
@rust-bors
rust-bors Bot merged commit 3bcfbad into rust-lang:main Oct 3, 2026
14 checks passed
@rustbot rustbot added this to the 1.101.0 milestone Oct 3, 2026
rust-bors Bot pushed a commit that referenced this pull request Oct 3, 2026
Rollup merge of #157973 - Jules-Bertholet:distinguish-c-zst-docs, r=RalfJung

Distinguish `repr(C)` ZSTs from others in ABI compatibility rules

FCP: #157973 (comment)

(Split out from compiler implementation in #156112)

Some C ABIs pass and return ZSTs by pointer. But `()` should never be returned by pointer, as it must match `void`. To account for this, we have to weaken the present guarantee of "any two types with size 0 and alignment 1 are ABI-compatible" to exclude `repr(C)`.

[t-lang nomination summary comment](#157973 (comment))

Fixes rust-lang/unsafe-code-guidelines#552; see also #78586, #155299.
Also related to #155984.

@rustbot label T-lang A-ABI needs-fcp
@Jules-Bertholet
Jules-Bertholet deleted the distinguish-c-zst-docs branch October 3, 2026 12:52
github-actions Bot pushed a commit to rust-lang/stdarch that referenced this pull request Oct 5, 2026
…alfJung

Distinguish `repr(C)` ZSTs from others in ABI compatibility rules

FCP: rust-lang/rust#157973 (comment)

(Split out from compiler implementation in rust-lang/rust#156112)

Some C ABIs pass and return ZSTs by pointer. But `()` should never be returned by pointer, as it must match `void`. To account for this, we have to weaken the present guarantee of "any two types with size 0 and alignment 1 are ABI-compatible" to exclude `repr(C)`.

[t-lang nomination summary comment](rust-lang/rust#157973 (comment))

Fixes rust-lang/unsafe-code-guidelines#552; see also rust-lang/rust#78586, rust-lang/rust#155299.
Also related to rust-lang/rust#155984.

@rustbot label T-lang A-ABI needs-fcp
github-actions Bot pushed a commit to rust-lang/stdarch that referenced this pull request Oct 5, 2026
…uwer

Rollup of 9 pull requests

Successful merges:

 - rust-lang/rust#163655 (explicitly handle tests that pass with -Znext-solver)
 - rust-lang/rust#159924 (Send -fno-lto when linker plugin LTO is not requested to avoid having GCC do LTO when using rustc_codegen_gcc)
 - rust-lang/rust#163572 (Update the minimum external LLVM to 22)
 - rust-lang/rust#129822 (Docs - type guarantees update)
 - rust-lang/rust#157973 (Distinguish `repr(C)` ZSTs from others in ABI compatibility rules)
 - rust-lang/rust#162332 (preserve overflow in builtin Field candidates)
 - rust-lang/rust#163574 (intrinsics: Rename `abort` to `abort_immediate`)
 - rust-lang/rust#163638 (avoid trivial `fn map_bound` validations)
 - rust-lang/rust#163660 (yeet compare-mode-coherence)
github-actions Bot pushed a commit to rust-lang/rustc-dev-guide that referenced this pull request Oct 5, 2026
…alfJung

Distinguish `repr(C)` ZSTs from others in ABI compatibility rules

FCP: rust-lang/rust#157973 (comment)

(Split out from compiler implementation in rust-lang/rust#156112)

Some C ABIs pass and return ZSTs by pointer. But `()` should never be returned by pointer, as it must match `void`. To account for this, we have to weaken the present guarantee of "any two types with size 0 and alignment 1 are ABI-compatible" to exclude `repr(C)`.

[t-lang nomination summary comment](rust-lang/rust#157973 (comment))

Fixes rust-lang/unsafe-code-guidelines#552; see also rust-lang/rust#78586, rust-lang/rust#155299.
Also related to rust-lang/rust#155984.

@rustbot label T-lang A-ABI needs-fcp
github-actions Bot pushed a commit to rust-lang/rustc-dev-guide that referenced this pull request Oct 5, 2026
…uwer

Rollup of 9 pull requests

Successful merges:

 - rust-lang/rust#163655 (explicitly handle tests that pass with -Znext-solver)
 - rust-lang/rust#159924 (Send -fno-lto when linker plugin LTO is not requested to avoid having GCC do LTO when using rustc_codegen_gcc)
 - rust-lang/rust#163572 (Update the minimum external LLVM to 22)
 - rust-lang/rust#129822 (Docs - type guarantees update)
 - rust-lang/rust#157973 (Distinguish `repr(C)` ZSTs from others in ABI compatibility rules)
 - rust-lang/rust#162332 (preserve overflow in builtin Field candidates)
 - rust-lang/rust#163574 (intrinsics: Rename `abort` to `abort_immediate`)
 - rust-lang/rust#163638 (avoid trivial `fn map_bound` validations)
 - rust-lang/rust#163660 (yeet compare-mode-coherence)
programskillforverification pushed a commit to programskillforverification/miri that referenced this pull request Oct 5, 2026
…alfJung

Distinguish `repr(C)` ZSTs from others in ABI compatibility rules

FCP: rust-lang/rust#157973 (comment)

(Split out from compiler implementation in rust-lang/rust#156112)

Some C ABIs pass and return ZSTs by pointer. But `()` should never be returned by pointer, as it must match `void`. To account for this, we have to weaken the present guarantee of "any two types with size 0 and alignment 1 are ABI-compatible" to exclude `repr(C)`.

[t-lang nomination summary comment](rust-lang/rust#157973 (comment))

Fixes rust-lang/unsafe-code-guidelines#552; see also rust-lang/rust#78586, rust-lang/rust#155299.
Also related to rust-lang/rust#155984.

@rustbot label T-lang A-ABI needs-fcp
programskillforverification pushed a commit to programskillforverification/miri that referenced this pull request Oct 5, 2026
…uwer

Rollup of 9 pull requests

Successful merges:

 - rust-lang/rust#163655 (explicitly handle tests that pass with -Znext-solver)
 - rust-lang/rust#159924 (Send -fno-lto when linker plugin LTO is not requested to avoid having GCC do LTO when using rustc_codegen_gcc)
 - rust-lang/rust#163572 (Update the minimum external LLVM to 22)
 - rust-lang/rust#129822 (Docs - type guarantees update)
 - rust-lang/rust#157973 (Distinguish `repr(C)` ZSTs from others in ABI compatibility rules)
 - rust-lang/rust#162332 (preserve overflow in builtin Field candidates)
 - rust-lang/rust#163574 (intrinsics: Rename `abort` to `abort_immediate`)
 - rust-lang/rust#163638 (avoid trivial `fn map_bound` validations)
 - rust-lang/rust#163660 (yeet compare-mode-coherence)
AzureXuanVerse pushed a commit to AzureXuanVerse/rustfmt that referenced this pull request Oct 7, 2026
…uwer

Rollup of 9 pull requests

Successful merges:

 - rust-lang/rust#163655 (explicitly handle tests that pass with -Znext-solver)
 - rust-lang/rust#159924 (Send -fno-lto when linker plugin LTO is not requested to avoid having GCC do LTO when using rustc_codegen_gcc)
 - rust-lang/rust#163572 (Update the minimum external LLVM to 22)
 - rust-lang/rust#129822 (Docs - type guarantees update)
 - rust-lang/rust#157973 (Distinguish `repr(C)` ZSTs from others in ABI compatibility rules)
 - rust-lang/rust#162332 (preserve overflow in builtin Field candidates)
 - rust-lang/rust#163574 (intrinsics: Rename `abort` to `abort_immediate`)
 - rust-lang/rust#163638 (avoid trivial `fn map_bound` validations)
 - rust-lang/rust#163660 (yeet compare-mode-coherence)
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Oct 7, 2026
…, r=oli-obk

Distinguish `repr(C)` ZSTs from others in ABI computation

Implement rust-lang#157973:

Some C ABIs pass and return ZSTs by pointer. But `()` should never be returned by pointer, as it must match `void`. To fix this, distinguish `repr(C)`  ZSTs (and `repr(transparent)` wrappers around them) from other ZSTs in ABI computation.

Fixes rust-lang/unsafe-code-guidelines#552.

cc @RalfJung
bjorn3 pushed a commit to rust-lang/rustc_codegen_cranelift that referenced this pull request Oct 8, 2026
…uwer

Rollup of 9 pull requests

Successful merges:

 - rust-lang/rust#163655 (explicitly handle tests that pass with -Znext-solver)
 - rust-lang/rust#159924 (Send -fno-lto when linker plugin LTO is not requested to avoid having GCC do LTO when using rustc_codegen_gcc)
 - rust-lang/rust#163572 (Update the minimum external LLVM to 22)
 - rust-lang/rust#129822 (Docs - type guarantees update)
 - rust-lang/rust#157973 (Distinguish `repr(C)` ZSTs from others in ABI compatibility rules)
 - rust-lang/rust#162332 (preserve overflow in builtin Field candidates)
 - rust-lang/rust#163574 (intrinsics: Rename `abort` to `abort_immediate`)
 - rust-lang/rust#163638 (avoid trivial `fn map_bound` validations)
 - rust-lang/rust#163660 (yeet compare-mode-coherence)
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Oct 8, 2026
…, r=oli-obk

Distinguish `repr(C)` ZSTs from others in ABI computation

Implement rust-lang#157973:

Some C ABIs pass and return ZSTs by pointer. But `()` should never be returned by pointer, as it must match `void`. To fix this, distinguish `repr(C)`  ZSTs (and `repr(transparent)` wrappers around them) from other ZSTs in ABI computation.

Fixes rust-lang/unsafe-code-guidelines#552.

cc @RalfJung
rust-bors Bot pushed a commit that referenced this pull request Oct 8, 2026
Rollup merge of #156112 - Jules-Bertholet:distinguish-c-zsts, r=oli-obk

Distinguish `repr(C)` ZSTs from others in ABI computation

Implement #157973:

Some C ABIs pass and return ZSTs by pointer. But `()` should never be returned by pointer, as it must match `void`. To fix this, distinguish `repr(C)`  ZSTs (and `repr(transparent)` wrappers around them) from other ZSTs in ABI computation.

Fixes rust-lang/unsafe-code-guidelines#552.

cc @RalfJung
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ABI Area: Concerning the application binary interface (ABI) disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. I-lang-nominated Nominated for discussion during a lang team meeting. P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-lang Relevant to the language team T-libs Relevant to the library team, which will review and decide on the PR/issue. T-opsem Relevant to the opsem team to-announce Announce this issue on triage meeting waived-reference-pr This language change does not need a Reference PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Any two types with size 0 and alignment 1 are ABI-compatible" vs the Windows ABI