This is a draft for a potential MCP I had in mind for a while. I don't really intend to champion it any time soon, especially since I already have a MCP in the pipeline. I'm posting this in case this inspires anyone, and for future reference in dyn-safety discussions. Feedback welcome.
Proposal
NOTE: There has been some debate in the past as to what term best describes when a trait can be turned into a trait object with the syntax dyn MyTrait
. The current official term is "object safety", but it is widely agreed to be unsatisfying. We use "dyn-safety" in this document.
Summary
-
Progressively move the language towards having by-default dyn-safety for all traits.
-
To that end, add a new
?dyn
trait qualifier to indicate that template parameters accept a trait object. -
Loosen dyn-safety restrictions. Allow traits with associated functions, associated constants and generic methods to be made into trait objects (with some caveats related to
?dyn
). -
Plan a long-term roadmap for more restrictions to be removed over time, so that eventually
impl SomeTrait
anddyn SomeTrait
in function arguments are functionally equivalent.
Motivation
There are frequent requests to make Rust friendlier towards runtime polymorphism, aka vtables. Use cases include:
- Dynamic linking of Rust libraries.
- Polymorphisation of generic functions (eg compile a generic function once with existential types, and re-use that version every time the function is instanced).
- Reducing code size for low-memory systems.
On the other hand, in current Rust, a given trait MyTrait
can only be used dynamically if it is dyn-safe; specifically, if it obeys a specific set of rules. The aim of these rules is to allow developers to pass compile-time implementations and trait objects interchangeably to any interface specying that it wants an instance of MyTrait
.
This proposal claims that a different set of rules should be adopted, that still allows trait-objects and compile-time types to be used interchangeably, while expanding the scope of traits that can be used as trait objects.
The new set of rules would be:
-
For each trait, internally compute a dyn-safe subset of that trait. For instance, that subset would exclude associated functions and associated constants.
-
Add a
?dyn
trait qualifier to template parameters. Possible syntax:fn my_func<T: ?dyn MyTrait>(x: &T) { x.do_something(); }
-
If the
?dyn
qualifier isn't used, the current rules apply. -
If the
?dyn
qualifer is used,my_func
is only allowed to use the dyn-safe subset of MyTrait.
-
-
Allow trait objects to be created for traits with associated functions and associated constants.
-
If a trait has associated functions and associated constants, it can only be passed to a function expecting a
?dyn
version of that trait. -
Objects that are currently dyn-safe can still be passed as normal.
-
-
Allow trait objects to have generic methods and be dyn-safe, if every generic type argument is bound to a dyn-safe trait or the dyn-safe subset of a trait. Example:
trait DebugPrinter { // DebugPrint is dyn-safe, because std::fmt::Debug is dyn-safe fn print_debug<D: std::fmt::Debug>(&mut self, data: &D); }
- In that case, a default implentation is generated that takes a trait object for every generic type the method expects.
This new set of rules would allow trait objects to be made for a wider set of traits (traits with associated functions/constants, traits with generic methods) with no modification of existing code. In particular, this would allow developers to use traits in existing libraries that weren't designed with trait objects in mind, in contexts where trait objects are necessary.
NOTE: This proposal deliberately doesn't mention Sized
. It's written with the assumption that, if/when RFC-1909 and RFC-2884 are implemented, size will matter less to the language over time, and syntax will eventually be changed accordingly. Strictly speaking, the examples would probably need + ?Sized
annotations to compile, barring an edition change.
Breaking changes
None that I can see.
This feature should be strictly additive.
Future improvements
-
Have
impl MyTrait
arguments use the dyn-safe subset of MyTrait by default (breaking change). -
Add a syntax to access associated functions/constants from a trait object (eg
<typeof my_trait_object>::static_method(...)
); this would require that additional data be stored in the vtable. -
Perform the step above automatically in cases where the relevant vtable can be trivially found from an argument.