My main concern was that despite the length and the in-depth nature of the description, I still felt pretty uncertain about some of the technical details. I don’t think this is due to complexity, but rather that a lot of the stuff just doesn’t seem necessary (to me, anyway).
I’ll put up a revision which purges out all talk about tail-padding and cfg configurability.
I do not think Coerce is a good idea. There’s a reason we don’t have DerefMove yet. In particular, making Coerce happen automatically sounds like a recipe for a lot of either bugs or surprising behavior, since it can chop off part of a structure!
Actually, std::mem::transmute refuses to transmute between data of difference sizes. I would also note the trait is unsafe precisely because you are toying with complications here.
A fair amount of the padding section seems not too interesting to me, though I can’t speak for others. I think people would probably be okay with requiring repr(C) if you’re relying on shared layout between two structs (it’s currently required for transmute to be well-defined).
I totally scrapped it out, it was distracting.
I’m much more interested in how all of this stuff applies to enums–does it? If not, it seems like a wasted opportunity, since one case that I frequently want is to be able to turn a set of enums into a larger or smaller set of enums that share the same base (as long as I’ve verified beforehand that all the necessary variants are shared between them).
No, not one bit.
It is easy for struct to share fields, but for enum to share variants brings complications:
- if you extend the
enum (add more variants), then matches on the parent enum do not cover the extra variants
- if you refine the
enum (remove some variants), then by seeing a mutable reference to the parent, you can add those variants
As a result, it seems that with enum the number of variants must be fixed. I cannot see how to deal with that.
I’m also interested in how generics fit in with all this. They aren’t really discussed, so I’m not sure if they present any additional challenges when it comes to dealing with alignment and so on.
I do not think so; they were not discussed because I did not see any additional challenges. I may just be short-sighted, of course.
I am not really sure about the part where you have to specify which implementations from the parent you are using. What does that buy you, exactly? What if it’s a new trait that’s defined in your own crate; do you have to define it for both the parent and any children you’re using it for there, too?
Rust tries to shy away as much as possible from implicit assumptions; I was just trying to play it safe.
All the cfg stuff sounds pretty bad to me. Why do we need it for every trait? We already have a Reflect OIBIT, which seems like a perfect candidate for deciding whether or not to expose RTTI information, and trait objects, which expose vtable information; neither option splits the language based on whether you want to generate vtables or not.
I flat out don’t understand why you need this new and elaborate rtti model for downcasts. Maybe there’s something I’m missing, but what is wrong with just using the type ID that we already have (which, again, is already bounded by Reflect)? This is how Any does it and it seems to work fine. Also, I don’t know Servo’s precise
requirements, but downcasting to an intermediate trait may be what they’re interested in, since Rust can already downcast just fine to an intermediate type (well… I’m not actually exactly sure why they need cheap downcasting at all, so I probably shouldn’t presume that).
I must admit having some difficulties in using Any, so it may well be that it can accomplish much more than I credit it for.
As far as I could see in my exploration, though, Any seemed to suffer from two issues:
- it could only be build from a concrete type
- it could only down-cast to that same concrete type
I’ve tried to coax it to down-cast to traits, but was left quite frustrated by the experience (downcast_mut only targets Sized types, for example).
Thin pointers are probably the most important thing Servo needs, and also probably the most useful thing I expect to come out of all these discussions, so it’s a bit weird that they receive so little attention here.
Yes, all of that to enable thin pointers… Box<DynClass<_, _>> is a thin pointer, and so is Box<Dyn<_>>.
() as a common ancestor really makes no sense to me. What about, say, [T ; 0], or PhantomData? I think the proposal would be better without this.
Actually, it seems the de-facto common ancestor already; I trudge through a few standard libraries which use an unknown type (in unsafe code) and it seems to always end up being either *const () or *mut ().
I thought about leaving it out, however I really wanted to present Dyn<T> as a thin pointer alternative to &T and the cheapest option was for it to be an alias of DynClass<T, ()>. An earlier version of the RFC had a whole new lot type, with conversions back and forth, it felt quite complicated for not that much.