Skip to content

Commit

Permalink
Implement RFC 2645 (transparent enums and unions)
Browse files Browse the repository at this point in the history
Tracking issue: #60405
  • Loading branch information
mjbshaw committed Jun 11, 2019
1 parent 02564de commit dac1c6a
Show file tree
Hide file tree
Showing 34 changed files with 730 additions and 208 deletions.
93 changes: 93 additions & 0 deletions src/doc/unstable-book/src/language-features/transparent-enums.md
@@ -0,0 +1,93 @@
# `transparent_enums`

The tracking issue for this feature is [#60405]

[60405]: https://github.com/rust-lang/rust/issues/60405

----

The `transparent_enums` feature allows you mark `enum`s as
`#[repr(transparent)]`. An `enum` may be `#[repr(transparent)]` if it has
exactly one variant, and that variant matches the same conditions which `struct`
requires for transparency. Some concrete illustrations follow.

```rust
#![feature(transparent_enums)]

// This enum has the same representation as `f32`.
#[repr(transparent)]
enum SingleFieldEnum {
Variant(f32)
}

// This enum has the same representation as `usize`.
#[repr(transparent)]
enum MultiFieldEnum {
Variant { field: usize, nothing: () },
}
```

For consistency with transparent `struct`s, `enum`s must have exactly one
non-zero-sized field. If all fields are zero-sized, the `enum` must not be
`#[repr(transparent)]`:

```rust
#![feature(transparent_enums)]

// This (non-transparent) enum is already valid in stable Rust:
pub enum GoodEnum {
Nothing,
}

// Error: transparent enum needs exactly one non-zero-sized field, but has 0
// #[repr(transparent)]
// pub enum BadEnum {
// Nothing(()),
// }

// Error: transparent enum needs exactly one non-zero-sized field, but has 0
// #[repr(transparent)]
// pub enum BadEmptyEnum {
// Nothing,
// }
```

The one exception is if the `enum` is generic over `T` and has a field of type
`T`, it may be `#[repr(transparent)]` even if `T` is a zero-sized type:

```rust
#![feature(transparent_enums)]

// This enum has the same representation as `T`.
#[repr(transparent)]
pub enum GenericEnum<T> {
Variant(T, ()),
}

// This is okay even though `()` is a zero-sized type.
pub const THIS_IS_OKAY: GenericEnum<()> = GenericEnum::Variant((), ());
```

Transparent `enum`s require exactly one variant:

```rust
// Error: transparent enum needs exactly one variant, but has 0
// #[repr(transparent)]
// pub enum TooFewVariants {
// }

// Error: transparent enum needs exactly one variant, but has 2
// #[repr(transparent)]
// pub enum TooManyVariants {
// First(usize),
// Second,
// }
```

Like transarent `struct`s, a transparent `enum` of type `E` has the same layout,
size, and ABI as its single non-ZST field. If it is generic over a type `T`, and
all its fields are ZSTs except for exactly one field of type `T`, then it has
the same layout and ABI as `T` (even if `T` is a ZST when monomorphized).

Like transparent `struct`s, transparent `enum`s are FFI-safe if and only if
their underlying representation type is also FFI-safe.
83 changes: 83 additions & 0 deletions src/doc/unstable-book/src/language-features/transparent-unions.md
@@ -0,0 +1,83 @@
# `transparent_unions`

The tracking issue for this feature is [#60405]

[60405]: https://github.com/rust-lang/rust/issues/60405

----

The `transparent_unions` feature allows you mark `union`s as
`#[repr(transparent)]`. A `union` may be `#[repr(transparent)]` in exactly the
same conditions in which a `struct` may be `#[repr(transparent)]` (generally,
this means the `union` must have exactly one non-zero-sized field). Some
concrete illustrations follow.

```rust
#![feature(transparent_unions)]

// This union has the same representation as `f32`.
#[repr(transparent)]
union SingleFieldUnion {
field: f32,
}

// This union has the same representation as `usize`.
#[repr(transparent)]
union MultiFieldUnion {
field: usize,
nothing: (),
}
```

For consistency with transparent `struct`s, `union`s must have exactly one
non-zero-sized field. If all fields are zero-sized, the `union` must not be
`#[repr(transparent)]`:

```rust
#![feature(transparent_unions)]

// This (non-transparent) union is already valid in stable Rust:
pub union GoodUnion {
pub nothing: (),
}

// Error: transparent union needs exactly one non-zero-sized field, but has 0
// #[repr(transparent)]
// pub union BadUnion {
// pub nothing: (),
// }
```

The one exception is if the `union` is generic over `T` and has a field of type
`T`, it may be `#[repr(transparent)]` even if `T` is a zero-sized type:

```rust
#![feature(transparent_unions)]

// This union has the same representation as `T`.
#[repr(transparent)]
pub union GenericUnion<T: Copy> { // Unions with non-`Copy` fields are unstable.
pub field: T,
pub nothing: (),
}

// This is okay even though `()` is a zero-sized type.
pub const THIS_IS_OKAY: GenericUnion<()> = GenericUnion { field: () };
```

Like transarent `struct`s, a transparent `union` of type `U` has the same
layout, size, and ABI as its single non-ZST field. If it is generic over a type
`T`, and all its fields are ZSTs except for exactly one field of type `T`, then
it has the same layout and ABI as `T` (even if `T` is a ZST when monomorphized).

Like transparent `struct`s, transparent `union`s are FFI-safe if and only if
their underlying representation type is also FFI-safe.

A `union` may not be eligible for the same nonnull-style optimizations that a
`struct` or `enum` (with the same fields) are eligible for. Adding
`#[repr(transparent)]` to `union` does not change this. To give a more concrete
example, it is unspecified whether `size_of::<T>()` is equal to
`size_of::<Option<T>>()`, where `T` is a `union` (regardless of whether or not
it is transparent). The Rust compiler is free to perform this optimization if
possible, but is not required to, and different compiler versions may differ in
their application of these optimizations.
22 changes: 9 additions & 13 deletions src/librustc/hir/check_attr.rs
Expand Up @@ -181,12 +181,9 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
let (article, allowed_targets) = match hint.name_or_empty() {
name @ sym::C | name @ sym::align => {
is_c |= name == sym::C;
if target != Target::Struct &&
target != Target::Union &&
target != Target::Enum {
("a", "struct, enum or union")
} else {
continue
match target {
Target::Struct | Target::Union | Target::Enum => continue,
_ => ("a", "struct, enum, or union"),
}
}
sym::packed => {
Expand All @@ -207,10 +204,9 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
}
sym::transparent => {
is_transparent = true;
if target != Target::Struct {
("a", "struct")
} else {
continue
match target {
Target::Struct | Target::Union | Target::Enum => continue,
_ => ("a", "struct, enum, or union"),
}
}
sym::i8 | sym::u8 | sym::i16 | sym::u16 |
Expand Down Expand Up @@ -241,7 +237,7 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
if is_transparent && hints.len() > 1 {
let hint_spans: Vec<_> = hint_spans.clone().collect();
span_err!(self.tcx.sess, hint_spans, E0692,
"transparent struct cannot have other repr hints");
"transparent {} cannot have other repr hints", target);
}
// Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
if (int_reprs > 1)
Expand Down Expand Up @@ -277,7 +273,7 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
attr.span,
stmt.span,
"attribute should not be applied to a statement",
"not a struct, enum or union",
"not a struct, enum, or union",
);
}
}
Expand All @@ -298,7 +294,7 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
attr.span,
expr.span,
"attribute should not be applied to an expression",
"not defining a struct, enum or union",
"not defining a struct, enum, or union",
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/ty/mod.rs
Expand Up @@ -2303,7 +2303,7 @@ impl<'a, 'gcx, 'tcx> AdtDef {
/// Returns an iterator over all fields contained
/// by this ADT.
#[inline]
pub fn all_fields<'s>(&'s self) -> impl Iterator<Item = &'s FieldDef> {
pub fn all_fields<'s>(&'s self) -> impl Iterator<Item = &'s FieldDef> + Clone {
self.variants.iter().flat_map(|v| v.fields.iter())
}

