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

Transparent Unions and Enums #2645

Merged
merged 27 commits into from
Apr 30, 2019
Merged

Conversation

mjbshaw
Copy link
Contributor

@mjbshaw mjbshaw commented Feb 25, 2019

Let's allow #[repr(transparent)] on unions and univariant enums that have exactly one non-zero-sized field (just like structs).

Rendered.
Tracking issue: rust-lang/rust#60405

Pre-RFC Discourse discussion on internals.rust-lang.org.

@mjbshaw mjbshaw changed the title Transparent Unions RFC Transparent Unions Feb 25, 2019
@Centril Centril added T-lang Relevant to the language team, which will review and decide on the RFC. A-repr #[repr(...)] related proposals & ideas A-unions Proposals relating to union items labels Feb 25, 2019
text/0000-transparent-unions.md Outdated Show resolved Hide resolved
text/0000-transparent-unions.md Outdated Show resolved Hide resolved
text/0000-transparent-unions.md Outdated Show resolved Hide resolved
text/0000-transparent-unions.md Outdated Show resolved Hide resolved
text/0000-transparent-unions.md Outdated Show resolved Hide resolved
text/0000-transparent-unions.md Outdated Show resolved Hide resolved
text/0000-transparent-unions.md Outdated Show resolved Hide resolved
text/0000-transparent-unions.md Outdated Show resolved Hide resolved
text/0000-transparent-unions.md Outdated Show resolved Hide resolved
text/0000-transparent-unions.md Outdated Show resolved Hide resolved
Centril and others added 18 commits February 25, 2019 06:46
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
Co-Authored-By: mjbshaw <github@mjb.io>
@mjbshaw
Copy link
Contributor Author

mjbshaw commented Feb 26, 2019

Thanks for the review, @Centril! I'm still working on getting to those last remaining items you mentioned, but in the meantime I implemented the feature (including for univariant enums). It probably needs some fixing, and definitely needs some tests, but it can serve as a starting point for what the feature might look like in real life.

@mjbshaw mjbshaw changed the title Transparent Unions Transparent Unions and Enums Feb 27, 2019
@Centril Centril added the A-enum Enum related proposals & ideas label Feb 27, 2019
@rfcbot rfcbot added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. labels Mar 20, 2019
@rfcbot
Copy link
Collaborator

rfcbot commented Mar 20, 2019

🔔 This is now entering its final comment period, as per the review above. 🔔

@gnzlbg
Copy link
Contributor

gnzlbg commented Mar 20, 2019

FWIW I consider that use case a very long stretch of our imagination about what this RFC could be useful for.

We explored many use-cases for a long time on Discord, and all of them could be solved by just guaranteeing the layout of these unions.

The Rust-Rust C FFI dynamic library case was what we invented to justify this, but you need to control Rust code at both sides to make it work, and if that's the case, why aren't you using the Rust ABI ? There might be good arguments for that, but I would prefer if this RFC was put on hold until we merge an RFC guaranteeing layout for these types first, and then see what value would this add on top of that.

@hanna-kruppe
Copy link

@gnzlbg The Rust calling convention is unstable, so the C calling convention (or others not defined by rustc) are your only choice if you want to link together two pieces of Rust compiled with different compiler versions. Most Rust types don't have a stable ABI either, so you can't use them in those interfaces, but MaybeUninit could be one of the types that work just fine there. (Of course, making is repr(C) would also achieve that, but as we discussed there will be cases where ABI transparency is desirable.)

@Centril
Copy link
Contributor

Centril commented Mar 20, 2019

There might be good arguments for that, but I would prefer if this RFC was put on hold until we merge an RFC guaranteeing layout for these types first, and then see what value would this add on top of that.

@gnzlbg Note that if/when this RFC is merged the actual implementation will go through the usual stabilization process. If you want to file an RFC you will have plenty of time to do so.

@gnzlbg
Copy link
Contributor

gnzlbg commented Mar 20, 2019

two pieces of Rust compiled with different compiler versions.

@rkruppe my point was that for the Rust-Rust dynamic lib over C FFI to work you kind of have to been in control of the Rust code on both places to make sure they make use over the right Rust types (if they only assume the code at the other side of the ABI is C then none of this applies).

That is, if you are in control of the Rust code at both sides, you can often compile it with the same compiler version. Sure, this is not always the case, but it feels like if.. if.. if.. if.. then this is useful, which is what i mean by a stretch.

@vi
Copy link

vi commented Mar 21, 2019

Shall this be allowed?:

#[repr(transparent)]
enum StaringIntoTheVoid {
   Empty(!),
   Lifeline(i32),
   DoublyNothing(!,!),
}

What is interaction between #[repr(transparent)] and the never_type?

@mjbshaw
Copy link
Contributor Author

mjbshaw commented Mar 21, 2019

@vi Interesting, I hadn't thought of that case. Currently the plan is to allow only single-variant enums to be transparent, so that wouldn't be allowed in the current plan. I think we could safely allow that in the future if desired (though I wouldn't pursue it without a good answer to how we should handle it if we changed the code to DoublyNothing(u8,!)).

@vi
Copy link

vi commented Mar 21, 2019

though I wouldn't pursue it without a good answer to how we should handle it if we changed the code to DoublyNothing(u8,!)

Obviously, the same as DoublyNothing(!,!) or std::convert::Infallible or other uninhabited type.

@Centril
Copy link
Contributor

Centril commented Mar 21, 2019

I think a consistent handling would look for singleton enums rather than "univariant" (i.e. semantic rather than syntactic approach). A semantic approach would make StaringIntoTheVoid legal. However, no one is going to realistically write StaringIntoTheVoid so I can't say I care unless consistency simplifies the definition/implementation.

@vi
Copy link

vi commented Mar 21, 2019

However, no one is going to realistically write ...

Macros and code generation may write such and other redundant things. Both #[repr(transparent)] and the uninhabited type may arrive though some macros or have associated #[cfg] trickery. Sometimes the enum would have two actual, inhabited variants, sometimes it would morph into a transparent wrapper around someting else. Using ! to delete enum's variant from within may be useful for macros/codegen.

@mjbshaw
Copy link
Contributor Author

mjbshaw commented Mar 22, 2019

My issue with enumerations like StaringIntoTheVoid are that:

I'm not opposed to these types of enums being transparent-able, but I think there are some open questions about how much work it is to do it and exactly what should and shouldn't be allowed.

@eddyb
Copy link
Member

eddyb commented Mar 29, 2019

Rust doesn't currently recognize that DoublyNothing(u8,!) can be optimized out of the type, so supporting this would require more work.

We had that optimized for a while and it was actually a bug (rust-lang/rust#49298).

@vi
Copy link

vi commented Mar 29, 2019

I assumed it is indended that a singe ! cascades though all tuples and structures, only stopping at enum variant.

Are partially initialized structures officialy OK per unsafe guidelines? Is this code guranteed?:

#![feature(maybe_uninit,never_type,maybe_uninit_ref)] 
#![allow(dead_code)]

use std::mem::MaybeUninit;

struct A { b:!, c:i32 }

fn main() {
    let mut a  : MaybeUninit<A> = MaybeUninit::uninitialized();
    unsafe { a.get_mut().c = 5; }
    println!("{}", unsafe { a.get_ref().c } );
}

If yes then it makes ! just a fancy ZST instead of actual zero type.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this RFC. and removed final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. labels Mar 30, 2019
@rfcbot
Copy link
Collaborator

rfcbot commented Mar 30, 2019

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

The RFC will be merged soon.

@gnzlbg
Copy link
Contributor

gnzlbg commented Apr 2, 2019

Note that the second example:

It is also useful for consuming pointers to uninitialized memory:

works without this feature, since it only requires layout compatibility, and not repr(transparent).

@gnzlbg
Copy link
Contributor

gnzlbg commented Apr 18, 2019

I have a dumb question, is

#[repr(transparent)] union<T> U { t: T, u: () }

ok?

What happens if I write U<()> (the union only contains ZSTs)? What ABI is picked, and how does behave in extern "C" ? Or do I get a compiler error saying that repr(transparent) unions cannot contain ZSTs ? (maybe that would be a killer for MaybeUninit ).

@hanna-kruppe
Copy link

FWIW with structs that is allowed, and S<()> just ends up being a ZST and is passed as such. That makes it not/barely suitable for C FFI, but ultimately that's up to the user (and the improper_ctypes lint). It's also not really substantially different from the same example without a second () field (i.e., just a generic newtype without any extra ZSTs), which should also be allowed.

@Centril Centril merged commit 2e1858f into rust-lang:master Apr 30, 2019
@Centril
Copy link
Contributor

Centril commented Apr 30, 2019

Huzzah! This RFC has been merged!

Tracking issue: rust-lang/rust#60405

(Please excuse the delay!)

@RalfJung
Copy link
Member

Looks like there might be a use-case after all for by-value MaybeUninit in FFI?

bors added a commit to rust-lang/rust that referenced this pull request Jan 27, 2020
…enkov

Stabilize `#[repr(transparent)]` on `enum`s in Rust 1.42.0

# Stabilization report

The following is the stabilization report for `#![feature(transparent_enums)]`.

Tracking issue: #60405
[Version target](https://forge.rust-lang.org/#current-release-versions): 1.42 (2020-01-30 => beta, 2020-03-12 => stable).

## User guide

A `struct` with only a single non-ZST field (let's call it `foo`) can be marked as `#[repr(transparent)]`. Such a `struct` has the same layout and ABI as `foo`. Here, we also extend this ability to `enum`s with only one variant, subject to the same restrictions as for the equivalent `struct`. That is, you can now write:

```rust
#[repr(transparent)]
enum Foo { Bar(u8) }
```

which, in terms of layout and ABI, is equivalent to:

```rust
#[repr(transparent)]
struct Foo(u8);
```

## Motivation

This is not a major feature that will unlock new and important use-cases. The utility of `repr(transparent)` `enum`s is indeed limited. However, there is still some value in it:

1. It provides conceptual simplification of the language in terms of treating univariant `enum`s and `struct`s the same, as both are product types. Indeed, languages like Haskell only have `data` as the only way to construct user-defined ADTs in the language.

2. In rare occasions, it might be that the user started out with a univariant `enum` for whatever reason (e.g. they thought they might extend it later). Now they want to make this `enum` `transparent` without breaking users by turning it into a `struct`. By lifting the restriction here, now they can.

## Technical specification

The reference specifies [`repr(transparent)` on a `struct`](https://doc.rust-lang.org/nightly/reference/type-layout.html#the-transparent-representation) as:

> ### The transparent Representation
>
>  The `transparent` representation can only be used on `struct`s that have:
>  - a single field with non-zero size, and
>  - any number of fields with size 0 and alignment 1 (e.g. `PhantomData<T>`).
>
> Structs with this representation have the same layout and ABI as the single non-zero sized field.
>
> This is different than the `C` representation because a struct with the `C` representation will always have the ABI of a `C` `struct` while, for example, a struct with the `transparent` representation with a primitive field will have the ABI of the primitive field.
>
> Because this representation delegates type layout to another type, it cannot be used with any other representation.

Here, we amend this to include univariant `enum`s as well with the same static restrictions and the same effects on dynamic semantics.

## Tests

All the relevant tests are adjusted in the PR diff but are recounted here:

- `src/test/ui/repr/repr-transparent.rs` checks that `repr(transparent)` on an `enum` must be univariant, rather than having zero or more than one variant. Restrictions on the fields inside the only variants, like for those on `struct`s, are also checked here.

- A number of codegen tests are provided as well:
    - `src/test/codegen/repr-transparent.rs` (the canonical test)
    - `src/test/codegen/repr-transparent-aggregates-1.rs`
    - `src/test/codegen/repr-transparent-aggregates-2.rs`
    - `src/test/codegen/repr-transparent-aggregates-3.rs`

- `src/test/ui/lint/lint-ctypes-enum.rs` tests the interactions with the `improper_ctypes` lint.

## History

- 2019-04-30, RFC rust-lang/rfcs#2645
  Author: @mjbshaw
  Reviewers: The Language Team

  This is the RFC that proposes allowing `#[repr(transparent)]` on `enum`s and `union`.

- 2019-06-11, PR #60463
  Author: @mjbshaw
  Reviewers: @varkor and @rkruppe

  The PR implements the RFC aforementioned in full.

- 2019, PR #67323
  Author: @Centril
  Reviewers: @davidtwco

  The PR reorganizes the static checks taking advantage of the fact that `struct`s and `union`s are internally represented as ADTs with a single variant.

- This PR stabilizes `transparent_enums`.

## Related / possible future work

The remaining work here is to figure out the semantics of `#[repr(transparent)]` on `union`s and stabilize those. This work continues to be tracked in #60405.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-enum Enum related proposals & ideas A-ffi FFI related proposals. A-repr #[repr(...)] related proposals & ideas A-unions Proposals relating to union items disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this RFC. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.