Presently there is no well-defined way of representing a dynamic dispatch object with a stable layout (Aside from defining the layout yourself). This means that rust is not a suitable language for writing runtime modular apis, requiring the use of a language like C++ for automatic martialling of these apis, or a manual COM-Like structure for the VTable. This can be inefficient for development. I propose an overload of the #[repr(C)] attrbute, to apply to traits, which will allow for the use of trait objects (and all kinds of well-behaved pointers to such trait objects) across module bounderies, and potentially (with some careful specification) accross languages.
Applying repr(C) to a trait has a few effects. First, all trait methods that do not have an explicit abi specification will be declared as though with extern"C"
. Second, pointers to all trait objects which contains at most one non-marker trait requirement, and that trait is a repr(C) trait, will be stablely guaranteed to conform to the layout as defined by the TraitObject
structure (which is presently only unstable, but could be stablized under this with a narrowed guarantee). A marker trait is defined as any trait which neither requires nor provides any functions and has no supertraits which are not marker traits (Send, Sync, and Unpin are marker traits, but Any is not, nor is Copy (it requires Clone)).
Additionally, the vtable layout for such pointers is defined here as well, and is layout compatible with the following exposition-only repr(C) structure:
#[repr(C)]
struct VTable{
size: usize,
align: usize,
destroy: Option<unsafe extern"C" fn (*c_void)->()>,
dealloc: Option<unsafe extern"C" fn (*c_void,usize)->()>,
vfns...: Option<unsafe? extern"C" fn(*c_void,<fn_signature>...)-><return-type>>
};
Where vfns
is the virtual function list for the non-marker trait, in declaration order.
The destroy
and dealloc
members perform the operations of drop_in_place
and a deallocation respectively, but restore the lost type information. The dealloc
operation is to help support similar constructs in other languages to things like Box<T>
which is a smart pointer, that is layed out exactly like a raw pointer (With aliasing requirements). Because of this, I recommend that the creation of a Box<dyn Trait>
fill the dealloc
item in the VTable, and the drop
operation of a Box<dyn Trait>
call the dealloc
item (Both when Trait
is a repr(C) trait). This allows for Boxes of repr(C) traits to soundly cross dso bounderies, which may have a different System or "Global" allocator (because of linking), as well as potentially allowing similarily specified constructs in different languages which are defined in the same manner here.
Questions I'd like to resolve before submitting as an RFC:
- Should this overload the repr(C) attribute, or should a new attribute be created for this purpose. C does not have a concept of virtual dispatch, and the closest is the Itanium C++ ABI, which would require more effort to make applicable to rust traits. In current practice,
repr(C)
is more of "Guarantee a stable, well specified layout forever" then a "do exactly what C does", when you consider the UCG rules reguarding rust's enums. - Cross language dispatch is definately a goal here, and I am working on a different language which provides the exact same feature set. However, whether or not it should be a primary point of this RFC is unresolved presently.
Unresolved questions which can be resolved now or during the RFC process:
- Reguarding marker traits, presently in this trait objects which only have marker trait requirements would be subject to the layout rules. Would this be something that would be fine to specify, or should that limit be lifted?
- Would it be reasonable to specify that
Drop
is a marker trait, despite requiring thedrop
item (asdrop
is covered by thedestroy
item in the VTable) - Should all of the functions in the trait be automatically
extern"C"
, or should it just be the VTable entries. - How should composed traits work w/ respect to repr(C) traits.
- Should it be a warning or error to put
repr(C)
on a trait that is not object-safe?