Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#[export] fails to compile for Gd types #197

Closed
KevinThierauf opened this issue Mar 21, 2023 · 4 comments · Fixed by #198
Closed

#[export] fails to compile for Gd types #197

KevinThierauf opened this issue Mar 21, 2023 · 4 comments · Fixed by #198
Labels
c: register Register classes, functions and other symbols to GDScript feature Adds functionality to the library

Comments

@KevinThierauf
Copy link
Contributor

KevinThierauf commented Mar 21, 2023

The following code fails to compile:

#[derive(GodotClass)]
#[class(base = Node)]
pub struct ExampleNode {
    #[base]
    base: Base<Node>,
    #[export]
    material: Gd<Material>,
}

#[godot_api]
impl ExampleNode {}

#[godot_api]
impl NodeVirtual for ExampleNode {
    fn init(base: Base<Self::Base>) -> Self {
        todo!()
    }
}

Using #[export] compiles fine for other types (e.g. int) but when using Gd the following error is provided:
error[E0507]: cannot move out of self.material which is behind a shared reference
--> src\example.rs:4:10
|
23 | #[derive(GodotClass)]
| ^^^^^^^^^^ move occurs because self.material has type godot::prelude::Gd<Material>, which does not implement the Copy trait
|
= note: this error originates in the derive macro GodotClass (in Nightly builds, run with -Z macro-backtrace for more info)

@KevinThierauf KevinThierauf changed the title #[export] fails to compile for Gd types #[export] fails to compile for Gd types Mar 21, 2023
@Bromeon Bromeon added bug c: register Register classes, functions and other symbols to GDScript labels Mar 21, 2023
@ttencate
Copy link
Contributor

I was aware of this while writing the code, but thanks for reporting because now we have a good place to discuss it :)

The autogenerated getters cannot move out of the field, so they need some way to create a copy of the value. For types that implement Copy, this happens automatically. For other types we need to generate code that makes a copy explicitly; these types are at least Gd, Array and Dictionary, but might include Signal and Callable too (would need to check).

Note that derive macro doesn't know the type of the field, only the type's path name in the current scope. In particular, it cannot know whether Copy, Clone or some other trait is supported on that type (unless we match on a fixed list of type names, but this is brittle). Therefore, we need some common trait that's implemented by all types that can be returned from an autogenerated getter.

The standard Rust way to do that is, of course, the Clone trait.

The objection to implementing Clone on Gd is, that users might confuse this with cloning the value that the Gd points to. But there is precedent in the standard library: Rc and Arc implement such a Clone operation, and explicitly recommend that you call it as Rc::clone(&foo) rather than foo.clone() for this very reason. In a similar discussion on gdnative, there were no serious objections to implementing Clone for their Ref type (rough equivalent of Gd in gdext).

The two other types that act as references are Array and Dictionary. Having a Clone implementation that just copies a reference is... unfortunate. But we can document this clearly, and if people are familiar with these types in GDScript, they might already be aware of the fact that it doesn't behave like a native Rust type. One interesting alternative that came up previously is to wrap these types in a sort of smart pointer of their own, e.g. Ref<Array>. That would allow Ref::clone(&array) for explicit cloning of the reference, at the cost of making these types a bit more verbose.

The alternative to Clone is that we create our own trait, let's say Gettable.

Because trait specialization is not yet stable, Gettable would have to be implemented for all core types. This could be done with a macro. For value types it would just invoke Copy or Clone; for reference types it would call the current implementation of Share::share() instead. It's not a lot of work to implement or maintain, but it's yet another concept exposed in our public API that needs to be explained.

@Bromeon
Copy link
Member

Bromeon commented Mar 22, 2023

Great writeup!

But we can document this clearly, and if people are familiar with these types in GDScript, they might already be aware of the fact that it doesn't behave like a native Rust type.

I think that's the key takeaway. One way to see Clone is: it does what GDScript does with = or argument passing. If it's by-reference, then Clone will not do a deep-copy, either.

Apart from that, we're not yet sure if Clone is the correct abstraction (gdnative has Export for it, with supertrait ToVariant, but the exporting mechanism is quite different). It might help keep things simple though, without the need for dedicated Export or Share traits...

@ttencate
Copy link
Contributor

ttencate commented Mar 22, 2023

I sent a PR with the Export trait, formerly known as Gettable (#198). But I have a branch that replaces Share by Clone as well.

Having our own trait makes it a bit more controllable, perhaps? For example, Signal might eventually implement Clone but it should not be #[export]ed (we have #[signal] for that).

@Bromeon Bromeon added feature Adds functionality to the library and removed bug labels Mar 22, 2023
@Bromeon
Copy link
Member

Bromeon commented Mar 22, 2023

(Relabeled as feature, because part of #[export] was not yet implemented. Export is that feature.)

@bors bors bot closed this as completed in eb30a8b Mar 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: register Register classes, functions and other symbols to GDScript feature Adds functionality to the library
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants