Migrating to v0.4
This guide covers deprecations and breaking changes when upgrading from godot-rust 0.3 to 0.4.
Table of contents
Godot version support
#1292: godot-rust v0.4 drops support for Godot 4.1.
The library now supports Godot versions 4.2 through 4.5, including the latest 4.5 release.
See also Compatibility and stability.
Overhauls to core APIs
Argument passing redesign
#1285, #1308, #1310, #1314: So far, AsArg
and ToGodot
traits have had very similar roles, namely converting
Rust types to Godot-compatible types. However, they had only limited interop. For example, it wasn't easily possible to pass user-defined enums to
emit()
of signals.
ToGodot::Pass
and automatic AsArg
impls
The ToGodot
trait has been simplified, replacing the associated type ToVia<'v>
with Pass
, which usually has one of two values.
AsArg
is then auto-implemented for types that support ToGodot
, in the following way:
Pass = ByValue
: what you typically want. This implementsAsArg<T>
for owned typesT
, e.g. you can passi64
directly to Godot.Pass = ByRef
for specifically optimized types. This implementsAsArg<T>
for references&T
, e.g. you can pass&GString
to Godot without cloning. As a user, you typically need to convert to a custom type anyway, soByValue
is often easier.
If you follow those, you should never need to implement AsArg
yourself. ToGodot
is sufficient, and can often be derived.
Before (0.3):
#![allow(unused)] fn main() { impl ToGodot for MyString { type ToVia<'v> = Self::Via; fn to_godot(&self) -> Self::ToVia<'_> { GString::from(self) } } }
After (0.4):
#![allow(unused)] fn main() { use godot::meta; impl ToGodot for MyString { type Pass = meta::ByValue; // Returns a new GString here, since we have to create one anyway. fn to_godot(&self) -> Self::Via { GString::from(self) } } impl ToGodot for MyCachedString { type Pass = meta::ByRef; // Returns &GString here, since we store a reference. fn to_godot(&self) -> &Self::Via { &self.cached_gstring } } }
Return type changes
For reference-counted types (GString
, Array
, Dictionary
, Variant
...), to_godot()
now returns references.
A value can be obtained by calling to_godot_owned()
.
Before (0.3):
#![allow(unused)] fn main() { let a: GString = my_value.to_godot(); }
After (0.4):
#![allow(unused)] fn main() { let a: &GString = my_value.to_godot(); let b: GString = my_value.to_godot_owned(); }
Object argument traits
The specialized AsObjectArg<T>
trait has been consolidated into the more general AsArg<Gd<T>>
trait, unifying the type system.
It is also capable of expressing optional arguments through AsArg<Option<Gd<T>>>
.
Before (0.3):
#![allow(unused)] fn main() { #[signal] fn my_signal(some_node: Gd<Node>); let node = Node::new_alloc(); let derived = Node2D::new_alloc(); // Argument had to be upcast. sig.emit(&derived.upcast()); // The type could be inferred, but arguments had to be implicitly upcast. let _array = array![&node, &derived.upcast()]; }
After (0.4):
#![allow(unused)] fn main() { fn my_signal(some_node: Gd<Node>); let node = Node::new_alloc(); let derived = Node2D::new_alloc(); // Will be implicitly upcast. sig.emit(&derived); // Type must be specified, but arguments will be implicitly upcast. let _array: Array<Gd<Node>> = array![&node, &derived]; }
Callable
return types
#1332, #1344, #1346: Callable
constructor from_local_fn()
is phased out in favor of from_fn()
, which supports any
return type implementing ToGodot
, eliminating the need for manual Variant
conversion and wrapping inside Result
.
The Err
variant didn't add much purpose but required a lot of boilerplate, and errors can still be supported through panics or RustCallable
customization.
Before (0.3):
#![allow(unused)] fn main() { let callable = Callable::from_local_fn("answer", |args| { Ok(42.to_variant()) }); let callable = Callable::from_local_fn("unit", |args| { do_sth(args); // Some side effect, no return value. Ok(Variant::nil()) }); }
After (0.4):
#![allow(unused)] fn main() { let callable = Callable::from_fn("answer", |args| 42); let callable = Callable::from_fn("unit", |args| { do_sth(args); }); }
Singleton
trait
#1325: Singleton access has been moved to a dedicated Singleton
trait, enabling generic programming while maintaining
backward compatibility with existing singleton()
methods.
In the future, we may look into other options to provide singletons, especially once we support them for user classes, too.
Before (0.3):
#![allow(unused)] fn main() { let input = Input::singleton(); let engine = Engine::singleton(); }
After (0.4):
If you already include the prelude, no code changes should be necessary:
#![allow(unused)] fn main() { use godot::prelude::Singleton; let input = Input::singleton(); // Still works let engine = Engine::singleton(); // Still works // Now supports generic access: fn summon_the_one<T: Singleton>() -> Gd<T> { T::singleton() } }
Removed conversions
#1286, #1316: By-value From
conversions between string types have been removed to clarify that no buffer optimization occurs during conversion.
You can still use From
for references.
Before (0.3):
#![allow(unused)] fn main() { let gstring = GString::from("hello"); let sname = StringName::from(gstring); // or .into() }
After (0.4):
#![allow(unused)] fn main() { let gstring = GString::from("hello"); let sname = StringName::from(&gstring); }
The From<&'static CStr>
implementation for StringName
has been removed, since Godot no longer offers a performance benefit,
and static storage duration may very easily cause memory leaks.
Furthermore, impl From<VariantArray> for Packed*Array
has been removed. This was unsound; VariantArray
can contain arbitrary types.
Renames
- Struct
ClassName
->ClassId
, which represents the idea of lightweight IDs better. - Method
apply_deferred()
->run_deferred()
andrun_deferred_gd()
, accepting both&mut T
andGd<T>
parameters. - Method
Base::to_gd()
->Base::to_init_gd()
, for proper access duringinit
. - Method
Callable::from_local_fn()
->Callable::from_fn()
(see above). - Assoc fn
Callable::from_local_static()
->Callable::from_class_static()
. - Trait
ToSignalObject
->ObjectToOwned
. - Trait
WithDeferredCall
-> merged intoWithBaseField
.
Registration changes
#[export(range)]
limits and type validation
#1320: #[export(range = ...)]
attribute traditionally required float literals, even for integer fields. The values were also not validated
against the field type, allowing out-of-bounds values.
Furthermore, reasonable range bounds are now inferred for all integer types.
Before (0.3):
#![allow(unused)] fn main() { #[export(range = (1.0, 500.0))] // Float was required, no bound checks. int_field: i8, #[export] // Default: full i64 range, allowing invalid values in editor. another_field: i8, }
After (0.4):
#![allow(unused)] fn main() { #[export(range = (1, 127))] // Must be i8-compatible literals, within bounds. int_field: i8, #[export] // Default: infer range from i8 as (-128, 127). another_field: i8, }
This change improves reliability, but may require updates to existing export declarations.
Export range for radians
#1320: The radians
option was deprecated in Godot itself, so the attribute was updated to use radians_as_degrees
, which follows Godot's current
convention. Using the old radians
will lead to a descriptive error.
Before (0.3):
#![allow(unused)] fn main() { #[export(range = (radians))] angle: f32, }
After (0.4):
#![allow(unused)] fn main() { #[export(range = (radians_as_degrees))] angle: f32, }
Editor class validation
#1272: Classes with names starting with "Editor" must now be explicitly marked with the internal
attribute to prevent accidental exposure in
exported builds. Godot has a special and undocumented rule to make those internal, which led to confusion.
Before (0.3):
#![allow(unused)] fn main() { #[derive(GodotClass)] #[class(base=EditorPlugin)] struct EditorMyPlugin { base: Base<EditorPlugin>, } }
After (0.4):
#![allow(unused)] fn main() { #[derive(GodotClass)] #[class(base=EditorPlugin, internal)] struct EditorMyPlugin { base: Base<EditorPlugin>, } }
Engine APIs
EngineEnum
+ EngineBitfield
introspection
#1232: Since some enums in Godot contain duplicates, there isn't a unique name for an enum variant (which is just an integer internally).
Thus, EngineEnum::godot_name()
sometimes returned incorrect names. The new API provides all_constants()
for a full introspection, and values()
for just distinct, useful values (e.g. when you need a drop-down list).
EngineBitfield
also has all_constants()
now, but no values()
.
Before (0.3):
#![allow(unused)] fn main() { let name = MyEnum::Variant1.godot_name(); }
After (0.4):
#![allow(unused)] fn main() { let constants = MyEnum::all_constants(); // Use constants map to find names. }
Array and string indexing with negative values
#1300: Operations that support negative indexing (arrays, strings, etc.) now use SignedRange
instead of accepting raw integer
parameters.
This provides better type safety for range operations that can include negative indices.
Before (0.3):
#![allow(unused)] fn main() { let slice = array.slice(start, end); // Raw integers }
After (0.4):
#![allow(unused)] fn main() { use godot::meta::wrapped; let a = array.slice(wrapped(..-2)); // from 0 to len-2 let b = array.slice(wrapped(1..-2)); // from 1 to len-2 }
Where possible and we consider it likely that the user named symbols in their own code, we provide deprecated aliases for the v0.4 cycle, to ease the transition and provide helpful warnings.