5 ms·
C++ interoperating with C makes it very C-like and often removes the benefit of using C++. In rust, you can do dynamic dispatch via trait objects over C struct
by jeffdavis 8y ago
C++ interoperating with C makes it very C-like and often removes the benefit of using C++.
In rust, you can do dynamic dispatch via trait objects over C structs. In C++, that wouldn't work, because dynamic dispatch uses inheritance and it needs to add a virtual method table pointer in the struct, meaning that you'd need to translate/copy the struct into a C+ one.
Also, rust has neat tricks. An Option type of a reference is represented as a single pointer where NULL means None. That means your plain old C struct can be interpeted as an Option type in rust without copying/translation.
- ridiculous_fish 8y agoI think I have understood your examples: 1. In Rust, you can define a method on a trait object (a type that encloses a C struct), and then invoke it dynamically with the syntactic appearance that 'self' is the struct. C++ does not allow you to add virtual methods to C structs. 2. In Rust, given a C struct T, you can convert T* to an Option<&T> without copying. This stuff is all gravy but I wonder about the meat. Is there a good story for consuming C headers directly? C structs are typically a hash of typedefs and preprocessor gunk; can Rust navigate that?
- steveklabnik 8y agoThere’s a tool, bindgen, that can read in C headers (and limited C++ support, I believe) and produce the bindings Rust needs to understand them. It’s not perfect but IIRC it’s good enough for Firefox.
- kazinator 8y agoRust is magic pixie dust, whereas C++ is limited to manipulating ones and zeros, which severely curtails the available techniques. class cpp_class { c_struct *pimpl; public: cpp_class(c_struct *p) : pimpl(p) { } virtual void whatever(); } class derived_cpp_class : public cpp_class { public: derived_cpp_class(c_struct *p) : cpp_class(p) { } virtual void whatever(); // override };
- htfy96 8y agoEven simpler with some template boilerplate code: // Just copy paste this template<typename T> class CContainer { protected: T *pimpl; CContainer(T* raw_ptr): pimpl(raw_ptr) {} public: CContainer(const CContainer&) = delete; CContainer& operator=(const CContainer&) = delete; virtual const char* get_my_name() = 0; virtual ~CContainer() {}; }; // User class MyCHandler : public CContainer<c_struct> { public: MyCHandler(int arg) : CContainer(my_c_new(arg)) {} virtual const char* get_my_name() override { static const char[] kMyName = "MyC"; return kMyName; } virtual ~MyCHandler() { my_c_destroy(pimpl); } std::unique_ptr<MyCHandler> clone() { return std::make_unique<MyCHandler>(my_c_clone(p_impl)); } }; Moreover, the zero-copy magic from pointer to option in Rust is not something like what you think - Rust relies on compiler magic to eliminate option type's heap allocation, and if you convert it from a pointer, it will reside on the heap.
- steveklabnik 8y agoThe option type never heap allocates on its own. There’s no reason to rely on compiler magic that doesn’t exist in the first place.
- jeffdavis 8y agoThat is creating an extra container object which needs to be allocated separately. Rust doesn't require that. I'm sure that you can construct the container object at each call site so that it behaves like rust (and it would just be allocated on the stack), but it would get a little ugly. In rust, it's very seamless and "rust-like" even though you are operating on a native C struct. Similarly, the Option type magic is pretty darn useful. I'm sure you could do that in C++ as well, but again it would end up ugly because you'd need to invent a new COption type or something.
- kazinator 8y ago> Rust doesn't require that. In the general case, the hardware requires it; that's how computers work. The C library you're working with that has handed you the structure has not reserved any space for you to put anything into the C structure for it to have embellishments like type-dispatched virtual functions. The memory addresses immediately above and below the structure are off-limits (if they exist at all). If you control the allocation and disposal of the memory, then you can easily embed the structure in a larger object. When calling the C code, it just gets a pointer to that part of the object that is the C structure it knows about. Amazingly enough, C++ can do this quite easily. It can be done not just via composition, but also by inheritance: class derived : public some_c_struct { public: virtual method(); }; Then it's just: extern "C" void c_function(some_c_struct *); { derived d; // define easily "on the stack" c_function(&d); // pointer upcasts to base implicitly } All you see here is 1995 draft vintage C++. The idea that C++ doesn't work smoothly with C structures is bizarre; C++ contains a dialect of C which is highly compatible with C90 (to the extent that large programs can be developed such that they compile as C or C++).
- aaaaaaaaaaab 8y ago>dynamic dispatch via trait objects over C structs What does this mean? A struct is just a block of memory with zero type information. So how does Rust do dynamic dispatch on C structs again?
- steveklabnik 8y agoI think what your parent is saying is that, since dynamic dispatch is created via a special kind of pointer in Rust (trait object), rather than embedding a vtable along with the data, Rust can dynamically dispatch to C structures where C++ can’t. Does that make any more sense? I can show some code later. I’m also not 100% sure that’s what your parent is saying.
- aaaaaaaaaaab 8y agoBut then it’s no longer a plain struct, rather a struct plus a pointer to the trait object. Which is perfectly doable in C++ too.
- steveklabnik 8y agoThe struct is a plain struct; just like an int is an int and a int* is a pointer, a struct is a struct and a trait object is a pointer. (It’s a double pointer: a pointer to that struct and a pointer to the vtable)