Add `repr(inherit($type))`

Would this only work on all-pub[1] fielded structs then (since those would all imply a semver contract about the names)? I think mentioning that this therefore does not add new semver hazards to visible structs would be useful (and supporting it can be suppressed with a private ZST field if suitable).


  1. Technically, visible; in-crate or or other in-module structs could name the fields and update as needed. ↩︎

3 Likes

It should also not support #[non_exhaustive] structs, so one can do that too.

1 Like

Edited the post above^^

This limits the value of this feature - it could be useful to have something like

struct A {
    a: u32,
}

struct B {
    b: u32,
}

struct AB {
   a: A,
   b: B,
}

#[repr(inhert(AB))]
struct Foo {
    a: u32, // alias with AB::a::a
    b: u32, // alias with AB::b::b
}

One idea would be to make the field mapping explicit with extra attributes, eg:

#[repr(inhert(AB))]
struct Foo {
    #[repr(map(AB.a.a))]
    a: u32,
    #[repr(map(AB.b.b))]
    b: u32, // alias with AB::b::b
}

This in turn suggests that this per-field attribute is actually what you're proposing, since this gives a much more general ability (you could also have something like map(A.0) to deal with tuple types). The overall struct annotation could be implemented with a proc-macro which uses the field name as a default, while still allowing per-field annotations.

This also makes the close-coupled relationship between the repr-aliased types extremely obvious - every field would need to have an explicitly designated field in the other type to be valid.

I still don't see how this can be composed though. What if Foo were composed of fields which were already repr(inherit) from other types - what does that imply for Foo?

How does this work with polymorphism? Is generic type Foo<T> transmutable from Foo<A> to Foo<B> if B repr(inherit)s A?

What are the implications for unions? Does it affect what niche filling in enums can do?

On balance I'm skeptical about this proposal - while it could enable some neat stuff, it seems like there's a lot more complexity that needs to be shaken out, and it's still going to be fairly niche, brittle and complex to use.

1 Like

That's not the same. The SafeTransmuteFrom (or MuckableFrom or whatever) trait would only provide a way to confirm that types Foo and Bar have the same layout.

The proposed repr would take Foo's layout and apply to Bar.

1 Like

I might be a tiny bit bitter, so take my advice with a grain of salt, but:

Don't sweat it too much.

The primary factor in how likely a RFC is to be accepted is "How much does it impact a compiler maintainer's daily coding". In the end it doesn't matter how well you phrase the proposal, and how much you think through the corner cases, the actual bottleneck is finding someone who knows compiler internals enough to actually implement it, and that person being motivated enough to put in the hours.

(Or alternatively, becoming that person, but that's no easy feat.)

I'm not saying that to discourage you, mind you. Putting out a formalized proposal with considered arguments is great. Just don't worry too much about whether it fits some imaginary standards of quality, because that's not really the main blocker.

1 Like

No, that is not necessarily sound. As an example:

struct Foo<A> {
    pub id: std::any::TypeId,
    pub phantom: std::marker::PhantomData<A>,
}

impl<A> Foo<A> {
    pub fn new() -> Self {
        Self {
            id: std::any::TypeId::of::<A>(),
            phantom: std::marker::PhantomData,
        }
    }
}

I have no particular opinion on whether this repr is a good idea or not, but I would like to comment on the use of the verb inherit. My initial assumption was that this feature was about supporting class inheritance by making types share common prefixes, similar to how C++ works. However, that doesn't seem to be what you want to propose (and I think that would be better solved, anyway, by making stronger guarantees around structs with unsized generics, but that is neither here nor there). Your motivation has nothing really to do with inheritance as generally understood, so I think you should steer clear of terminology that will make people think of classes and OOP.

Alternative suggestion: maybe repr(of Foo)?

3 Likes

Do you have any non artificial examples? I feel like we should try to make this feature as simple as possible while still solving the problem that I want some layout control without full manual layouting.

The code is missing two #[repr(transparent)] for A and B.

This seems to me like a lot of extra work. I understand why you would like to rename/have better control over what fields get mapped to each other, but in my mind this feature's main usage will be for types you control both of them. So you could just choose a name that is fitting in both worlds.

If Foo has no repr(inherit), then the fields are laid out according to the repr of Foo. If it has repr(inherit), then its layout is determined by the supplied type. Each field needs to be layout compatible with the same name field in the supplied type. They might be the same, or if repr(inherit) is nested, then it may be the other type. Example:

struct A {
}

struct B {
}

#[repr(inherit(A))]
struct A1 {
}

#[repr(inherit(B))]
struct B1 {
}

struct C {
    a: A,
    b: B,
}

#[repr(inherit(C))]
struct C1 {
    a: A1,
    b: B1,
}

No this is not the case, see here.

I do not know what you mean by this. The proposal allows #[repr(inherit)] only on structs. I have not a complete picture of how unions could be supported, but there might be some use for this. That is why I put it into the future possibilities section.

Thanks for your feedback! It is indeed niche, but as a systems programming language, I think rust should fill this niche, as this problem is "created" by the more advanced layouting the rust compiler does. In C you have layout compatibility as soon as you declare the correct type. In rust you either have full control over the layout, or none at all. I think a middle ground is definitely needed.

Good point, going to add this to some later bikeshedding. Other alternatives that come to mind:

  • repr(like $type)
  • repr(equal $type)

I would like to postpone this to a point where the other concerns have been addressed.

About the repr(inherit) naming, I don’t think that inherit is a bad choice of a word since inheritance (at least in C++) means that if B inherit from A, all bit at the beginning of B match exactly the layout of A, and thus at pointer to B can be safely reinterpret_casted into a pointer to A. Therefore what you propose match is an exact subset of what you can do with C++ inheritance. The only additional thing that’s possible with C++ inheritance is that B can have extra fields after the fields of A (unlike what you propose), but that could be a future extension too (that being said I don’t think it would be that useful unless we want to have a #[repr(C++)] in a distant future).

I also agree that this discussion should be postponed.

You really want static_cast here. This adds the appropriate offset for this case:

struct B : public C, public A {};
1 Like

My impression is that among the intended use cases are things like partial initialization behind a pointer. If repr(inherit) does not guarantee the exact same layout (no extra fields), you can't create a Box<FooUninit> and convert it to a Box<FooInit>. In this case, if you want to end up with a Box<FooInit>, you're reduced to either (a) creating a FooInit and then moving it into a Box after the fact, or (b) creating a Box<MaybeUninit<FooInit>> and doing it the "old fashioned" way. Guaranteeing only a common layout prefix severely limits the applicability of this feature for partial initialization.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.