Skip to content

Commit 8713721

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

File tree

2 files changed

+363
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)