Skip to content

Commit 4ccc62f

Browse files
committed
September 2025 dev update
1 parent a861c0a commit 4ccc62f

File tree

2 files changed

+361
-0
lines changed

2 files changed

+361
-0
lines changed
31.9 KB
Loading
Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
+++
2+
# Copyright (c) godot-rust; Bromeon and contributors.
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
title = "September 2025 dev update"
8+
authors = ["Yarwin", "Bromeon"]
9+
10+
[extra]
11+
summary = "v0.4 and recent developments"
12+
tags = ["dev-update"]
13+
+++
14+
15+
We just released godot-rust **v0.4**!
16+
17+
With it, we'd also like to highlight some of the major features and improvements since our last [dev update in May 2025][may-dev-update],
18+
both during the 0.3 cycle and the 0.4.0 release.
19+
20+
21+
## Properties and exports
22+
23+
The `register` module saw various improvements regarding properties and exports:
24+
25+
### Export groups and subgroups
26+
27+
Thanks to Yarwin, properties can now be grouped to organize them neatly in the Inspector dock, [just like in GDScript][gdscript-export-groups] ([#1214], [#1261]).
28+
29+
`#[export_group]` and `#[export_subgroup]` are the Rust equivalents of GDScript's `@export_group` and `@export_subgroup` annotations.
30+
31+
Something unconventional is that they affect multiple following fields, not just one field. We considered alternatives (repetition or struct splitting), but come at the cost of fast gamedev iteration, and many Godot users are already familiar with this pattern.
32+
33+
![car-export-groups.png][car-export-groups-img]
34+
35+
[car-export-groups-img]: car-export-groups.png
36+
37+
38+
<details>
39+
<summary><i>Expand to see code...</i></summary>
40+
41+
```rust
42+
#[derive(GodotClass)]
43+
#[class(init, base=Node)]
44+
struct MyNode {
45+
#[export_group(name = "Racer Properties")]
46+
#[export]
47+
nickname: GString,
48+
#[export]
49+
age: i64,
50+
51+
#[export_group(name = "Car Properties")]
52+
#[export_subgroup(name = "Car prints", prefix = "car_")]
53+
#[export]
54+
car_label: GString,
55+
#[export]
56+
car_number: i64,
57+
58+
#[export_subgroup(name = "Wheels/Front", prefix = "front_wheel")]
59+
#[export]
60+
front_wheel_strength: i64,
61+
#[export]
62+
front_wheel_mobility: i64,
63+
64+
#[export_subgroup(name = "Wheels/Rear", prefix = "rear_wheel_")]
65+
#[export]
66+
rear_wheel_strength: i64,
67+
#[export]
68+
rear_wheel_mobility: i64,
69+
70+
#[export_subgroup(name = "Wheels", prefix = "wheel_")]
71+
#[export]
72+
wheel_material: OnEditor<Gd<PhysicsMaterial>>,
73+
#[export]
74+
other_car_properties: GString,
75+
76+
// Use empty group name to break out from the group:
77+
#[export_group(name = "")]
78+
#[export]
79+
ungrouped_field: GString,
80+
}
81+
```
82+
</details>
83+
84+
### Phantom properties
85+
86+
The [`PhantomVar<T>`][api-phantomvar] field type enables ZST (zero-sized type) properties without backing fields, for dynamic properties that are computed on-the-fly or stored elsewhere. Thanks to ttencate for adding this in [#1261]!
87+
88+
```rust
89+
#[derive(GodotClass)]
90+
#[class(init, base=Node)]
91+
struct MyNode {
92+
#[var(get = get_computed_value)]
93+
computed_value: PhantomVar<i32>, // zero bytes
94+
}
95+
96+
#[godot_api]
97+
impl INode for MyNode {
98+
#[func]
99+
fn get_computed_value(&self) -> i32 { ... }
100+
}
101+
```
102+
103+
### Numeric export limits
104+
105+
For integer exports, a reasonable range is automatically inferred. Additionally, `#[export(range)]` literals are validated against field types at compile time ([#1320]).
106+
107+
```rust
108+
#[export(range = (0.0, 255.0))] // no longer compiles (float)
109+
int_property: i8,
110+
111+
#[export(range = (0, 128))] // doesn't compile either (out of range)
112+
int_property: i8,
113+
114+
#[export] // infers from i8 that range = (-128, 127)
115+
int_property: i32,
116+
```
117+
118+
[#1214]: https://github.com/godot-rust/gdext/pull/1214
119+
[#1261]: https://github.com/godot-rust/gdext/pull/1261
120+
[#1320]: https://github.com/godot-rust/gdext/pull/1320
121+
122+
[api-phantomvar]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.PhantomVar.html
123+
[gdscript-export-groups]: https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_exports.html#grouping-exports
124+
125+
## Easier callables
126+
127+
Several parts were improved on the `Callable` front:
128+
129+
### Type-safe deferred calls
130+
131+
A type-safe `call_deferred()` alternative, present as [`run_deferred()`][api-gd-rundeferred] and [`run_deferred_gd()`][api-gd-rundeferredgd] in the API, provides compile-time guarantees for deferred method calls. This eliminates string-based method names and runtime errors ([#1327], [#1332]).
132+
133+
```rust
134+
// Old way (string-based, error-prone):
135+
node.call_deferred("set_position", &[pos.to_variant()]);
136+
137+
// New way (type-safe):
138+
node.run_deferred(|obj| obj.set_position(pos));
139+
```
140+
141+
### Type-safe return types
142+
143+
Modern callable constructors like [`from_fn()`][api-callable-fromfn] support any return type implementing `ToGodot`, eliminating manual `Variant` conversion boilerplate ([#1346]).
144+
145+
```rust
146+
// in 0.3 (and deprecated in 0.4):
147+
let callable = Callable::from_local_fn("unit", |args| {
148+
do_sth(args);
149+
Ok(Variant::nil())
150+
});
151+
152+
// new in 0.4:
153+
let callable = Callable::from_fn("unit", |args| {
154+
do_sth(args);
155+
});
156+
```
157+
158+
[#1223]: https://github.com/godot-rust/gdext/pull/1223
159+
[#1327]: https://github.com/godot-rust/gdext/pull/1327
160+
[#1332]: https://github.com/godot-rust/gdext/pull/1332
161+
[#1344]: https://github.com/godot-rust/gdext/pull/1344
162+
[#1346]: https://github.com/godot-rust/gdext/pull/1346
163+
164+
[api-gd-rundeferred]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.run_deferred
165+
[api-gd-rundeferredgd]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.run_deferred_gd
166+
[api-callable-fromfn]: https://godot-rust.github.io/docs/gdext/master/godot/builtin/struct.Callable.html#method.from_fn
167+
[`Gd::linked_callable()`]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.linked_callable
168+
169+
## Signal enhancements
170+
171+
Since the introduction of signals in v0.3, several convenience APIs have been added.
172+
173+
Signals now offer disconnection ([#1198]):
174+
```rust
175+
let handle = self.signals().my_signal().connect(...);
176+
// Later that day:
177+
handle.disconnect();
178+
```
179+
180+
Thanks to Yarwin's work on `linked_callable()`, signals connected to a receiver object are automatically disconnected when the receiver is freed ([#1223]):
181+
182+
```rust
183+
let obj: Gd<MyClass> = ...;
184+
let handle = self.signals().my_signal().connect_other(&obj, ...);
185+
186+
obj.free(); // Auto-disconnects the signal.
187+
```
188+
189+
User ogapo enabled conversion to untyped signals for better Godot interop, with [`TypedSignal::to_untyped()`][api-typedsignal-tountyped] ([#1288]):
190+
191+
```rust
192+
let typed = self.signals().my_typed_signal();
193+
let untyped: Signal = typed.to_untyped();
194+
```
195+
196+
[#1198]: https://github.com/godot-rust/gdext/pull/1198
197+
[#1223]: https://github.com/godot-rust/gdext/pull/1223
198+
[#1288]: https://github.com/godot-rust/gdext/pull/1288
199+
200+
[api-typedsignal-tountyped]: https://godot-rust.github.io/docs/gdext/master/godot/register/struct.TypedSignal.html#method.to_untyped
201+
202+
## Ergonomics and developer experience
203+
204+
In good tradition, godot-rust has shipped a truckload of little tools to make everyday development more enjoyable.
205+
206+
### Class dispatching
207+
208+
No more tedious `try_cast()` cascades for explicit dynamic dispatch. The [`match_class!`][api-matchclass] macro allows dynamic class matching, similar to Rust's `match` keyword ([#1225]).
209+
210+
Thanks to sylbeth's work, the macro supports mutable bindings ([#1242]), optional fallback branches ([#1246]), and discard patterns ([#1252]):
211+
212+
```rust
213+
let simple_dispatch: i32 = match_class!(event, {
214+
button @ InputEventMouseButton => 1,
215+
motion @ InputEventMouseMotion => 2,
216+
action @ InputEventAction => 3,
217+
_ => 0, // Fallback.
218+
});
219+
```
220+
221+
### Generic packed arrays
222+
223+
Generic [`PackedArray<T>`][api-packedarray] abstracts over all specific packed array types, enabling code reuse across different array variants ([#1291]):
224+
225+
```rust
226+
fn format_packed_array<T>(array: &PackedArray<T>) -> String
227+
where T: PackedArrayElement {
228+
// ...
229+
}
230+
```
231+
232+
### Variant slices
233+
234+
The [`vslice!`][api-vslice] macro provides a concise way to create `&[Variant]` slices from heterogeneous values ([#1191]):
235+
236+
```rust
237+
// Old way:
238+
let args = &[1.to_variant(), "hello".to_variant(), vector.to_variant()];
239+
240+
// New way:
241+
let args = vslice![1, "hello", vector];
242+
```
243+
244+
This comes in handy for dynamic/reflection APIs like `Object::call()`.
245+
246+
### Engine API type safety
247+
248+
Arrays and dictionaries now offer runtime type introspection via [`ElementType`][api-elementtype] ([#1304]).
249+
250+
Lots of engine APIs have been made more type-safe; check out [#1315] to get an idea of the scope. In particular, many "intly-typed" method parameters have been replaced with enums or bitfields, no longer leaving you the guesswork of what values are expected.
251+
252+
```rust
253+
let s: Variant = obj.get_script();
254+
// now:
255+
let s: Option<Gd<Script>> = obj.get_script();
256+
257+
obj.connect_ex(...).flags(ConnectFlags::DEFERRED as u32).done();
258+
// now:
259+
obj.connect_flags(..., ConnectFlags::DEFERRED);
260+
```
261+
262+
### Negative indexing
263+
264+
The [`SignedRange`][api-signedrange] type provides negative indexing for arrays and strings ([#1300]).
265+
266+
### Enum and bitfield introspection
267+
268+
Programmatic access to all enum and bitfield values enables runtime introspection of Godot's type system ([#1232]).
269+
270+
```rust
271+
// Access all enum constants.
272+
let constants = MyEnum::all_constants();
273+
let values = MyEnum::values(); // Distinct values only.
274+
275+
for (name, value) in constants {
276+
add_dropdown_option(name, value);
277+
}
278+
```
279+
280+
[#1191]: https://github.com/godot-rust/gdext/pull/1191
281+
[#1225]: https://github.com/godot-rust/gdext/pull/1225
282+
[#1232]: https://github.com/godot-rust/gdext/pull/1232
283+
[#1242]: https://github.com/godot-rust/gdext/pull/1242
284+
[#1246]: https://github.com/godot-rust/gdext/pull/1246
285+
[#1252]: https://github.com/godot-rust/gdext/pull/1252
286+
[#1291]: https://github.com/godot-rust/gdext/pull/1291
287+
[#1300]: https://github.com/godot-rust/gdext/pull/1300
288+
[#1304]: https://github.com/godot-rust/gdext/pull/1304
289+
[#1315]: https://github.com/godot-rust/gdext/pull/1315
290+
291+
[api-matchclass]: https://godot-rust.github.io/docs/gdext/master/godot/classes/macro.match_class.html
292+
[api-vslice]: https://godot-rust.github.io/docs/gdext/master/godot/builtin/macro.vslice.html
293+
[api-packedarray]: https://godot-rust.github.io/docs/gdext/master/godot/builtin/struct.PackedArray.html
294+
[api-signedrange]: https://godot-rust.github.io/docs/gdext/master/godot/meta/trait.SignedRange.html
295+
[api-elementtype]: https://godot-rust.github.io/docs/gdext/master/godot/meta/enum.ElementType.html
296+
297+
## Object lifecycle and initialization
298+
299+
Object initialization and lifecycle management was extended to provide better parity with Godot APIs.
300+
301+
### Base pointer access
302+
303+
So far, it has not been possible to do much with the base object during `init()` method. There was a half-broken `to_gd()` method. This
304+
has been improved with [`Base::to_init_gd()`][api-base-toinitgd]. What seems easy is actually quite a hack due to the way how Godot treats
305+
ref-counted objects during initialization ([#1273]).
306+
307+
### Virtual methods on `Gd<Self>`
308+
309+
Thanks to the great pull request by Yarwin, `#[func(gd_self)]` can now be used with various lifecycle methods. Using `Gd<T>` instead of `&T`/`&mut T` gives precise control over when the given instance is bound ([#1282]):
310+
311+
```rust
312+
#[func(gd_self)]
313+
fn ready(this: Gd<Self>) {
314+
this.signals().call_me_back_maybe().emit(&this);
315+
}
316+
```
317+
318+
### Post-initialization notification
319+
320+
The [`POSTINITIALIZE`][api-postinitialize] notification is now emitted after `init()` completes, providing a hook for setup that requires a fully initialized object. Thanks to beicause for this addition in [#1211]!
321+
322+
### Generic singleton access
323+
324+
The [`Singleton`][api-singleton] trait enables generic programming with singletons while maintaining backward compatibility with existing `singleton()` methods ([#1325]).
325+
326+
[#1211]: https://github.com/godot-rust/gdext/pull/1211
327+
[#1273]: https://github.com/godot-rust/gdext/pull/1273
328+
[#1282]: https://github.com/godot-rust/gdext/pull/1282
329+
[#1325]: https://github.com/godot-rust/gdext/pull/1325
330+
331+
[api-base-toinitgd]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Base.html#method.to_init_gd
332+
[api-singleton]: https://godot-rust.github.io/docs/gdext/master/godot/obj/trait.Singleton.html
333+
[api-postinitialize]: https://godot-rust.github.io/docs/gdext/master/godot/classes/notify/enum.NodeNotification.html#variant.POSTINITIALIZE
334+
335+
## Advanced argument passing and type conversion
336+
337+
The argument passing system received a comprehensive overhaul with the new `ToGodot::Pass` design, automatic [`AsArg`][api-asarg] implementations, and unified object argument handling. For detailed migration information, see the [v0.4 migration guide][migration-guide].
338+
339+
The system now uses explicit `ByValue` or `ByRef` passing modes, eliminates the need for manual `AsArg` implementations, and supports optional object parameters through `AsArg<Option<DynGd>>` ([#1285], [#1308], [#1310], [#1314], [#1323]).
340+
341+
[#1285]: https://github.com/godot-rust/gdext/pull/1285
342+
[#1308]: https://github.com/godot-rust/gdext/pull/1308
343+
[#1310]: https://github.com/godot-rust/gdext/pull/1310
344+
[#1314]: https://github.com/godot-rust/gdext/pull/1314
345+
[#1323]: https://github.com/godot-rust/gdext/pull/1323
346+
347+
[api-asarg]: https://godot-rust.github.io/docs/gdext/master/godot/meta/trait.AsArg.html
348+
349+
## Outlook
350+
351+
For upgrading existing code, consult the [v0.4 migration guide][migration-guide]. For a complete list of changes including bugfixes and internal improvements, see the [changelog in the repository][changelog].
352+
353+
Version 0.4 is yet another milestone in godot-rust's journey, with major improvements to the developer experience. This wouldn't be possible without the many contributions from the community, whether as code, feedback or [building projects with godot-rust][ecosystem].
354+
355+
The 0.4 cycle will put a focus on more control and performance. An example of that is beicause's work in [#1278] to give the user to trade off performance for safety across different runtime profiles. Other performance PRs are already open, too!
356+
357+
[#1278]: https://github.com/godot-rust/gdext/pull/1278
358+
[may-dev-update]: ../may-2025-update
359+
[migration-guide]: https://godot-rust.github.io/book/migrate/v0.4.html
360+
[changelog]: https://github.com/godot-rust/gdext/blob/master/Changelog.md
361+
[ecosystem]: https://godot-rust.github.io/book/ecosystem/

0 commit comments

Comments
 (0)