Expand Down
34 changes: 23 additions & 11 deletions src/librustc_lint/types.rs
Expand Up @@ -531,8 +531,8 @@ fn ty_is_known_nonnull<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, ty: Ty<'tcx>) -> b
match ty.sty {
ty::FnPtr(_) => true,
ty::Ref(..) => true,
ty::Adt(field_def, substs) if field_def.repr.transparent() && field_def.is_struct() => {
for field in &field_def.non_enum_variant().fields {
ty::Adt(field_def, substs) if field_def.repr.transparent() && !field_def.is_union() => {
for field in field_def.all_fields() {
let field_ty = tcx.normalize_erasing_regions(
ParamEnv::reveal_all(),
field.ty(tcx, substs),
Expand Down Expand Up @@ -627,8 +627,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
return FfiUnsafe {
ty: ty,
reason: "this struct has unspecified layout",
help: Some("consider adding a #[repr(C)] or #[repr(transparent)] \
attribute to this struct"),
help: Some("consider adding a `#[repr(C)]` or \
`#[repr(transparent)]` attribute to this struct"),
};
}

Expand Down Expand Up @@ -668,11 +668,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
if all_phantom { FfiPhantom(ty) } else { FfiSafe }
}
AdtKind::Union => {
if !def.repr.c() {
if !def.repr.c() && !def.repr.transparent() {
return FfiUnsafe {
ty: ty,
reason: "this union has unspecified layout",
help: Some("consider adding a #[repr(C)] attribute to this union"),
help: Some("consider adding a `#[repr(C)]` or \
`#[repr(transparent)]` attribute to this union"),
};
}

Expand All @@ -690,6 +691,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
ParamEnv::reveal_all(),
field.ty(cx, substs),
);
// repr(transparent) types are allowed to have arbitrary ZSTs, not just
// PhantomData -- skip checking all ZST fields.
if def.repr.transparent() && is_zst(cx, field.did, field_ty) {
continue;
}
let r = self.check_type_for_ffi(cache, field_ty);
match r {
FfiSafe => {
Expand All @@ -712,26 +718,32 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {

// Check for a repr() attribute to specify the size of the
// discriminant.
if !def.repr.c() && def.repr.int.is_none() {
if !def.repr.c() && !def.repr.transparent() && def.repr.int.is_none() {
// Special-case types like `Option<extern fn()>`.
if !is_repr_nullable_ptr(cx, ty, def, substs) {
return FfiUnsafe {
ty: ty,
reason: "enum has no representation hint",
help: Some("consider adding a #[repr(...)] attribute \
to this enum"),
help: Some("consider adding a `#[repr(C)]`, \
`#[repr(transparent)]`, or integer `#[repr(...)]` \
attribute to this enum"),
};
}
}

// Check the contained variants.
for variant in &def.variants {
for field in &variant.fields {
let arg = cx.normalize_erasing_regions(
let field_ty = cx.normalize_erasing_regions(
ParamEnv::reveal_all(),
field.ty(cx, substs),
);
let r = self.check_type_for_ffi(cache, arg);
// repr(transparent) types are allowed to have arbitrary ZSTs, not
// just PhantomData -- skip checking all ZST fields.
if def.repr.transparent() && is_zst(cx, field.did, field_ty) {
continue;
}
let r = self.check_type_for_ffi(cache, field_ty);
match r {
FfiSafe => {}
FfiUnsafe { .. } => {
Expand Down

0 comments on commit dac1c6a

Please sign in to comment.