Crubit requires types to be “movable” to be passed by value: if a Rust type does not logically support a C++ move operation, then it can receive bindings, but it cannot be passed by value.
A Rust type can be made movable in C++ in one of three ways:
Clone.Drop, and nor do any of its fields.)Default.The easiest way to ensure your type is useful to end users, even if it is changed in the future, is to implement Clone and Default. This makes the type default-constructible and copyable[^semiregular], as well as efficiently movable.
If the Rust type implements Clone, then the C++ type will be copyable:
Clone::clone.Clone::clone_from.Because the type is copyable, it is also movable, at worst by a copy operation.
If no logic occurs during destruction, because the type doesn't implement Drop, and none of its fields do, then the C++ type will be trivially-movable and trivially-destructible:
NOTE: All Copy types are guaranteed to be trivially move-constructible and destructible.
If the Rust type is Copy, then the moved-from object is guaranteed to hold its old value, and be valid for all operations.
Otherwise, the object is only valid for assignment and destruction, and the behavior of performing any other operation is undefined.
If the Rust type is not trivially movable and destructible, but implements Default, then the resulting C++ type will be (non-trivially) move constructible:
std::mem::take(): it copies the bytes to the new object, as if by a Rust move, and replaces the moved-from object with Default::default().In general, Crubit needs to be able to move objects as part of the implementation of pass-by-value, even in C++17, due to platform ABI restrictions. Even without this requirement, types are not very useful in C++ if they are not movable.
Unlike Rust, C++ has no “destructive move”. There is no way to change an object‘s location in memory, only to create a new object with the same value, and leave behind something in the old (still valid) object. Sometimes, what’s left behind is an identical copy of the object state: this is a copy operation, implemented by the C++ copy constructor or copy assignment operator. But sometimes, copying is expensive, and instead what we might leave behind is some kind of junk value. It still must be a valid object (at least so that its destructor and assignment operator can be invoked), but it might represent some invalid or moved-from state.
For example, to “move” a unique_ptr (the C++ equivalent of Box) from one variable to another, you copy the bytes, and then replace the old location with a special null value representing an unoccupied / moved-from unique_ptr. This is why unique_ptr must be nullable in the C++ type system: otherwise, it could not be moved!
[^semiregular]: The combination of default-constructible and copyable is so important for making types useful in C++ that it even has a name: “semiregular”