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 implements AsArg<T> for owned types T, e.g. you can pass i64 directly to Godot.
  • Pass = ByRef for specifically optimized types. This implements AsArg<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, so ByValue 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

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.