Repository navigation
Tracking issue for RFC 2645, "Transparent Unions" (formerly: and Enums) #60405
Description
This is a tracking issue for the RFC "Transparent Unions and Enums" (rust-lang/rfcs#2645).
Steps:
- Implement the RFC (cc @rust-lang/compiler -- can anyone write up mentoring instructions?)
- Adjust documentation (see instructions on forge)
- Stabilization PR (see instructions on forge) [done for enums]
Unresolved questions:
The role of
#[repr(transparent)]in nonnull-style optimizations is not entirely clear. Specifically, it is unclear whether the user can rely on these optimizations to be performed when they make a type transparent. Transparentunions somewhat complicate the matter. General concensus seems to be that the compiler is free to decide where and when to perform nonnull-style optimizations onunions (regardless of whether or not theunionis transaprent), and no guarantees are made to the user about when and if those optimizations will be applied. It is still an open question exactly what guarantees (if any) Rust makes about transparentstructs (andenums) and nonnull-style optimizations.This RFC doesn't propose any changes to transparent
structs, and so does not strictly depend on this question being resolved. But since this RFC is attempting to round out the#[repr(transparent)]feature, it seems reasonable to dedicate some time to attempting to round out the guarantees about#[repr(transparent)]onstructs.
Also it is not clear if transparent unions can even be implemented on LLVM without seriously restricting our semantics for unions overall.
Activity
mjbshaw commentedon Apr 30, 2019
I plan on attempting to implement the feature by adapting my previous implementation. I'd appreciate it if someone could take a look at that commit and let me know if it's horribly wrong :)
Centril commentedon Apr 30, 2019
@pnkfelix Up for reviewing ^--- ?
Auto merge of #60463 - mjbshaw:transparent, r=varkor,rkruppe
67 remaining items
hanna-kruppe commentedon May 7, 2020
Since repr(Rust) unions have no ABI commitments, we can just represent them as a literal byte array. That allows each byte to be poison-or-not independently.
mjbshaw commentedon May 7, 2020
@hanna-kruppe That's true, but I'm not sure it's useful to allow such granularity for poison because you still can't read
f. You can only memcpy the non-poison bytes off, which I'm not sure Rust should guarantee you can do because it's such a big footgun (and I can't think of any real value it provides).hanna-kruppe commentedon May 7, 2020
That goes more into your second question (why/how this observation matters), which I deliberately didn't go into because it's a more complex topic and my time is currently very limited.
RalfJung commentedon May 7, 2020
The Rust semantics as they exist in my head and as it is drafted here would guarantee that this program prints 0. This is what "unions are just bags of bytes" means.
But it turns out LLVM actually says "nope this is UB as all of
u2is poisoned". So, the desired semantics is not implementable with LLVM while upholdingrepr(transparent)guarantees.Of course we could try to adjust our semantics, but (a) that will make the semantics of unions significantly more complicated, and (b) it seems like a shame that LLVM would force us to cripple our semantics like that, for no good reason. If LLVM's type system was not quite so restrictive, we could just tell LLVM to load 4 bytes at once and preserve which byte is
poisonand which is not. But LLVM conflates "bytes" with "integers that have arithmetic operations", and then a lot of sadness ensues.mjbshaw commentedon May 8, 2020
Thanks for the demo, @RalfJung. I personally think this should have the same behavior as
#[repr(transparent)] struct S { f: u32 }. I'll take some time to read through the draft you linked to, though.RalfJung commentedon May 8, 2020
@mjbshaw do you still think that if we replace
UbyMaybeUninit<u32>? SinceMaybeUninitisrepr(transparent), that's basically the same type.alercah commentedon Jul 13, 2022
Doesn't writing
MaybeUninit::uninit()still write undef and not poison? Unless I'm quite mistaken, in this playground, it'sundefand notpoisonthat gets passed towrite_to_float. And since undef is tracked bitwise, this all works out doesn't it?Of course, if LLVM does change uninitialized memory to poison, then we would run into this problem, without bitwise semantics.
RalfJung commentedon Jul 14, 2022
Indeed, this was written assuming LLVM would switch to
poisoneventually.Cc #94428
alercah commentedon Oct 8, 2022
Is the concern in this comment about poison spreading to entire union fields not equally valid for
#[repr(C)]unions that must be passed as integers per platform ABIs?RalfJung commentedon Oct 8, 2022
Yeah sure, that's the same situation.
Judging from this discussion, it seems like LLVM will get a "freezing load" operation. Whenever we have a load that would allow partially uninit data, we could use the freezing load and be sure that the data is preserved correctly. This does lose some information, but at least it would resolve the concern about spreading poison to neighboring bytes.
clarfonthey commentedon Dec 3, 2022
So, poking around the various open issues and PRs and this looks like the best place to mention this.
I mentioned in #101179 that I think that allowing DSTs in
MaybeUninitmight be a way to improve the API for slices (by allowingMaybeUninit<[T]>), but it appears that DSTs are still not allowed in unions, even#[repr(transparent)]ones likeMaybeUninit. The closest this was to being implemented was #47650 which got closed because the author didn't have time to finish it.Was there ever an explicit reason to disallow this, or was it just not implemented/considered since no one had discussed it much?
RalfJung commentedon Dec 3, 2022
The problem is custom DST. If
CStrbecomes a DST, thensize_of_valmight have to actually read the data behind the reference to determine the length of the value -- but clearly with aMaybeUninit<CStr>that would be bad.clarfonthey commentedon Dec 4, 2022
Ah, right -- we haven't fully eliminated the possibility of thin DSTs. I guess that we're still unsure what a proper custom DST RFC would look like, although my guess is that
CStrwill be a thorn in the side for most of them. C strings really are the worst. (I say this sarcastically, although I'm not surprised to find another reason why they're problematic.)My gut feeling is to say that any RFC which would permit thin DSTs (which, as demonstrated by
CStr, would be something we want) is that we'd also want a mechanism to filter them out, precisely for cases like this. But I guess that this isn't a strong enough argument to be able to simply allow them now and deal with the consequences later.