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

Specify #[repr(transparent)] #1758

Merged
merged 2 commits into from
Jul 3, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions text/0000-repr-transparent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
- Feature Name: `repr_transparent`
- Start Date: 2016-09-26
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary
[summary]: #summary

Extend the existing `#[repr]` attribute on newtypes with a `transparent` option
specifying that the type representation is the representation of its only field.
Copy link
Member

Choose a reason for hiding this comment

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

I find this RFC confusing, I think because I am unclear what you mean by representation. Rust uses interior allocation for structs (which while not guaranteed because of no formal ABI, you can definitely rely on). So in that respect, the in-memory representation does not change at all. But you seem to be proposing changing the how newtypes interact with FFI? It might be better to write the RFC with that focus.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#[repr(C)] can also influence how values are passed to and returned from FFI functions, notably struct Foo(f64) on ARM64.

Copy link

Choose a reason for hiding this comment

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

I think I agree with @nrc here. Unless I'm confused, this has nothing to do with 'repr' but is purely a calling convention issue.

However, if you change the calling convention like this then it is incompatible with FFI. Could you possibly give a more detailed example how you would use this in a FFI case?

This matters in FFI context where `struct Foo(T)` might not behave the same
as `T`.


# Motivation
[motivation]: #motivation

On some ABIs, structures with one field aren't handled the same way as values of
the same type as the single field. For example on ARM64, functions returning
a structure with a single `f64` field return nothing and take a pointer to be
filled with the return value, whereas functions returning a `f64` return the
floating-point number directly.
Copy link
Member

@nagisa nagisa Sep 26, 2016

Choose a reason for hiding this comment

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

I do not see why the (unspecified) Rust ABI {w,sh}ould share the same behaviour, as opposed to being “transparent” by default.

Copy link
Contributor

Choose a reason for hiding this comment

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

+, transparent seems to be relevant only for repr(C) stuff, as a tweak to the default C ABI

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"+"?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think @petrochenkov meant "and"


This means that if someone wants to wrap a `f64` value in a struct tuple
wrapper and use that wrapper as the return type of a FFI function that actually
returns a bare `f64`, the calls to this function will be compiled incorrectly
by Rust and the execution of the program will segfault.

This also means that `UnsafeCell<T>` cannot be soundly used in place of a
bare `T` in FFI context, which might be necessary to signal to the Rust side
of things that this `T` value may unexpectedly be mutated.

```c
// The value is returned directly in a floating-point register on ARM64.
double do_something_and_return_a_double(void);
```

```rust
mod bogus {
#[repr(C)]
struct FancyWrapper(f64);

extern {
// Incorrect: the wrapped value on ARM64 is indirectly returned and the
// function takes a pointer to where the return value must be stored.
fn do_something_and_return_a_double() -> FancyWrapper;
}
}

mod correct {
#[repr(transparent)]
struct FancyWrapper(f64);

extern {
// Correct: FancyWrapper is handled exactly the same as f64 on all
// platforms.
fn do_something_and_return_a_double() -> FancyWrapper;
}
}
```

Given this attribute delegates all representation concerns, no other `repr`
attribute should be present on the type. This means the following definitions
are illegal:
Copy link
Member

Choose a reason for hiding this comment

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

I thought briansmith's comment gave an example of where we would need to mix transparent and align ?

Copy link
Member

Choose a reason for hiding this comment

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

Oh maybe this example is left-over from a previous iteration of the RFC? The current detailed design does allow for alignment specifications, right?


```rust
#[repr(transparent, align = "128")]
struct BogusAlign(f64);

#[repr(transparent, packed)]
struct BogusPacked(f64);
```

# Detailed design
[design]: #detailed-design

The `#[repr]` attribute on newtypes will be extended to include a form such as:

```rust
#[repr(transparent)]
struct TransparentNewtype(f64);
```

This structure will still have the same representation as a raw `f64` value.

Syntactically, the `repr` meta list will be extended to accept a meta item
with the name "transparent". This attribute can be placed on newtypes,
i.e. structures (and structure tuples) with a single field, and on structures
that are logically equivalent to a newtype, i.e. structures with multiple fields
where only a single one of them has a non-zero size.

Some examples of `#[repr(transparent)]` are:

```rust
// Transparent struct tuple.
#[repr(transparent)]
struct TransparentStructTuple(i32);

// Transparent structure.
#[repr(transparent)]
struct TransparentStructure { only_field: f64 }

// Transparent struct wrapper with a marker.
#[repr(transparent)]
struct TransparentWrapper<T> {
only_non_zero_sized_field: f64,
marker: PhantomData<T>,
}
```

This new representation is mostly useful when the structure it is put on must be
used in FFI context as a wrapper to the underlying type without actually being
affected by any ABI semantics.
Copy link
Member

@nagisa nagisa Sep 26, 2016

Choose a reason for hiding this comment

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

Oh, so you expect to use this when passing stuff into foreign functions… Then a multiple questions arise, for example:

#[repr(transparent)] struct Transp<T>(T);

extern { fn banana(x: Transp<SomeRustType>); } // does the non-c-types lint warn? probably yes, but not specified in the RFC
extern { fn banana(x: Transp<SomeCType>); } // does the non-c-types lint warn? probably no, but not specified in the RFC

#[repr(transparent)] struct TranspPack(PackedTy);  // is packed? Probably yes.
#[repr(packed,transparent)] struct TransPack(SomeTy); // is packed? No idea.
#[repr(transparent, align="128")] struct TransU32(u32); // is valid? If so, is alignment 128 or `align_of::<u32>()`? What happens if native alignment of u32 is greater than alignment specified for the transparent newtype?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This RFC specifies that Transp<SomeRustType> should warn because it says the representation of Transp is whatever the representation of its single field is. So if the single field is not a proper C type, using it in FFI should warn.

But yeah, it should mention that no other repr should be used with it, so no align nor packed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mentioned in the RFC that these are illegal.


It is also useful for `AtomicUsize`-like types, which [RFC 1649] states should
have the same representation as their underlying types.

[RFC 1649]: https://github.com/rust-lang/rfcs/pull/1649

This new representation cannot be used with any other representation attribute
but alignment, to be able to specify a transparent wrapper with additional
alignment constraints:

```rust
#[repr(transparent, align = "128")]
struct OverAligned(f64); // Behaves as a bare f64 with 128 bits alignment.

#[repr(C, transparent)]
struct BogusRepr(f64); // Nonsensical, repr cannot be C and transparent.
```

As a matter of optimisation, eligible `#[repr(Rust)]` structs behave as if
they were `#[repr(transparent)]` but as an implementation detail that can't be
relied upon by users.

```rust
struct ImplicitlyTransparentWrapper(f64);

#[repr(C)]
struct BogusRepr {
// While ImplicitlyTransparentWrapper implicitly has the same representation
// as f64, this will fail to compile because ImplicitlyTransparentWrapper
// has no explicit transparent or C representation.
wrapper: ImplicitlyTransparentWrapper,
}
```

The representation of a transparent wrapper is the representation of its
only non-zero-sized field, transitively:

```rust
#[repr(transparent)]
struct Transparent<T>(T);

#[repr(transparent)]
struct F64(f64);

#[repr(C)]
struct C(usize);

type TransparentF64 = Transparent<F64>; // Behaves as f64.

type TransparentString = Transparent<String>; // Representation is Rust.

type TransparentC = Transparent<C>; // Representation is C.

type TransparentTransparentC = Transparent<Transparent<C>>; // Transitively C.
```

Coercions and casting between the transparent wrapper and its non-zero-sized
types are forbidden.
Copy link
Member

Choose a reason for hiding this comment

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

What about transmute? Is it explicitly always legal to transmute between a transparent structure and its representation type, or between two transparent structures that have compatible representation types?

This is relevant because there are cases where today people are relying on the repr(Rust) "optimization" that allows such transmutes to be safe, but that optimization, as noted in this RFC, is not guaranteed. The presence of this attribute might be interpreted as changing that.

However, it also might be something that can be just as well left for a later RFC.


# Drawbacks
[drawbacks]: #drawbacks

None.

# Alternatives
[alternatives]: #alternatives

The only alternative to such a construct for FFI purposes is to use the exact
same types as specified in the C header (or wherever the FFI types come from)
and to make additional wrappers for them in Rust. This does not help if a
field using interior mutability (i.e. uses `UnsafeCell<T>`) has to be passed
to the FFI side, so this alternative does not actually cover all the uses cases
allowed by `#[repr(transparent)]`.

# Unresolved questions
[unresolved]: #unresolved-questions

* None