Skip to content
This repository was archived by the owner on Dec 31, 2025. It is now read-only.
This repository was archived by the owner on Dec 31, 2025. It is now read-only.

Mix-ins, manually registered #[methods] blocks applicable to multiple types #984

@chitoyuu

Description

@chitoyuu

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 impl blocks a type might have. It's natural to want to use multiple impl blocks 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: exportComponent: export (mod export, derive)featureAdds functionality to the library

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions