Add `repr(inherit($type))`

Before writing a pre-RFC, I want to see if there are any fundamental problems with this idea.

Introduce a new repr variant: #[repr(inherit($type))].

This representation ensures that the annotated type has the same layout as $type. This allows safe transmutations between the two types.

Example:

struct Foo {
    a: MaybeUninit<u8>,
    b: MaybeUninit<u32>,
}

#[repr(inherit(Foo))]
struct Bar {
    b: u32,
    a: u8,
}

Without repr(inherit), we would need to use #[repr(C)] (because otherwise Foo and Bar are not guaranteed to have compatible layout) and we would also need to ensure that the order of the fields is the same. When many structs with many fields (potentially with #[cfg(...)]) are involved this is error-prone. It also requires the programmer to think about the effects that the order has on the layout/size of the type.

Here are some more examples of how it interacts with some other features.

#[repr(C)]
struct Foo {
    a: u8,
    b: u32,
}

#[repr(C)] // you are allowed to mention other reprs, if they are compatible
#[repr(inherit(Foo))]
struct Foo1 {
    a: u8,
    b: u32,
}

#[repr(C)]
#[repr(inherit(Foo))]
struct Foo2 {
    b: u32,
    a: u8,
//  ^ error fields in the wrong order
// this only occurs, because we explicitly specified `#[repr(C)]`
}

#[repr(inherit(Foo))]
#[repr(inherit(Foo1))]
// only without error if they are compatible
// maybe allow `#[repr(inherit(Foo, Foo1))]`
struct Foo3 {
    b: u32,
    a: u8,
}

A more elaborate example, why this is needed:

I want to create a library that enables safe pinned initialization (see my older post). The core idea is to prevent misuse of uninitialized data with the type system (you cannot use FooUninit in a place where you need Foo). But when you want to initialize pinned data, then you need to transmute the pointer to the correct type. This is why the two types (Foo and FooUninit) need to have the same layout. At the moment I am using #[repr(C)] and the same order of fields, this works, but is not ideal. Layout optimizations are not in effect and a user cannot specify #[repr(transparent)], because I need #[repr(C)]. Here some code that would be generated by the use of my macro library:

#[repr(C)]
struct FooUninit {
    ptr: MaybeUninit<*const i32>,
    data: [i32; 64],
    bar: BarUninit,
}

#[repr(C)]
struct Foo {
    ptr: *const i32,
    data: [i32; 64],
    bar: Bar,
}

(I am currently ignoring the problem of mutable aliasing)

This might also work well together with rfcs/2835-project-safe-transmute.md at master · rust-lang/rfcs · GitHub

6 Likes

I prefer to see a Repr trait or similar, allowing to express complex representations as trait bounds.

1 Like

What kind of complex representations would you like to express? And how would the trait look like? How would the compiler be able to ensure that types do indeed have the correct layout?

I think keeping this feature simple (allowing two types to have the same layout is going to be the most complex layout repr anyway) is a benefit. The compiler does not have to handle very complex representation relations, it only has to know if layout(A) == layout(B) is true in a stable sense (so that changing a compiler version/enabling layout randomization do not change the layout equivalence) the value of layout(A) should still be variable. In particular layout(A) == layout(B) is not true for:

struct A {
    _0: Field0,
    _1: Field1,
    _2: Field2,
    _3: Field3,
}

struct B {
    _0: Field0,
    _1: Field1,
    _2: Field2,
    _3: Field3,
}

If you have a good example of why you would need more complex repr then I would like to see it. But I think that you can already do a lot with this new feature.

If you would like to create code like

fn safe_transmute<A, B>(a: A) -> B
where
    A: SameLayout<B>,
{
    unsafe { core::mem::transmute(a) }
}

Then you should look at project-safe-transmute, as that is outside of this scope (allthough it could probably be nicely integrated).

Assuming that the safe transmute project happens, that could also be done with just something like

assert_impl_all!(Foo: SafeTransmuteFrom<Bar>);

(using static_assertions::assert_impl_all - Rust)

And thus it's not obvious to me that this should be a full language feature.

That would be great! I have not had the time to read all the details of the safe transmute project, so I do not know what it does exactly.

It also seems like I was looking at the wrong places, the repository hasnt had a commit for 2 years (so it seems like the discussion is on zulip, but following there is probably a lot of reading).

I don't know that precisely the form it will take is decided, but here's some progress towards part of it:

I think this is not enough, because if Foo and Bar are #[repr(Rust)], then e.g. layout randomizations would not give the two types the same layout even if their fields are identical/transmutible

Well, but then the assert would fail. (Or maybe it always would, without repr(C).)

Then the safe transmute project might not be enough. That is why I want repr(inherit(...)). It is to guarantee that two types have the same layout.

I want to begin writing a pre-RFC and I want to add this in the alternatives section. Did you have a clear idea of how this would be implemented?

Not completely clear, but something like

pub trait HasLayout {
    type Layout: sealed::LayoutType;
    
    const ALIGN: Option<usize>;
    const PACKED: Option<usize>;
}

mod sealed {
    pub trait LayoutType;
}

// #[repr(rust)] or no repr generates an impl HasLayout<Layout = RustLayout>.
pub struct RustLayout;
impl sealed::LayoutType for RustLayout {}
pub struct CLayout;
// ...
pub struct TransparentLayout;
// ...
pub struct U8Layout; // Etc.
// ...

// There may be problems with layout not applicable, for example primitive representations for non-enums or `#[repr(transparent)]` for more than one field.
// There may be a need for post-monomorphization errors to handle that, I'm not sure.

But IIUC you still need to specify the layout using #[repr(...)], right? So it is essentially just a marker trait that you could use in some trait bounds. How would you specify my proposed #[repr(inhereit(...))]?

If it is a marker trait, then this is probably not the right RFC for that, as I only want to add the additional repr.

No, #[repr()] becomes like a macro that emits an implementation of the trait. You can implement the trait to change the layout.

Ok that makes sense, I do not know how the current mechanism of layout selection works in the compiler, so I do not know if this is a feasible architecture change. I will add it to the pre-RFC