Skip to content

Commit 2bc0872

Browse files
authored
Merge pull request #1142 from Yarwin/document-type-inference-for-dyngd
Document `DynGd<_, D>` type inference.
2 parents dc9ba89 + 3529708 commit 2bc0872

File tree

6 files changed

+120
-20
lines changed

6 files changed

+120
-20
lines changed

godot-core/src/obj/dyn_gd.rs

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,75 @@ use std::{fmt, ops};
155155
///
156156
/// `#[export]` for a `DynGd<T, D>` allows you to limit the available choices to implementors of a given trait `D` whose base inherits the specified `T`
157157
/// (for example, `#[export] Option<DynGd<Resource, dyn MyTrait>>` won't include Rust classes with an Object base, even if they implement `MyTrait`).
158+
///
159+
/// # Type inference
160+
///
161+
/// If a class implements more than one `AsDyn<D>` relation (usually via `#[godot_dyn]`), type inference will only work when the trait
162+
/// used for `D` explicitly declares a `: 'static` bound.
163+
/// Otherwise, if only one `impl AsDyn` is present for a given class, the type can always be inferred.
164+
///
165+
/// ```no_run
166+
/// # use godot::prelude::*;
167+
/// trait Health: 'static { /* ... */ }
168+
///
169+
/// // Exact equivalent to:
170+
/// trait OtherHealth
171+
/// where
172+
/// Self: 'static
173+
/// { /* ... */ }
174+
///
175+
/// trait NoInference { /* ... */ }
176+
///
177+
/// #[derive(GodotClass)]
178+
/// # #[class(init)]
179+
/// struct Monster { /* ... */ }
180+
///
181+
/// #[godot_dyn]
182+
/// impl Health for Monster { /* ... */ }
183+
///
184+
/// #[godot_dyn]
185+
/// impl NoInference for Monster { /* ... */ }
186+
///
187+
/// // Two example functions accepting trait object, to check type inference.
188+
/// fn deal_damage(h: &mut dyn Health) { /* ... */ }
189+
/// fn no_inference(i: &mut dyn NoInference) { /* ... */ }
190+
///
191+
/// // Type can be inferred since 'static bound is explicitly declared for Health trait.
192+
/// let mut dyn_gd = Monster::new_gd().into_dyn();
193+
/// deal_damage(&mut *dyn_gd.dyn_bind_mut());
194+
///
195+
/// // Otherwise type can't be properly inferred.
196+
/// let mut dyn_gd = Monster::new_gd().into_dyn::<dyn NoInference>();
197+
/// no_inference(&mut *dyn_gd.dyn_bind_mut());
198+
/// ```
199+
///
200+
/// ```compile_fail
201+
/// # use godot::prelude::*;
202+
/// trait Health { /* ... */ }
203+
///
204+
/// trait OtherTrait { /* ... */ }
205+
///
206+
/// #[derive(GodotClass)]
207+
/// # #[class(init)]
208+
/// struct Monster { /* ... */ }
209+
/// #[godot_dyn]
210+
/// impl Health for Monster { /* ... */ }
211+
/// #[godot_dyn]
212+
/// impl OtherTrait for Monster { /* ... */ }
213+
///
214+
/// fn deal_damage(h: &mut dyn Health) { /* ... */ }
215+
///
216+
/// // Type can't be inferred.
217+
/// // Would result in confusing compilation error
218+
/// // since compiler would try to enforce 'static *lifetime* (&'static mut ...) on our reference.
219+
/// let mut dyn_gd = Monster::new_gd().into_dyn();
220+
/// deal_damage(&mut *dyn_gd.dyn_bind_mut());
221+
/// ```
158222
pub struct DynGd<T, D>
159223
where
160224
// T does _not_ require AsDyn<D> here. Otherwise, it's impossible to upcast (without implementing the relation for all base classes).
161225
T: GodotClass,
162-
D: ?Sized,
226+
D: ?Sized + 'static,
163227
{
164228
// Potential optimizations: use single Gd; use Rc/Arc instead of Box+clone; store a downcast fn from Gd<T>; ...
165229
obj: Gd<T>,
@@ -169,7 +233,7 @@ where
169233
impl<T, D> DynGd<T, D>
170234
where
171235
T: AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
172-
D: ?Sized,
236+
D: ?Sized + 'static,
173237
{
174238
pub(crate) fn from_gd(gd_instance: Gd<T>) -> Self {
175239
let erased_obj = Box::new(gd_instance.clone());
@@ -185,7 +249,7 @@ impl<T, D> DynGd<T, D>
185249
where
186250
// Again, T deliberately does not require AsDyn<D> here. See above.
187251
T: GodotClass,
188-
D: ?Sized,
252+
D: ?Sized + 'static,
189253
{
190254
/// Acquires a shared reference guard to the trait object `D`.
191255
///
@@ -297,7 +361,7 @@ where
297361
impl<T, D> DynGd<T, D>
298362
where
299363
T: GodotClass + Bounds<Memory = bounds::MemManual>,
300-
D: ?Sized,
364+
D: ?Sized + 'static,
301365
{
302366
/// Destroy the manually-managed Godot object.
303367
///
@@ -311,7 +375,7 @@ where
311375
impl<T, D> Clone for DynGd<T, D>
312376
where
313377
T: GodotClass,
314-
D: ?Sized,
378+
D: ?Sized + 'static,
315379
{
316380
fn clone(&self) -> Self {
317381
Self {
@@ -355,7 +419,7 @@ where
355419
impl<T, D> ops::Deref for DynGd<T, D>
356420
where
357421
T: GodotClass,
358-
D: ?Sized,
422+
D: ?Sized + 'static,
359423
{
360424
type Target = Gd<T>;
361425

@@ -367,7 +431,7 @@ where
367431
impl<T, D> ops::DerefMut for DynGd<T, D>
368432
where
369433
T: GodotClass,
370-
D: ?Sized,
434+
D: ?Sized + 'static,
371435
{
372436
fn deref_mut(&mut self) -> &mut Self::Target {
373437
&mut self.obj
@@ -398,7 +462,10 @@ where
398462
// ----------------------------------------------------------------------------------------------------------------------------------------------
399463
// Type erasure
400464

401-
trait ErasedGd<D: ?Sized> {
465+
trait ErasedGd<D>
466+
where
467+
D: ?Sized + 'static,
468+
{
402469
fn dyn_bind(&self) -> DynGdRef<D>;
403470
fn dyn_bind_mut(&mut self) -> DynGdMut<D>;
404471

@@ -408,7 +475,7 @@ trait ErasedGd<D: ?Sized> {
408475
impl<T, D> ErasedGd<D> for Gd<T>
409476
where
410477
T: AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
411-
D: ?Sized,
478+
D: ?Sized + 'static,
412479
{
413480
fn dyn_bind(&self) -> DynGdRef<D> {
414481
DynGdRef::from_guard::<T>(Gd::bind(self))
@@ -549,7 +616,7 @@ where
549616
impl<T, D> GodotConvert for OnEditor<DynGd<T, D>>
550617
where
551618
T: GodotClass,
552-
D: ?Sized,
619+
D: ?Sized + 'static,
553620
{
554621
type Via = Option<<DynGd<T, D> as GodotConvert>::Via>;
555622
}

godot-core/src/obj/gd.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ impl<T: GodotClass> Gd<T> {
482482
pub fn into_dyn<D>(self) -> DynGd<T, D>
483483
where
484484
T: crate::obj::AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
485-
D: ?Sized,
485+
D: ?Sized + 'static,
486486
{
487487
DynGd::<T, D>::from_gd(self)
488488
}

godot-core/src/obj/guards.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,10 @@ pub struct DynGdRef<'a, D: ?Sized> {
104104
cached_ptr: *const D,
105105
}
106106

107-
impl<'a, D: ?Sized> DynGdRef<'a, D> {
107+
impl<'a, D> DynGdRef<'a, D>
108+
where
109+
D: ?Sized + 'static,
110+
{
108111
#[doc(hidden)]
109112
pub fn from_guard<T: AsDyn<D>>(guard: GdRef<'a, T>) -> Self {
110113
let obj = &*guard;
@@ -147,7 +150,10 @@ pub struct DynGdMut<'a, D: ?Sized> {
147150
cached_ptr: *mut D,
148151
}
149152

150-
impl<'a, D: ?Sized> DynGdMut<'a, D> {
153+
impl<'a, D> DynGdMut<'a, D>
154+
where
155+
D: ?Sized + 'static,
156+
{
151157
#[doc(hidden)]
152158
pub fn from_guard<T: AsDyn<D>>(mut guard: GdMut<'a, T>) -> Self {
153159
let obj = &mut *guard;

godot-core/src/obj/traits.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ unsafe impl<T: GodotClass> Inherits<T> for T {}
148148
// Note: technically, `Trait` doesn't _have to_ implement `Self`. The Rust type system provides no way to verify that a) D is a trait object,
149149
// and b) that the trait behind it is implemented for the class. Thus, users could any another reference type, such as `&str` pointing to a field.
150150
// This should be safe, since lifetimes are checked throughout and the class instance remains in place (pinned) inside a DynGd.
151-
pub trait AsDyn<Trait: ?Sized>: GodotClass {
151+
pub trait AsDyn<Trait>: GodotClass
152+
where
153+
Trait: ?Sized + 'static,
154+
{
152155
fn dyn_upcast(&self) -> &Trait;
153156
fn dyn_upcast_mut(&mut self) -> &mut Trait;
154157
}

godot-core/src/registry/callbacks.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,15 @@ pub unsafe extern "C" fn to_string<T: cap::GodotToString>(
192192
out_string: sys::GDExtensionStringPtr,
193193
) {
194194
// Note: to_string currently always succeeds, as it is only provided for classes that have a working implementation.
195-
// is_valid output parameter thus not needed.
196195

197196
let storage = as_storage::<T>(instance);
198197
let instance = storage.get();
199198
let string = T::__godot_to_string(&*instance);
200199

201200
// Transfer ownership to Godot
202201
string.move_into_string_ptr(out_string);
202+
203+
// Note: is_valid comes uninitialized and must be set.
203204
*is_valid = sys::conv::SYS_TRUE;
204205
}
205206

itest/rust/src/object_tests/dyn_gd_test.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,47 @@ fn dyn_gd_creation_deref() {
5656

5757
#[itest]
5858
fn dyn_gd_creation_deref_multiple_traits() {
59-
let obj = foreign::NodeHealth::new_alloc();
60-
let original_id = obj.instance_id();
59+
let original_obj = foreign::NodeHealth::new_alloc();
60+
let original_id = original_obj.instance_id();
6161

62-
// `dyn Health` must be explicitly declared if multiple AsDyn<...> trait implementations exist.
63-
let mut obj = obj.into_dyn::<dyn Health>();
62+
// Type can be inferred because `Health` explicitly declares a 'static bound.
63+
let mut obj = original_obj.clone().into_dyn();
6464

6565
let dyn_id = obj.instance_id();
6666
assert_eq!(dyn_id, original_id);
6767

6868
deal_20_damage(&mut *obj.dyn_bind_mut());
6969
assert_eq!(obj.dyn_bind().get_hitpoints(), 80);
7070

71+
// Otherwise type inference doesn't work and type must be explicitly declared.
72+
let mut obj = original_obj
73+
.clone()
74+
.into_dyn::<dyn InstanceIdProvider<Id = InstanceId>>();
75+
assert_eq!(get_instance_id(&mut *obj.dyn_bind_mut()), original_id);
76+
77+
// Not recommended – for presentational purposes only.
78+
// Works because 'static bound on type is enforced in function signature.
79+
// I.e. this wouldn't work with fn get_instance_id(...).
80+
let mut obj = original_obj.into_dyn();
81+
get_instance_id_explicit_static_bound(&mut *obj.dyn_bind_mut());
82+
7183
obj.free();
7284
}
7385

7486
fn deal_20_damage(h: &mut dyn Health) {
7587
h.deal_damage(20);
7688
}
7789

90+
fn get_instance_id(i: &mut dyn InstanceIdProvider<Id = InstanceId>) -> InstanceId {
91+
i.get_id_dynamic()
92+
}
93+
94+
fn get_instance_id_explicit_static_bound(
95+
i: &mut (dyn InstanceIdProvider<Id = InstanceId> + 'static),
96+
) -> InstanceId {
97+
i.get_id_dynamic()
98+
}
99+
78100
#[itest]
79101
fn dyn_gd_upcast() {
80102
let original = foreign::NodeHealth::new_alloc();
@@ -428,7 +450,8 @@ fn dyn_gd_multiple_traits() {
428450
// ----------------------------------------------------------------------------------------------------------------------------------------------
429451
// Example symbols
430452

431-
trait Health {
453+
// 'static bound must be explicitly declared to make type inference work.
454+
trait Health: 'static {
432455
fn get_hitpoints(&self) -> u8;
433456

434457
fn deal_damage(&mut self, damage: u8);

0 commit comments

Comments
 (0)