Skip to content

Document DynGd<_, D> type inference. #1142

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

Merged
merged 2 commits into from
May 1, 2025

Conversation

Yarwin
Copy link
Contributor

@Yarwin Yarwin commented Apr 27, 2025

Closes: #1026

  • Document when type inference works and when it doesn't.
  • Going with lilizoey suggestion – Explicitly enforce Trait: 'static for DynGd<T, Trait> and AsDyn<Trait> (we have no means to track lifetimes of given object either way which means it might be invalidated at any point – albeit creating such DynGd is hard, since GodotClass must be 'static either way).

Despite messing with it for some time I couldn't force compiler to properly infer given types – thus I limited myself to merely documenting it 😒.

@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1142

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Apart from the : 'static bound users need to add, are there any other ergonomic downsides of this change?

: 'static is arguably a bit annoying since that is very unusual for most traits, but I guess if it makes inference clearer, that's probably worth it 🤔

Comment on lines 180 to 184
/// // Type can't be inferred.
/// // Would result in confusing compilation error
/// // since compiler would try to enforce 'static **lifetime** on our reference.
/// // let mut dyn_gd = Monster::new_gd().into_dyn();
/// // no_inference(&mut *dyn_gd.dyn_bind_mut());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a separate block with compile_fail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, instead of **lifetime**, you could write *lifetime* (&'static mut ...).

Comment on lines 168 to 189
/// fn deal_damage(h: &mut dyn Health) { /* ... */ }
///
/// fn no_inference(i: &mut dyn NoInference) { /* ... */ }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// fn deal_damage(h: &mut dyn Health) { /* ... */ }
///
/// fn no_inference(i: &mut dyn NoInference) { /* ... */ }
/// // Two example functions accepting trait object, to check type inference.
/// fn deal_damage(h: &mut dyn Health) { /* ... */ }
/// fn no_inference(i: &mut dyn NoInference) { /* ... */ }

Comment on lines 142 to 143
/// If class implements more than one `#[godot_dyn]` trait, type inference only works when the trait we dereference to explicitly declares
/// `'static` as its supertrait.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// If class implements more than one `#[godot_dyn]` trait, type inference only works when the trait we dereference to explicitly declares
/// `'static` as its supertrait.
/// If a class implements more than one `AsDyn` relation (usually via `#[godot_dyn]`), type inference will only work when the trait
/// used for `D` explicitly declares a `: 'static` bound.

///
/// If class implements more than one `#[godot_dyn]` trait, type inference only works when the trait we dereference to explicitly declares
/// `'static` as its supertrait.
/// Otherwise, if only one `#[godot_dyn]` trait is implemented for a given class, the type can be properly inferred.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Otherwise, if only one `#[godot_dyn]` trait is implemented for a given class, the type can be properly inferred.
/// Otherwise, if only one `impl AsDyn` is present for a given class, the type can always be inferred.

@Yarwin Yarwin force-pushed the document-type-inference-for-dyngd branch from 86bd36a to a098d0a Compare April 29, 2025 10:13
@Yarwin
Copy link
Contributor Author

Yarwin commented Apr 29, 2025

Apart from the : 'static bound users need to add, are there any other ergonomic downsides of this change?

Personally I haven't found any downsides 🤔.

I'll try to make another deep dive in a month or two to figure out what is going on – but for now, especially for 0.3, I just want to have this quirk documented since compile errors related to this case are very confusing if one doesn't know the internals.

I also updated comment related to #1145 (I was too focused to make it work ASAP to take care of such details 😅).

@Yarwin Yarwin force-pushed the document-type-inference-for-dyngd branch from a098d0a to 901e79a Compare April 29, 2025 10:28
@Yarwin Yarwin added documentation Improvements or additions to documentation c: core Core components labels Apr 30, 2025
Comment on lines 62 to 63
// Type can be inferred because `Health` explicitly declares
// 'static as its supertrait.
let mut obj = original_obj.clone().into_dyn();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Type can be inferred because `Health` explicitly declares
// 'static as its supertrait.
let mut obj = original_obj.clone().into_dyn();
// Type can be inferred because `Health` explicitly declares a 'static bound.
let mut obj = original_obj.clone().into_dyn();

Line width 140-150 (see code style)

Also, 'static isn't a trait, so it's not a supertrait either.

Comment on lines +140 to +162
/// # Type inference
///
/// If a class implements more than one `AsDyn<D>` relation (usually via `#[godot_dyn]`), type inference will only work when the trait
/// used for `D` explicitly declares a `: 'static` bound.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section could probably come last, no?

Exporting is definitely more important (arguably also more than dyn-re-enrichment).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, I think you are right – albeit I'm finding Polymorphic dyn re-enrichment a bit more important 🤔 (reasoning – the first two sections explain how to use it and how it works, while following ones focus on documenting various quirks)

I put Type inference as the last section

/// fn deal_damage(h: &mut dyn Health) { /* ... */ }
/// fn no_inference(i: &mut dyn NoInference) { /* ... */ }
///
/// // Type can be inferred since `static supertrait is explicitly declared for Health trait.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// // Type can be inferred since `static supertrait is explicitly declared for Health trait.
/// // Type can be inferred since 'static bound is explicitly declared for Health trait.

No backtick, but apostrophe -- and no supertrait

@@ -428,7 +451,8 @@ fn dyn_gd_multiple_traits() {
// ----------------------------------------------------------------------------------------------------------------------------------------------
// Example symbols

trait Health {
// 'static supertrait must be explicitly declared to make type inference work.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 'static supertrait must be explicitly declared to make type inference work.
// 'static bound must be explicitly declared to make type inference work.


// Not recommended – for presentational purposes only.
// Works because 'static bound on type is enforced in function signature.
// i.e. This wouldn't work with fn get_instance_id(...).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// i.e. This wouldn't work with fn get_instance_id(...).
// I.e. this wouldn't work with fn get_instance_id(...).

/// # Safety
///
/// - `r_is_valid` must be assignable.
/// - `r_is_valid` comes uninitialized and must be set.
pub unsafe extern "C" fn to_string<T: cap::GodotToString>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Safety sections are the requirements a caller must fulfill, not the requirements on the function implementation (2nd bullet point).

But let's remove it altogether, as only Godot calls it and none of the other callbacks has it. You can still mention the fact that

is_valid comes uninitialized and must be set by this function

as a regular (non-safety) comment.

pub unsafe extern "C" fn to_string<T: cap::GodotToString>(
instance: sys::GDExtensionClassInstancePtr,
is_valid: *mut sys::GDExtensionBool,
r_is_valid: *mut sys::GDExtensionBool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert the rename. While it follows Godot convention, it's inconsistent with all the other callback signatures here. It's even inconsistent with the other parameter in this exact function (would be p_instance in Godot).

Yarwin added 2 commits April 30, 2025 12:07
- Document when type inference works and when it doesn't.
- Explicitly enforce `Trait: 'static` for `DynGd<T, D>` and `AsDyn<D>` – mostly for clarity, creating non-static `DynGd<T, D>` was already pretty much impossible, and `T` – GodotClass – must be 'static either way.
@Yarwin Yarwin force-pushed the document-type-inference-for-dyngd branch from 901e79a to 3529708 Compare April 30, 2025 10:10
@Bromeon Bromeon added this to the 0.3 milestone May 1, 2025
@Bromeon Bromeon added this pull request to the merge queue May 1, 2025
Merged via the queue into godot-rust:master with commit 2bc0872 May 1, 2025
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: core Core components documentation Improvements or additions to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Document/improve DynGd<_, D> type inference
3 participants