4 ms·
A trait is like an interface. A struct implementing a trait means it takes on the methods of that interface (but there's more to it, because a trait can have de
by algesten 8y ago
A trait is like an interface. A struct implementing a trait means it takes on the methods of that interface (but there's more to it, because a trait can have default implementations of of the trait methods).
If you want to express something like "this is a variable that holds something that fulfils this trait", without knowing the _actual_ type it is, that variable effectively has an unknown runtime size. std::io::Read is an interface for reading bytes of some source, like a file or a socket.
This matters because we're talking about a stack frame. So the size needs to be known at compile time.
let a: u64 = 42; // ok, because well known size.
let b: Read = ...; // illegal, because unknown size.
A "trait object" places the object on the heap and has a pointer in its place.
let b: Box<Read> = ... // legal, because pointer is a known size
However it's a bit more complicated, because this syntax allows for dynamic dispatch at runtime using a vtable. So there's a quite big difference between Box<u64> (a 64 bit unsigned integer on the heap) vs Box<Read> (a runtime dispatched lookup via a vtable).
This difference is not obvious at a glance though. Hence the new syntax: Box<dyn Read>.
(I think I got that right)
- steveklabnik 8y agoYou did, except that it’s not one pointer, it’s two. This is one way that rust is different than C++; the vtable isn’t stored with the data, but separately, with the “trait object” itself being two pointers to the two things.
- algesten 8y agoGood to know!
- tzahola 8y ago>This is one way that rust is different than C++; the vtable isn’t stored with the data Which “data” is the vtable stored with in C++? An object contains only a pointer to its vtable, not the vtable itself...
- steveklabnik 8y agoMy understanding is that, generally (because (again in my understanding) this is all compiler-specific, not mandated by the standard) is that in C++, there's a single pointer that points to the combo of the vtable and then the data, after it. See http://www.drdobbs.com/cpp/storage-layout-of-polymorphic-objects/240012098 http://www.drdobbs.com/cpp/storage-layout-of-polymorphic-obj... for example. While a Shape is a position, outline, and fill, if you have virtual functions, the pointer points to a vtable pointer, and then all of the data. In other words, in Rust, a pointer to a shape is (pointer to vtable, pointer to data) whereas, in C++, a pointer to a shape is (pointer to shape) where shape is (pointer to vtable, data) If that's incorrect, I'm quite happy to be corrected! This isn't an area I'm an expert in.
- tzahola 8y agoThat’s absolutely correct. I get it now, that you meant the “pointer to the vtable” under “vtable” in your original post.
- steveklabnik 8y agoAh, I see. Yes, I should probably have been more clear there. Thanks for pointing that out.
- stormbrew 8y agoA C++ object with virtual methods and some members looks like this, if you put it in terms of C: struct ClassVTable { void (*firstMethod)(); void (*secondMethod)(); } struct Class { struct ClassVTable *virt; int firstMember; int secondMember; }; and a pointer to it looks like: struct Class *objectRef; A language that uses fat pointers, like rust, has this separated completely: struct Class { int firstMember; int secondMember; }; struct FatPointerToClass { struct ClassVTable *virt; struct Class *data; } FatPointerToClass objectRef; // note lack of pointer, it'd be stored on the stack directly for eg. This means a much simpler object layout in exchange for passing around larger pointers, and it's a good fit for trait-based typing since it doesn't require you to know all possible interface subtypes of the object in order to describe or use its layout. (please note that I'm using C struct to be precise about the concept, and this should not be taken as a perfectly verbatim description of the actual object layouts in memory)
- int_19h 8y agoThe linked port says that it has proven to be a bad design decision specifically in the contect of "impl Read" etc. Can you give an example of that, and why it was confusing before?