Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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)



You 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.


>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...


A 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)


Oh, I didn't know that: this is also great for serializing code where you might have an array of `Class`, it would still be a POD-type to use C++ language.


My 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-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.


That’s absolutely correct.

I get it now, that you meant the “pointer to the vtable” under “vtable” in your original post.


Ah, I see. Yes, I should probably have been more clear there. Thanks for pointing that out.


Good to know!


The 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?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: