Skip to content

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

Open
Jules-Bertholet wants to merge 8 commits into
rust-lang:mainfrom
Jules-Bertholet:distinguish-c-zst-docs
Open

Distinguish repr(C) ZSTs from others in ABI compatibility rules#157973
Jules-Bertholet wants to merge 8 commits 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

(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 on lines +1844 to +1845
/// - Any two types fulfilling all the following conditions are ABI-compatible;
/// such types are said to have "trivial ABI":

@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.

Suggested change
/// - Any two types fulfilling all the following conditions are ABI-compatible;
/// such types are said to have "trivial ABI":
/// - Any two types with "trivial ABI" are ABI-compatible.
/// A type has trivial ABI if is satisfies all of the following:

View changes since the review

@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)).

@RalfJung RalfJung added the I-lang-nominated Nominated for discussion during a lang team meeting. label Jul 2, 2026
Co-authored-by: Brian Smith <brian@briansmith.org>
Co-authored-by: Ralf Jung <post@ralfj.de>
Comment thread library/core/src/primitive_docs.rs Outdated
Co-authored-by: Ralf Jung <post@ralfj.de>
@traviscross traviscross added the P-lang-drag-2 Lang team prioritization drag level 2.https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang. label 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.

Comment on lines +1852 to +1854
/// - 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 `!`][prim_never].
/// - It is a function item type or closure type.

@Darksonn Darksonn Jul 15, 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 think this change is reasonable. We have to change these rules because they're just wrong and this is unfixable. The rules as stated seem like a reasonable way to fix it.

My main question would be: Why include arrays? I get a bit worried about that one because array arguments have weird properties in C. We don't want to run into another case in the future where Rust cannot represent certain kinds of C ABIs involving arrays.

View changes since the review

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.

See discussion thread at #157973 (comment). The "and its element type has trivial ABI" requirement should exclude array types with C equivalents.

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.

Hrm, I guess you are right that the element type having a trivial ABI requirement is probably sufficient.

@RalfJung

Copy link
Copy Markdown
Member

Let's just ask opsem officially. :)
@rfcbot merge opsem

@rust-rfcbot

This comment was marked as outdated.

@rust-rfcbot rust-rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. and removed needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. labels Jul 15, 2026
@traviscross

traviscross commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Let's poll both in parallel.

@rfcbot cancel

@rust-rfcbot

Copy link
Copy Markdown
Collaborator

@traviscross proposal cancelled.

@rust-rfcbot rust-rfcbot removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Jul 15, 2026
@traviscross

Copy link
Copy Markdown
Contributor

@rfcbot fcp merge lang,opsem

@rust-rfcbot

rust-rfcbot commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

@traviscross has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

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.
See this document for info about what commands tagged team members can give me.

@rust-rfcbot rust-rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Jul 15, 2026
@traviscross traviscross added P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang and removed P-lang-drag-2 Lang team prioritization drag level 2.https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang. labels Jul 15, 2026
@CAD97

CAD97 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

I still personally think that, given a time machine, using anything in #[repr(C)]/extern "C" that doesn't have a corresponding equivalent in the C standard should've been an error. In such a world, system would be the identifier for getting "C with the target system extensions" semantics.

That said, I do recognize practicality. Lacking a time machine, I agree that this is the correct thing to do for Rust in practice.

@rustbot reviewed

@Jules-Bertholet

Jules-Bertholet commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Does the Reference have any discussion of ABI compatibility currently?

The Reference actually delegates explicitly to these stdlib docs: https://doc.rust-lang.org/reference/type-layout.html#r-layout.guarantees

After this PR is merged, we'll need to do the follow-up work of adjusting repr(transparent), and the relevant section of the Reference will need to be modified at that point. We're not there yet, though.

@saethlin

Copy link
Copy Markdown
Member
  • The current rules say that all repr(Rust) 1-ZST have trivial ABI, without recursing further.

Isn't this PR introducing the concept of "trivial ABI"? It's worded like it does. I would like to read the rendered documentation for this file on the website but I can't find what page primitive_docs.rs is rendered as. It's definitely not std::primitive which is the only promising search result.

@Jules-Bertholet

Jules-Bertholet commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

I can't find what page primitive_docs.rs is rendered as.

As many pages, but the relevant one for this PR is https://doc.rust-lang.org/core/primitive.fn.html#abi-compatibility

I think that by "current rules", Ralf was referring to the current state of this PR.

@RalfJung

Copy link
Copy Markdown
Member

I think that by "current rules", Ralf was referring to the current state of this PR.

Yes, sorry for the confusion. The PR was changed a bit in the beginning. I have clarified my comment.

