Let me elaborate on what I was thinking here, though it's been a while since I've had my head in this space and I think that the "gnome-class" effort has evolved quite a bit. Still, I think it's worth talking about, because the use case seems like an important one.
The idea was that sometimes field offsets do need to be computed dynamically. In the case of GObject, there is a little bit of code that is ordinarily baked into a macro, which computes a negative offset from the pointer if I recall.
I had hoped to allow people to write "unsafe impls" where you give a little snippet of code to compute the field offset. I imagined code that would return a *mut T
(or *const T
for read-only fields). Something like:
impl Foo for Bar {
let x = unsafe {
// a block of code where `self` is in scope
GObject_helper_compute_offset(self, 0) // or whatever
};
}
It would then be on the implementor to guarantee the disjointness requirements. And yes, this seems to imply that we extend the proposal with the ability to support fields that are reached not via an interior offset but via executing some code found in the vtable. (More on that in a second.)
Well, there is a tension, but I'd not say mutually exclusive. In order to achieve performance parity with C++, we already need the ability to tag traits and place limits on their impls. For example, it would be useful to be able to tag traits as #[repr(prefix)]
, which means that the fields in the traits must appear as a prefix of the structs that implement those traits (this in turn implies limitations on the impls: e.g., you can only implement this for a struct in the current crate, etc etc). So -- presumably -- limiting to interior fields, but with arbitrary offsets, would be another kind of repr (roughly corresponding to "virtual inheritance" in C++). And the most general form would permit executing a small shim to identify the offset.
I think in the end we want this anyhow, even for safe code, because it allows us to support general paths:
struct Foo {
data: Box<FooBox>
}
struct FooBox {
a: A,
b: B
}
impl SomeTrait for Foo {
let f = self.data.a; // this is not interior to `self`
}
So, while I could see trying to cut out the unsafe part and leave that for a possible future extension, I do think we should make provisions for executing shims, which then leaves the door for those shims to be written by the user.