Skip to content

Commit fe88f9f

Browse files
committed
September 2025 dev update
1 parent a861c0a commit fe88f9f

File tree

2 files changed

+362
-0
lines changed

2 files changed

+362
-0
lines changed
31.9 KB
Loading
Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
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+
[`run_deferred()`][api-gd-rundeferred] and [`run_deferred_gd()`][api-gd-rundeferredgd] act as a type-safe `call_deferred()` alternative, allowing deferred method calls based on closures. This eliminates string-based method names and runtime errors. Thanks to goatfryed for the design and implementation ([#1204], [#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_gd(|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+
[#1204]: https://github.com/godot-rust/gdext/pull/1204
159+
[#1223]: https://github.com/godot-rust/gdext/pull/1223
160+
[#1327]: https://github.com/godot-rust/gdext/pull/1327
161+
[#1332]: https://github.com/godot-rust/gdext/pull/1332
162+
[#1344]: https://github.com/godot-rust/gdext/pull/1344
163+
[#1346]: https://github.com/godot-rust/gdext/pull/1346
164+
165+
[api-gd-rundeferred]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.run_deferred
166+
[api-gd-rundeferredgd]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.run_deferred_gd
167+
[api-callable-fromfn]: https://godot-rust.github.io/docs/gdext/master/godot/builtin/struct.Callable.html#method.from_fn
168+
[`Gd::linked_callable()`]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.linked_callable
169+
170+
## Signal enhancements
171+
172+
Since the introduction of signals in v0.3, several convenience APIs have been added.
173+
174+
Signals now offer disconnection ([#1198]):
175+
```rust
176+
let handle = self.signals().my_signal().connect(...);
177+
// Later that day:
178+
handle.disconnect();
179+
```
180+
181+
Thanks to Yarwin's work on `linked_callable()`, signals connected to a receiver object are automatically disconnected when the receiver is freed ([#1223]):
182+
183+
```rust
184+
let obj: Gd<MyClass> = ...;
185+
let handle = self.signals().my_signal().connect_other(&obj, ...);
186+
187+
obj.free(); // Auto-disconnects the signal.
188+
```
189+
190+
User ogapo enabled conversion to untyped signals for better Godot interop, with [`TypedSignal::to_untyped()`][api-typedsignal-tountyped] ([#1288]):
191+
192+
```rust
193+
let typed = self.signals().my_typed_signal();
194+
let untyped: Signal = typed.to_untyped();
195+
```
196+
197+
[#1198]: https://github.com/godot-rust/gdext/pull/1198
198+
[#1223]: https://github.com/godot-rust/gdext/pull/1223
199+
[#1288]: https://github.com/godot-rust/gdext/pull/1288
200+
201+
[api-typedsignal-tountyped]: https://godot-rust.github.io/docs/gdext/master/godot/register/struct.TypedSignal.html#method.to_untyped
202+
203+
## Ergonomics and developer experience
204+
205+
In good tradition, godot-rust has shipped a truckload of little tools to make everyday development more enjoyable.
206+
207+
### Class dispatching
208+
209+
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]).
210+
211+
Thanks to sylbeth's work, the macro supports mutable bindings ([#1242]), optional fallback branches ([#1246]), and discard patterns ([#1252]):
212+
213+
```rust
214+
let simple_dispatch: i32 = match_class!(event, {
215+
button @ InputEventMouseButton => 1,
216+
motion @ InputEventMouseMotion => 2,
217+
action @ InputEventAction => 3,
218+
_ => 0, // Fallback.
219+
});
220+
```
221+
222+
### Generic packed arrays
223+
224+
Generic [`PackedArray<T>`][api-packedarray] abstracts over all specific packed array types, enabling code reuse across different array variants ([#1291]):
225+
226+
```rust
227+
fn format_packed_array<T>(array: &PackedArray<T>) -> String
228+
where T: PackedArrayElement {
229+
// ...
230+
}
231+
```
232+
233+
### Variant slices
234+
235+
The [`vslice!`][api-vslice] macro provides a concise way to create `&[Variant]` slices from heterogeneous values ([#1191]):
236+
237+
```rust
238+
// Old way:
239+
let args = &[1.to_variant(), "hello".to_variant(), vector.to_variant()];
240+
241+
// New way:
242+
let args = vslice![1, "hello", vector];
243+
```
244+
245+
This comes in handy for dynamic/reflection APIs like `Object::call()`.
246+
247+
### Engine API type safety
248+
249+
Arrays and dictionaries now offer runtime type introspection via [`ElementType`][api-elementtype] ([#1304]).
250+
251+
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.
252+
253+
```rust
254+
let s: Variant = obj.get_script();
255+
// now:
256+
let s: Option<Gd<Script>> = obj.get_script();
257+
258+
obj.connect_ex(...).flags(ConnectFlags::DEFERRED as u32).done();
259+
// now:
260+
obj.connect_flags(..., ConnectFlags::DEFERRED);
261+
```
262+
263+
### Negative indexing
264+
265+
The [`SignedRange`][api-signedrange] type provides negative indexing for arrays and strings ([#1300]).
266+
267+
### Enum and bitfield introspection
268+
269+
Programmatic access to all enum and bitfield values enables runtime introspection of Godot's type system ([#1232]).
270+
271+
```rust
272+
// Access all enum constants.
273+
let constants = MyEnum::all_constants();
274+
let values = MyEnum::values(); // Distinct values only.
275+
276+
for (name, value) in constants {
277+
add_dropdown_option(name, value);
278+
}
279+
```
280+
281+
[#1191]: https://github.com/godot-rust/gdext/pull/1191
282+
[#1225]: https://github.com/godot-rust/gdext/pull/1225
283+
[#1232]: https://github.com/godot-rust/gdext/pull/1232
284+
[#1242]: https://github.com/godot-rust/gdext/pull/1242
285+
[#1246]: https://github.com/godot-rust/gdext/pull/1246
286+
[#1252]: https://github.com/godot-rust/gdext/pull/1252
287+
[#1291]: https://github.com/godot-rust/gdext/pull/1291
288+
[#1300]: https://github.com/godot-rust/gdext/pull/1300
289+
[#1304]: https://github.com/godot-rust/gdext/pull/1304
290+
[#1315]: https://github.com/godot-rust/gdext/pull/1315
291+
292+
[api-matchclass]: https://godot-rust.github.io/docs/gdext/master/godot/classes/macro.match_class.html
293+
[api-vslice]: https://godot-rust.github.io/docs/gdext/master/godot/builtin/macro.vslice.html
294+
[api-packedarray]: https://godot-rust.github.io/docs/gdext/master/godot/builtin/struct.PackedArray.html
295+
[api-signedrange]: https://godot-rust.github.io/docs/gdext/master/godot/meta/trait.SignedRange.html
296+
[api-elementtype]: https://godot-rust.github.io/docs/gdext/master/godot/meta/enum.ElementType.html
297+
298+
## Object lifecycle and initialization
299+
300+
Object initialization and lifecycle management was extended to provide better parity with Godot APIs.
301+
302+
### Base pointer access
303+
304+
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
305+
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
306+
ref-counted objects during initialization ([#1273]).
307+
308+
### Virtual methods on `Gd<Self>`
309+
310+
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]):
311+
312+
```rust
313+
#[func(gd_self)]
314+
fn ready(this: Gd<Self>) {
315+
this.signals().call_me_back_maybe().emit(&this);
316+
}
317+
```
318+
319+
### Post-initialization notification
320+
321+
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]!
322+
323+
### Generic singleton access
324+
325+
The [`Singleton`][api-singleton] trait enables generic programming with singletons while maintaining backward compatibility with existing `singleton()` methods ([#1325]).
326+
327+
[#1211]: https://github.com/godot-rust/gdext/pull/1211
328+
[#1273]: https://github.com/godot-rust/gdext/pull/1273
329+
[#1282]: https://github.com/godot-rust/gdext/pull/1282
330+
[#1325]: https://github.com/godot-rust/gdext/pull/1325
331+
332+
[api-base-toinitgd]: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Base.html#method.to_init_gd
333+
[api-singleton]: https://godot-rust.github.io/docs/gdext/master/godot/obj/trait.Singleton.html
334+
[api-postinitialize]: https://godot-rust.github.io/docs/gdext/master/godot/classes/notify/enum.NodeNotification.html#variant.POSTINITIALIZE
335+
336+
## Advanced argument passing and type conversion
337+
338+
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].
339+
340+
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]).
341+
342+
[#1285]: https://github.com/godot-rust/gdext/pull/1285
343+
[#1308]: https://github.com/godot-rust/gdext/pull/1308
344+
[#1310]: https://github.com/godot-rust/gdext/pull/1310
345+
[#1314]: https://github.com/godot-rust/gdext/pull/1314
346+
[#1323]: https://github.com/godot-rust/gdext/pull/1323
347+
348+
[api-asarg]: https://godot-rust.github.io/docs/gdext/master/godot/meta/trait.AsArg.html
349+
350+
## Outlook
351+
352+
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].
353+
354+
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].
355+
356+
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!
357+
358+
[#1278]: https://github.com/godot-rust/gdext/pull/1278
359+
[may-dev-update]: ../may-2025-update
360+
[migration-guide]: https://godot-rust.github.io/book/migrate/v0.4.html
361+
[changelog]: https://github.com/godot-rust/gdext/blob/master/Changelog.md
362+
[ecosystem]: https://godot-rust.github.io/book/ecosystem/

0 commit comments

Comments
 (0)