Summary
Allow associated consts in object-safe traits, by adding the const in the trait vtable.
Motivation
Today, we can emulate having an associated const in an object-safe manner by wrapping it in a function:
trait ObjectSafe {
fn get_constant(&self) -> &'static str;
}
However, this has several limitations:
- Getting the constant from a
dyn ObjectSafe
requires a vcall, which has a runtime cost - The vcall is opaque to the compiler, so it removes many optimization opportunity (like knowing that this is actually a pure function)
- The function could actually be impure, which would be misleading for a programmer
Adding the constant (or a reference to it) directly in the vtable would remove those limitations. Additionally, this limitation feels a bit arbitrary, so removing it would make trait object feel more first-class and easier to teach.
Design
Associated constants are allowed in object-safe traits. The constant in included in vtables so it can be retrieved at runtime from a dyn
reference.
For example, the trait Any
could be changed as:
trait Any {
const TYPE_ID: TypeId = TypeId::of::<Self>();
// Kept for backward compatibity
fn type_id(&self) -> TypeId {
TypeId::of::<Self>()
}
}
impl dyn Any + 'static {
fn is::<T: Any>(&self) -> bool {
// Placeholder syntax to retrieve a constant from a value
// This does not involve a vcall, only a vtable lookup
self@@TYPE_ID == T::TYPE_ID
}
fn downcast_ref::<T: Any>(&self) -> Option<&T> {
self.is::<T>.then(|| unsafe { /* ... */ })
}
}
Its vtable would look like this:
#[repr(C)]
struct Vtable {
drop_in_place: unsafe fn(*mut ()),
size: usize,
align: usize,
TYPE_ID: TypeId, // This is new
type_id: unsafe fn(*const ()) -> TypeId,
}
If the constant is too large, a reference to it could be stored in the vtable instead, which can avoid bloating the program if it is shared by several concrete types.
Interaction with non-copy types
I see two possibilities here:
- Using a constant with (placeholder)
@@
syntax create a new byte-copied value, as does using a "real"const
now. - Allow only
Copy
types, others can only be used through a ref. This is more conservative, and forward compatible with the former proposition, but feels somewhat arbitrary and harder to teach.
I think the former possibility is the best, and it feels coherent with how const
values work today.
Drawbacks
Syntax
I could not find any syntax which feels nice and inline with what we have today, and clear about the fact that we're just copying a constant (eventually from a vtable).
Vtable bloat
A common criticism of trait object is the size they take in the final binary. This proposition could increase it even more.
However, this proposition does not affect the size of vtables if not used, and for large values a pointer can be stored in the vtable instead of the value itself.