Comment Re:What could possibly go wrong? (Score 1) 157
FWIW, Rust also supports interfaces and extensions thereof, including extensions of multiple interfaces. In fact, you have to do some really unusual things in Rust to define a type that doesn't implement at least one interface, and most implement many. Even primitive types implement many interfaces. The lowly "u8" (C++: uint8_t), for example, implements well over a hundred interfaces.
Most such interface implementation in Rust is static, meaning it doesn't make use of dynamic dispatch, but it's also extremely common in Rust to define interfaces that can have multiple implementations but be referenced only through the interface and use dynamic dispatch to call methods. In Rust (as in C++, actually), you can use either compile-time or run-time polymorphism.
The restrictions on the above, compared to C++, are that (a) all Rust interfaces ("traits") are pure[*] and (b) while you can have arbitrarily-deep stacks of interface inheritance, once you derive a concrete type -- a non-pure "class" -- you can't subclass that. Inheritance is of interfaces only, though interfaces can inherit from one another and multiple inheritance is not just common but nigh-universal.
The lack of the ability to subclass a concrete type isn't an omission or oversight, it's a deliberate design decision to avoid the trickiest and least-useful characteristic of C++'s object model. Indeed, current common wisdom among C++ developers (I've been writing C++ professionally since 1991 and have seen the evolution of the wisdom, and the pain that drove it) is that implementation inheritance is generally a bad idea. It's not forbidden, by any means, but it's treated as a mild code smell, and a design that makes very heavy use of implementation inheritance is almost always a bad design. And, of course, multiple implementation inheritance is just asking for eternal pain.
For those interested, the way this stuff works in Rust, and in Rust nomenclature, is that interfaces are called "traits". A trait is like a C++ class that contains only pure virtual and static methods. It has no data members and cannot be instantiated; it's not a concrete type. Traits can extend other traits, and can mix in multiple traits. Like C++ or Java interfaces, functions can take traits as arguments, though they must be passed by reference, not value. Of course, in Java all objects are passed by reference, while in C++ you have to explicitly pass a pointer or a reference to an abstract interface.
Concrete types come in a few flavors, but the one most relevant here is a "struct". A "struct" is like a C++ struct/class. It can have data. It's generally a concrete type that can be instantiated. Structs can implement any number of traits, and it's a pretty rare struct that doesn't implement several traits implicitly, and often they implement multiple traits explicitly, too. Trait implementation is so common that there's macro infrastructure specifically to facilitate it.
For example, very often you'll see struct definitions with a line above them like #[derive(Debug,Clone,Copy,PartialEq,Eq,PartialOrd,Ord)]. That macro invocation declares that the struct implements those seven traits, and automatically generates the methods to implement them:
* The "Debug" trait is implemented by things that can provide a string representation of themselves suitable for debug output. The macro generates a reasonable format, two of them, actually, a compact one and a "pretty" one. If you don't like those formats, you can instead manually implement the trait.
* The "Clone" trait is implemented by things that can be copied, but only with an explicit "clone()" method call. The macro generates an implementation that clones all of the fields. This only works if the fields all implement Clone. You can, of course, provide a custom implementation if needed.
* The "Copy" trait is implemented by things that can be copied automatically. Essentially, this tell the compiler that it's allowed to insert "clone()" calls as convenient and that by-value arguments of this type should be cloned rather than moved (like C++, Rust supports move semantics, but unlike C++ in Rust moving is the default). To implement "Copy" you must also implement "Clone", which provides the implementation for copying, and on top of that you're asserting that copies are cheap.
* The "PartialEq" trait is implemented by things that can be compared for equality, but doesn't promise an equivalence relation (i.e. A == B && B == C does not necessarily imply that A == C). The macro implements a default partial equality operator that checks field by field, which only works if all of the data members also implement PartialEq (otherwise, compile error).
* The "Eq" trait is implemented by things that can be compared and do promise an equivalence relation. Types must implement PartialEq to implement Eq. The macro provides a field-wise comparator.
* The "PartialOrd" and "Ord" traits are implemented by things that can be ordered, similar to "PartialEq" and "Eq". The macros implement an ordering that compares fields lexicographically. Obviously the data members must also implement the ordering traits for this to work.
In addition to those, an ordinary struct automatically implements the "Sized", "Send", "Sync" and "Unpin" traits:
* The Sized trait means that the data structure has a fixed size. Note that apparently variable-length structures like vectors and strings also implement Sized; a "String" structure has a fixed size, but contains a pointer a heap buffer that may be resized. A concrete type must implement "Sized" in order to be instantiated because we need to know how much memory to allocate.
* The "Send" trait means the data structure is safe to send to another thread. This is automatically implemented if all of the data members are "Send".
* The "Sync" trait means the data structure is safe to read from another thread. This is automatically implemented if all of the data members are "Sync".
* The "Unpin" trait means the data structure can be safely moved in memory (assuming references to the data are managed appropriately -- Unpin is a promise that nothing inside the data structure will break if it's moved, i.e. it doesn't contain any pointers to itself, not a promise that nothing outside of the data structure will be broken if it's moved).
And, of course, structs may explicitly implement traits, and must define the necessary methods for the traits they implement.
One really big difference with C++ is the interaction between local types defined in the current program (or library) and types defined in other libraries. In C++ if a library exports an interface (pure or not), you can subclass it, but you cannot make a library type a subclass of an interface you created. In Rust you can do both. This works in Rust because Rust doesn't put vtable pointers inside the struct, unlike C++. In C++, the memory allocated for an object with virtual methods contains a pointer to the vtable used to find those methods when they're called. In Rust, the object does not contain the vtable pointer. Instead a pointer (or reference) to an object through an interface is "fat", containing the address of the object and the address of the vtable for the interface (trait) the pointer is referencing. Obviously, if you're referencing the concrete type directly there's no need for a vtable at all. It's only when you're referencing an object through some abstract interface that you need the vtable.
Anyway, I can think of no construction in C++ that can't be translated into Rust, though some amount of refactoring may be required, most especially in the case of C++ code that makes heavy use of implementation inheritance. The basic process of converting a deeply-layered inheritance hierarchy is (a) pull all the virtual methods out into one or more traits and (b) replicate the inheritance hierarchy with a set of increasingly-nested structs. Even multiple virtual inheritance hierarchies can be transformed this way.
If someone can think of a C++ construction that can't be fairly easily translated into Rust, I'd love to hear about it. Obviously, many C++ constructions are hard to translate into safe Rust, because they're unsafe. Converting them to unsafe Rust would often require deeper refactoring. Still, the unsafe Rust version wouldn't be any less safe than C++, and after conversion you can start the process of refactoring to eliminate or minimize the unsafe bits. Equally obviously, directly translating C++ to Rust is going to result in very non-idiomatic Rust. Among other things it's going to be littered with "mut" everywhere because in C++ stuff is mutable by default while in Rust stuff is immutable by default. Really well-written C++ uses "const" everywhere it can and tries to minimize mutability, so that'll convert more nicely, at least in that aspect.
What is most likely to be problematic in large-scale, automated translation is borrow checking. The C++ code is almost guaranteed to violate borrow checker rules all over the place, and some of those violations will only be fixable with very deep refactoring or really sketchy Rust code. This doesn't mean the C++ can't be translated, just that the result will be nasty (even if not unsafe).
[*] Interfaces (traits) actually can have default method implementations, which subclasses (structs) that implement them can optionally override. Since the interface has no data members this usually only makes sense when there are methods that can be implemented in terms of other interface methods, without direct reference to instance data.