-
Notifications
You must be signed in to change notification settings - Fork 211
Mix-ins, manually registered #[methods] blocks applicable to multiple types #984
Description
It has been a long-standing limitation that only one #[methods] blocks can be exposed for any given type. There are many reasons why it's desirable to remove this limitation:
- Generally, Rust puts no restriction on the number of
implblocks a type might have. It's natural to want to use multipleimplblocks for code organization. - It's nice to be able use traits to group common functionality together, so a consistent GDScript interface can be enforced by the compiler.
- Traits can also potentially be used to simplify the process of correctly overriding an engine virtual method (e.g.
_ready). Some interesting experiments are being done in the GDExtension bindings, centered on a curated set of methods.
In light of #983, there's also a new important use for the ability: to declare multiple impl blocks with different bounds, which is critical for the usefulness of generic types.
Design
The public interface would be fairly simple, and compatible with the existing API:
#[derive(Clone, NativeClass)]
#[register_with(register_mixins)]
struct Foo;
#[methods(as = "InherentMixin")]
impl Foo {
#[method]
fn foo() {}
}
#[methods(as = "TraitMixin")]
impl<C> MyDeepCloneTrait for C
where
C: NativeClass + Clone,
{
#[method]
fn duplicate(&self) -> Instance<C> {
Instance::emplace(self.clone()).into_shared()
}
}
fn register_mixins(builder: &ClassBuilder<Foo>) {
builder.mixin::<InherentMixin>();
builder.mixin::<TraitMixin>();
}Here, the presence of the #[methods(as)] argument tells the macro to generate a mixin type called MyMixin, instead of an ordinary NativeClassMethods implementation. A mixin type is an opaque type with no public interface, that serves as a handle to pass into ClassBuilder::mixin, which will then perform registration.
It's important that a type is generated instead of a plain register function here: the latter does not have a nameable type. While it doesn't make a lot difference for simple use cases with manual registration, they can be useful for type-based shenanigans in the future, should we find ourselves having to expand the internal interface beyond just a single function.
Internally, #[methods] (without the as argument) could be refactored to generate an unnameable mixin that is immediately wrapped into a NativeClassMethods. This behavior can be removed in a future breaking version where we introduce automatic registration for inherent impls (on supported platforms, see #350), and remove the NativeClassMethods trait completely.