Yes indeed, my proposal is more a call to twist the thin trait proposal, than a full fledged RFC yet. Everything that you said in the blog post applies unless amended in my proposal.
I think that a major difference between my proposal and Fat traits, is that Fat traits even separate the struct definition from the possibility of having virtual dispatch. That is, you can have a struct Gizmo { x: u32 } and if you want virtual dispatch you create a Fat< Gizmo, Trait>. In my proposal, the virtual function pointer is already part of the structure with something like: struct Gizmo virtual Trait { x: u32 }.
So it is closer to your blog post than the Fat trait in that respect, except that the presence of a vtable pointer in the structure is explicitly declared in the struct’s definition.
So coercion rules can be put in place because you know that Gizmos do have vtable pointers. the Fat object proposal, however, while it offers some flexibility, does not provide coercion between thin and fat pointers.
The implicitness of the presence of a vtable is indeed a big concern for me. If we have a reciprocal annotation on the struct, then things already get better. Now why would this annotation need to be reciprocal? since the important part is shoving a vtable in a struct, why annotate the trait at all? is it just in order to not have to annotate the pointers? I may be missing something important here so please let me know. I feel like if thin pointers are the niche optimization that some people say they are, then the burden of annotating pointers (Rc< virtual Gizmo>, etc) when opting into thin-ness does not seem to be an issue, and it is only a burden for people who make the decision of opting into it.
A typical example of implicit change that causes side effects that break things without people noticing in C++ is calling a virtual function from a destructor. One might think it’s a different issue, but I believe that it belongs to the same higher level group of issues with languages allowing me to make changes in a certain place of the code which has a bad side effect on an other area of the code where an assumption was made. The assumption that was made was, “this function is non-virtual so I can call it in this place which is in the destructor or in something called by the destructor” and somewhere else in the code, someone added the virtual keyword in front of the function definition because to solve a different problem he decided that it was a good idea, but didn’t check all of the destructors to verify that it was not going to cause problems. The initial assumption is then broken, subtle bugs happen thanks to the subtle rules of virtual dispatch in a destructor in C++, but the code is still legal and compiles fine. I find bugs like this one every now and then in Gecko. There is no memory safety issues here, just code that doesn’t do what you expected.
If a struct needs to fit exactly in a cache line, for some performance reason, things that may modify the layout of your structure without you noticing will make it easy for your program to regress. That’s a bug. Not a memory safety bug, but nor was the virtual function call in the destructor.
If you need to put your structure in shared memory, it is important to be able to tell that the structure does not hold pointers, otherwise implicitly adding a vtable will cause actual memory safety problems.
Same thing with ffi, although I guess we can make sure the compiler errors if a struct is #[repr©] and gets a vtable implicitly added.
Really my proposal works exactly like what is described in the blog post, except that what affects a structure is explicitly stated in the struct. So the design patterns it can or can’t allow should be the same. I am just trying to avoid surprising bugs caused by side effects.
I would be sufficiently happy with something where thin-ness is at least explicitly expressed in the struct even if it is also marked on the trait, I suppose.
I see this differently. If you don’t care about thin-ness, just don’t use thin pointers. If you are forced to see thin in your code, it is because you decided to opt into it (so you in fact do care), or you are reading someone else’s code and you can keep ignore it, or a library forces you to pass a thin trait as an argument and you are forced to opt-into thin-ness. Granted the latter forces you to care about a bit more than you wanted to, but if caring is going to save you from the kind of side effect bugs I described, I believe it is a good thing.
Great things could emerge from this kind of flexibility. Perhaps the flexibility comes with risks (let me know), and Rust doesn’t want to be the language that takes the risks and ventures into these new experiments. That’s a perfectly respectable position, but to have a practical point of view, we’d need to try it (or find a language that did) first.
Not at all! I haven’t perceived your post as harsh and I thank you for taking the time to explain your view in detail.
What motivated me to open this thread was not the fat object proposal (even though I think it is an interesting one), but really avoiding side-effects on data layout, so I hope we can continue a more focused discussion here and open a new topic about fat objects if people want to argue about it some more.