Add an extended example to the transparent discussion

pull/263/head
Mark R. Tuttle 4 years ago
parent 8551afbb2c
commit 21ac4fb0ce

@ -75,7 +75,106 @@ Foo(f32)` to always have the same ABI as `f32`.
More details are in the [RFC][rfc-transparent].
As an extended example, it is instructive to think about transparent types
in terms of wrappers.
In Rust we can define an integer type three ways
```
struct Wrapper1(u32);
struct Wrapper2 {
value: u32
}
fn main() {
let x:u32 = 4;
let y:Wrapper1 = Wrapper1(4);
let z:Wrapper2 = Wrapper2{ value: 4 };
}
```
This defines an integer, a tuple with a single member, and a struct
with a single field. They are three different types, but they are all
represented by four bytes in memory representing the value 4. They
all represent the value 4, but they are not 4. In particular, we can
write `x+1` but we cannot write `y+1` or `z+1`.
There are two reasons you might want to use a wrapper:
* New types: You can use wrappers `Vec1Index` and `Vec2Index` around
integers and create types `Vec1` and `Vec2` that can be indexed by
`Vec1Index(4)` and `Vec2Index(4)` respectively but not vice versa.
The wrappers `Vec1Index(4)` and `Vec2Index(4)` around the integer `4`
define new, distinct types even though the underlying representation
is just four bytes representing the integer `4`.
* New assertions: You can use a wrapper like `Positive` around an
integer to indicate to the user that the integer is guaranteed to be
positive. Just ensure that `Positive::new(4)` succeeds and
`Postive::new(0)` panics, so the value wrapped by `Positive` is
guaranteed to be positive.
Again, the integer wrappers above are all respresented by four bytes.
The method of accessing the four bytes, however, differ. We can write
`x+1` and not `z+1`, but we can write `z.value+1`.
Rust lets us annotate wrappers with the "transparent" attribute. (Rust
allows the wrappers to have additional zero-length fields like phantom
data, but let's ignore that for now).
See the
[Transparent RFC](https://rust-lang.github.io/rfcs/1758-repr-transparent.html) for
an excellent discussion.
The purpose of the transparent attribute on an integer wrapper is to
allow us to silently transmute between the integer and the integer
wrapper. It is a mistake to read the documentation as saying that the
wrapper just disappears when it is declared transparent. Even if we
make `Wrapper1` and `Wrapper2` above transparent, that doesn't mean that
in Rust code we can treat them like ordinary integers. We still have to
use tuple or field selection to access the underlying integer, even
though they have the same underlying representation.
The place where transparent becomes interesting is in a foreign
function interface (FFI).
There are two reasons you might want to declare a wrapper to be
transparent in the conext of an FFI:
* You might want to give an assurance or a warning about the return
value from a function. Imagine C function that returns a `u32` as a
return value `rv`. You might want to wrap `rv` in a wrapper
`Postive(rv)` or `Warning(rv)` because you know that `rv` is positive
or you know there might be something dangerous about `rv` that a user
should be forced to check. If we declare the wrapper to be
transparent, then the FFI is allowed to silently transmute the `u32`
return value `rv` into `Positive(rv)` or `Warning(rv)`.
* You might be compiling to an architecture that does not allow
functions to return structs, so a function fundamentally cannot return
a tuple or struct wrapper. The original motivation for transparent
appears to be the ARM architecture where a struct value is returned by
passing the function a pointer to the destination struct, and the
function fills in the struct via the pointer. This is true even for a
wrapper struct like ours that contains only a single integer field,
and even if the function is fully capable of returning the integer
value intended to be contained in that field.
It is this second case and discussion in the literature of "handling"
the integer wrapper as if it were just an integer that can lead to
confusion.
What transparent really means is that the underlying representation of
the wrapper is the same as the underlying representation of the wrapped
value. But this is often naturally true. An u32 and a u32 struct
wrapper are both going to consume four bytes. A struct containing a u32
struct wrapper as a substruct is going to allocate four bytes for the
u32 struct wrapper just as if would allocate four bytes for the u32 if
the u32 struct wrapper were replaced by the u32 itself.
But the struct containing the u32 wrapper and the
struct containing the u32 are different structs with different types,
even though the underlying representations are the same.
The method of accessing the u32 value is different: access the wrapped u32
with struct.member.submember versus access the u32 with struct.member.
# repr(u*), repr(i*)

Loading…
Cancel
Save