3 ms·
> A trait must follow so-called object safety rules to be used as a trait object This seems for me to be a major design flaw of Rust. It tries to repurpose tr
by Panzerschrek 28d ago
> A trait must follow so-called object safety rules to be used as a trait object
This seems for me to be a major design flaw of Rust. It tries to repurpose traits for dynamic polymorphism, even if this doesn't fit perfectly. C++ is more honest, it has two separate mechanisms for static polymorphism (templates) and dynamic polymorphism (inheritance).
- gignico 28d agoIn the contrary it’s one of the best design decisions of the language. The rules for object safety are the same C++ applies to virtual functions. On the other hand you don’t have to choose a priori whether you’ll need dynamic or static polymorphism, you can choose at use site, and the size of the objects do not pay for dynamic dispatch because the vtable pointer is kept together with the object pointer, not within the object.
- Panzerschrek 28d ago> you can choose at use site This is possible in C++ too, but it may require extra work. But is it really needed that often to use both kinds of polymorphism for the same type? > the size of the objects do not pay for dynamic dispatch because the vtable pointer is kept together with the object pointer, not within the object. You have identical memory overhead in Rust and C++ if you store a single pointer to a polymorphic object. But if more than one such pointer is stored (if it's shared), Rust uses more memory, since it stores N virtual table pointers (together with each object pointer), where C++ stores exactly one virtual table pointer within the object itself.
- tialaramex 28d agoThe C++ design is optimised for the case where you're mostly storing Things everywhere and then at runtime code works out whether each particular Thing is a Customer, or a Product, or a Target, or an Artist, or what... I don't think even an LLM writes software like this, maybe somebody's Java 101 class teaches this, but frankly I think that's a bad way to teach even Java. The Rust approach optimises for cases where Customers and Products and Targets and Artists are stored and treated separately and if we do need the generic Thing somewhere it's pretty rare so we store the extra information only where needed.
- SkiFire13 28d ago> This is possible in C++ too, but it may require extra work. Everything is possible, what matters is how much work you have to do to achieve and maintain it. > You have identical memory overhead in Rust and C++ if you store a single pointer to a polymorphic object. But if more than one such pointer is stored (if it's shared), Rust uses more memory, since it stores N virtual table pointers (together with each object pointer), where C++ stores exactly one virtual table pointer within the object itself. On the other hand Rust uses less memory if you store 0 polymorphic pointers, while C++ still pays the cost of storing a vtable pointer for each object instance. Effectively C++ choose a design that optimizes for having a lot of polymorphic pointers, while Rust optimized for supporting polymorphism for all objects at no extra cost if you don't use it.
- lightingthedark 27d agoThere is another important reason not to store the vtable pointer in the object: performance! In C++ if you want to make a virtual method call you have a double indirection where you must first load the object and only then can you load the vtable. Having a fat pointer like Rust does means that both loads can be in flight at once. If you are using enough dyn references to care about the extra memory then you'll likely care about the performance impact of those extra indirections even more.
- imtringued 28d agoI don't see how the first rule is a major design flaw. The second rule is mildly annoying but all it means is that dyn compatible traits must use dyn compatible traits in their function signatures so you have to duplicate it for the compile time dispatch and the runtime dispatch.
- mrkeen 28d agoI'm not a C++er, but I sorely miss this is nearly every non-Haskell language. I have a type capable of some behavior, e.g ToString. I also want to write general code operating on such objects (highlighting, obfuscating passwords, escaping, etc.). But I have zero desire to throw away its static type information. If it gets stored in an Escaped<T>, the compiler ought to know T is still my type.