@RalfJung

RalfJung commented Jul 17, 2026

Copy link
Copy Markdown
Member

Is it correct that the only 1-ZST that don't have trivial ABI under this are things like [u8; 0] and repr(C) structs, and repr(transparent) wrappers around such types?

(I think our rules should be stated the way they are, positively, but for understanding what this changes compared to the status quo it is useful to also think of what the exceptions are.)

@Darksonn

Copy link
Copy Markdown
Member

I can't think of anything else.

Comment thread library/core/src/primitive_docs.rs Outdated
/// - 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).

@workingjubilee workingjubilee Jul 20, 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 tried to read this while asking myself why repr(packed) doesn't matter here. I know, and you know, but does every reader know? Why is it just an "additional flag"? I am inclined to think this is currently confusingly worded.

To start, it uses the term "flag", which feels a bit out-of-left field, like you're thinking about how the compiler reduces this to bitflags, but elsewhere on this page the only use of "flags" is in reference to compiler flags. We instead should talk, here, about the source syntax alone, attributes.

I think something like this would be a wording that makes it clearer that some attributes do conflict with repr(Rust), and some do not, the latter of which you referred to as "additional". If this seems like a bit much then maybe the text should just say "implicitly or explicitly" and move on, possibly with a reference to an explanation elsewhere or pushing the warning text into a later example.

Suggested change
/// - 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 `repr(Rust)` `struct`, `enum`, or `union`, regardless of its fields. This includes when a type is implicitly assigned `repr(Rust)` because no explicit `repr` attribute would conflict, such as when `repr(packed)` is treated as `repr(Rust, packed)`, as opposed to `repr(C, packed)`.

View changes since the 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.

Also, the parentheses are unnecessary if we use a sentence structure that doesn't need them.

@Jules-Bertholet Jules-Bertholet Jul 20, 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.

To start, it uses the term "flag", which feels a bit out-of-left field

Good point. Changed to "modifier", which is what the Reference uses.

Also, the parentheses are unnecessary if we use a sentence structure that doesn't need them.

I use parentheses to enclose clarifications that don't change the meaning (as opposed to normative statements). That's a valuable distinction, and parentheses are the punctuation best suited to express it.

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.

Hmm. I honestly don't really assign any such extremely specific intent to parentheses because they are used to provide "additional explanation" in various contexts and whether or not that is meaning-changing is ambiguous.

Footnotes are something that one might imagine to be non-normative and merely clarifying, yes? But the entire C programming ecosystem rests on a footnote's "clarification" being treated as normative.

I will give it another read to see if it makes sense with that, though.

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.

So, regarding parens: Mostly, it feels confusing to read something that is constantly interrupting itself. I actually prefer the next line because it finishes a thought in one sentence, then has another sentence in parentheses. While I felt that made the parentheses slightly redundant, if you think they're a useful stylistic qualifier between normative/clarifying, then that's fine. But if you can find a variant of this line that reads more like that one, it would be appreciated.

Comment thread library/core/src/primitive_docs.rs
/// - 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][prim_tuple] (regardless of its fields, and including [`()`][prim_unit]).

@workingjubilee workingjubilee Jul 20, 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.

Less parens, please.

Suggested change
/// - It is a [tuple][prim_tuple] (regardless of its fields, and including [`()`][prim_unit]).
/// - It is a [tuple][prim_tuple], including [`()`][prim_unit], regardless of its fields.

View changes since the review

@workingjubilee workingjubilee Jul 20, 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.

Rephrasing without the specific note about parens: I think reminding people that () is technically considered a tuple here is more important, thus should come first.

I initially considered suggesting just removing every "regardless of..." case and letting the absence of qualifier speak for itself. There are good reasons to not, however.

Comment thread library/core/src/primitive_docs.rs Outdated
/// `packed` representation modifiers.
/// - The enum `E` has exactly two variants.
/// - One variant has exactly one field, of type `T`.
/// - All fields of the other variant are zero-sized with 1-byte alignment.

@riking riking Jul 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Here as well, "have trivial ABI" instead of size/align

View changes since the review

@RalfJung RalfJung Jul 20, 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.

In don't think so. This is about a repr(Rust) enum, we fully make the rules there, and we can totally say that [u8; 0] will be ignored without having to worry about C compatibility.

Co-authored-by: Ralf Jung <post@ralfj.de>
@rustbot

rustbot commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

⚠️ Warning ⚠️

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. I-lang-nominated Nominated for discussion during a lang team meeting. needs-reference-pr This language change needs an approved Reference PR to proceed. P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. 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

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