You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
"Any two types with size 0 and alignment 1 are ABI-compatible" vs the Windows ABI #552
So apparently ZST return types are actually returned via a return pointer on windows-gnu targets -- except of course when the return type is (). At the same time we promise "Any two types with size 0 and alignment 1 are ABI-compatible". These two things are in direct contradiction with each other. We have to either add something like "except on platforms with a ridiculous ABI, such as windows, then you are just on your own" to the ABI docs, or decide we do not follow the likely accidental ad-hoc ABI of windows-gnu, or emit a hard error for types that the ABI simply does not support.
Note that the reason for this rule of any two 1-ZST being ABI compatible is that for types like #[repr(transparent)] ZST([u8; 0]; ()), we promise that the type is ABI-compatible with the field that is being transparently wrapped, and that could be either field. So the only way to always deliver on our repr(transparent) ABI-compatibility promise is to make all 1-ZST ABI-compatible.
changed the title [-]"Any two types with size 0 and alignment 1 are ABI-compatible" vs the Windows API[/-][+]"Any two types with size 0 and alignment 1 are ABI-compatible" vs the Windows ABI[/+]on Jan 10, 2025
could we make the function return type syntax meaningful here? so extern "win64" fn() means no sret pointer but extern "win64" fn() -> () means there is a sret pointer since the return type is explicit? or is this too big of a change?
extern "win64" fn() is an alias for extern "win64" fn() -> () by definition:
yeah, I was proposing thinking about changing that...
another probably better option would be to have #[repr(transparent)] always pick a particular field to duplicate the ABI of, e.g. it could pick the generic field, or if there isn't one it'd pick an annotated field or the first field, and a future edition could require ambiguous #[repr(transparent)] structs to annotate one field.
that way you'd have the following ABI lowering:
My understanding is that ZSTs are a non-standard extension to C by clang/gcc, hence why they don't exist under MSVC.
In other words, we don't necessarily have to follow the same ABI - our ABI would be "just as correct" for windows. The question is whether that would cause too many issues for interop with C.
We could have an ABI-modifying attribute to apply at a function level which makes the return value be returned via pointer, to handle that edgecase.
@rust-lang/lang is there any appetite for a post-monomorphization lint or error rejecting argument types that are not supported by a particular ABI? The context here is passing ZST across extern "C" on Windows targets; MSVC does not have such types so there is no official ABI and the de-facto ABI implemented by gcc and clang is... odd.
This could be seen as similar to the lint that we already have rejecting SIMD vectors passed via extern "C" without the required target features.
We could have an ABI-modifying attribute to apply at a function level which makes the return value be returned via pointer, to handle that edgecase.
This would have to be a separate extern "ABI" string, since otherwise function pointers are unsound.
And also to have it reject any #[repr(Rust)] type for which we don't guarantee a specific ABI.
In the interest of minimizing breaking changes, I'd like to make a difference between "we don't guarantee this type's ABI" and "this type just can't be passed via this ABI".
Please don't expand the scope of this issue further than is absolutely necessary.
I think the answer ought to be just "All return 1-ZSTs match c void" (not the same as c_void), and we just don't match C ZSTs in return position on windows-gnu.
Although, now that I think about it, we should check other ABIs.
Something tells me, aggregate ZSTs might be returned in memory on some other platforms (ARM32 maybe)?
I think there's a more general problem here where Rust is making guarantees that are not strictly within its remit.
A platform's C ABI can be arbitrarily "odd", and yet we would still want to be able to interoperate with it via extern "C" functions.
Can we relax the guarantees we're making in cases where it's outside our remit. For example, #[repr(transparent)] would still guarantee that the representation of the outer vs inner types in memory and in extern "Rust" functions is identical, but would only be "best effort" as far as how the type is passed to or returned from an extern "C" function.
At monomorphisation time we should check that an extern "C" function signature unambiguously corresponds to a valid C function definition for the target architecture.
We would return an error or warning if either of the following:
There are multiple "equally correct" translations to C which could be valid, with different ABIs. This would occur on windows-gnu for a function that tries to return the following type:
BTW, if 1-ZSTs don't all have the same ABI, what's the ABI of the following?
#[repr(transparent)]structTransparent6;
I would expect Transparent61 would be ABI-compatible with () (unit), since the Reference currently states that, for #[repr(transparent)]
Structs and enums with this representation have the same layout and ABI as the only non-size 0 non-alignment 1 field, if present, or unit otherwise.
(source, emphasis mine). However, this does give the wrinkle that, by a strict reading, if there are 1-ZSTs that are not ABI-compatible with () (say Other1Zst), then #[repr(transparent)] struct Bar(Other1Zst); is ABI-compatible with () and not with Other1Zst.2
The meaning for #[repr(transparent)] on a struct without a non-1-ZST field was T-lang FCP'd (here), but IIUC under the assumption that all 1-ZSTs are ABI-compatible: "[...] The real question here is whether there exists some "special" 1-ZST that would be treated differently by the ABI than any other 1-ZST. I do not believe that is the case, which is why () is kind of "as good as any other". [...]" (src).
Footnotes
and any #[repr(transparent)] struct with no non-1-ZST field ↩
Would #[repr(transparent)] struct _Bar<T>(T); type Bar = _Bar<Other1Zst>; be ABI-compatible with Other1Zst or () in that case? I assume Other1Zst since AFAICT we treat generic types "as if" they are non-1-ZST, but that is a fragile assumption. ↩
Activity
[-]"Any two types with size 0 and alignment 1 are ABI-compatible" vs the Windows API[/-][+]"Any two types with size 0 and alignment 1 are ABI-compatible" vs the Windows ABI[/+]programmerjake commentedon Jan 10, 2025
could we make the function return type syntax meaningful here? so
extern "win64" fn()means nosretpointer butextern "win64" fn() -> ()means there is asretpointer since the return type is explicit? or is this too big of a change?bjorn3 commentedon Jan 10, 2025
extern "win64" fn()is an alias forextern "win64" fn() -> ()by definition:https://doc.rust-lang.org/stable/reference/items/functions.html#r-items.fn.implicit-return.
Also
extern "win64" fn foo() -> ()is the literal translation ofvoid foo(void)on windows.extern "win64" fn foo()is just a convenience shorthand.programmerjake commentedon Jan 10, 2025
yeah, I was proposing thinking about changing that...
another probably better option would be to have
#[repr(transparent)]always pick a particular field to duplicate the ABI of, e.g. it could pick the generic field, or if there isn't one it'd pick an annotated field or the first field, and a future edition could require ambiguous#[repr(transparent)]structs to annotate one field.that way you'd have the following ABI lowering:
RalfJung commentedon Jan 10, 2025
I don't think the quirks of this... special... ABI should affect what we guarantee for other, more reasonable, ABIs.
Diggsey commentedon Jan 10, 2025
My understanding is that ZSTs are a non-standard extension to C by clang/gcc, hence why they don't exist under MSVC.
In other words, we don't necessarily have to follow the same ABI - our ABI would be "just as correct" for windows. The question is whether that would cause too many issues for interop with C.
We could have an ABI-modifying attribute to apply at a function level which makes the return value be returned via pointer, to handle that edgecase.
RalfJung commentedon Jan 10, 2025
@rust-lang/lang is there any appetite for a post-monomorphization lint or error rejecting argument types that are not supported by a particular ABI? The context here is passing ZST across
extern "C"on Windows targets; MSVC does not have such types so there is no official ABI and the de-facto ABI implemented by gcc and clang is... odd.This could be seen as similar to the lint that we already have rejecting SIMD vectors passed via
extern "C"without the required target features.This would have to be a separate
extern "ABI"string, since otherwise function pointers are unsound.bjorn3 commentedon Jan 10, 2025
I would love to have that. And also to have it reject any
#[repr(Rust)]type for which we don't guarantee a specific ABI.RalfJung commentedon Jan 10, 2025
In the interest of minimizing breaking changes, I'd like to make a difference between "we don't guarantee this type's ABI" and "this type just can't be passed via this ABI".
Please don't expand the scope of this issue further than is absolutely necessary.
chorman0773 commentedon Jan 10, 2025
I think the answer ought to be just "All return 1-ZSTs match c
void" (not the same asc_void), and we just don't match C ZSTs in return position on windows-gnu.chorman0773 commentedon Jan 10, 2025
Although, now that I think about it, we should check other ABIs.
Something tells me, aggregate ZSTs might be returned in memory on some other platforms (ARM32 maybe)?
Diggsey commentedon Jan 11, 2025
I think there's a more general problem here where Rust is making guarantees that are not strictly within its remit.
A platform's C ABI can be arbitrarily "odd", and yet we would still want to be able to interoperate with it via
extern "C"functions.Can we relax the guarantees we're making in cases where it's outside our remit. For example,
#[repr(transparent)]would still guarantee that the representation of the outer vs inner types in memory and inextern "Rust"functions is identical, but would only be "best effort" as far as how the type is passed to or returned from anextern "C"function.At monomorphisation time we should check that an
extern "C"function signature unambiguously corresponds to a valid C function definition for the target architecture.We would return an error or warning if either of the following:
There are multiple "equally correct" translations to C which could be valid, with different ABIs. This would occur on
windows-gnufor a function that tries to return the following type:There is no "correct" translation to C. This would occur on
windows-msvcfor the following type:ie. we should avoid "extending" the ABI, since it's not within our remit to do so and could cause backwards compatibility problems in future.
(These are just examples, but there might be other cases)
chorman0773 commentedon Jan 11, 2025
BTW, if 1-ZSTs don't all have the same ABI, what's the ABI of the following?
Lokathor commentedon Jan 11, 2025
If there isn't a single correct answer we should be at least warning about this during compilation, if not deny by default.
zachs18 commentedon Jan 12, 2025
I would expect
Transparent61 would be ABI-compatible with()(unit), since the Reference currently states that, for#[repr(transparent)](source, emphasis mine). However, this does give the wrinkle that, by a strict reading, if there are 1-ZSTs that are not ABI-compatible with
()(sayOther1Zst), then#[repr(transparent)] struct Bar(Other1Zst);is ABI-compatible with()and not withOther1Zst.2The meaning for
#[repr(transparent)]on a struct without a non-1-ZST field was T-lang FCP'd (here), but IIUC under the assumption that all 1-ZSTs are ABI-compatible: "[...] The real question here is whether there exists some "special" 1-ZST that would be treated differently by the ABI than any other 1-ZST. I do not believe that is the case, which is why()is kind of "as good as any other". [...]" (src).Footnotes
and any
#[repr(transparent)]struct with no non-1-ZST field ↩Would
#[repr(transparent)] struct _Bar<T>(T); type Bar = _Bar<Other1Zst>;be ABI-compatible withOther1Zstor()in that case? I assumeOther1Zstsince AFAICT we treat generic types "as if" they are non-1-ZST, but that is a fragile assumption. ↩33 remaining items
Jules-Bertholet commentedon Jun 22, 2026
Should this have been closed? Until rust-lang/rust#157973 and rust-lang/rust#156112 are merged, the issue still exists
CAD97 commentedon Jun 23, 2026
Those seem relevant enough to keep this open, I think.
Rollup merge of rust-lang#157973 - Jules-Bertholet:distinguish-c-zst-…
Unrolled build for #157973 in rollup 163680
Rollup merge of #157973 - Jules-Bertholet:distinguish-c-zst-docs, r=R…
Rollup merge of #157973 - Jules-Bertholet:distinguish-c-zst-docs, r=R…
Rollup merge of #157973 - Jules-Bertholet:distinguish-c-zst-docs, r=R…
Rollup merge of rust-lang#156112 - Jules-Bertholet:distinguish-c-zsts…
Rollup merge of rust-lang#156112 - Jules-Bertholet:distinguish-c-zsts…
Unrolled build for #156112 in rollup 164005
RalfJung commentedon Oct 9, 2026
Fixed by rust-lang/rust#156112 :)