Migrating to v0.5

This guide covers deprecations and breaking changes when upgrading from godot-rust 0.4 to 0.5.

Smooth transition

To reduce the friction, we recommend first updating to the latest patch release of the previous minor version, v0.4.5. Many changes are announced early in the form of deprecation warnings, which contain instructions on how to switch to newer APIs.

You can update your Cargo.toml to the latest patch release by running:

cargo update -p godot

Once you have addressed all deprecation warnings, you can update to the new minor version:

cargo upgrade -p godot

Table of contents

Godot versions

#1484: godot-rust v0.5 adds API level 4.6, which is now the new default.

This doesn't mean you have to update your Godot version -- just provide Cargo features to use older API levels, e.g. api-4-5.

#1505: API level features are now limited to minor versions only. If you previously used patch-level features like api-4-3-1, you need to change them to the corresponding minor version, e.g. api-4-3.

Rust 2024 edition

#1500: godot-rust v0.5 uses the Rust 2024 edition.
The minimum supported Rust version (MSRV) is now 1.94.

Required objects in engine APIs

#1383, #1493: GDExtension 4.6 provides non-null ("required") type information for function parameters and return types. This means that many Rust APIs can now become more type-safe, and your code will not need to deal with Option in cases where values are not supposed to be null.

If you have Option<Gd> where Godot guarantees non-null objects, you'll need to change this to Gd.

Before (0.4):

#![allow(unused)]
fn main() {
let maybe_child: Option<Gd<Node>> = ...;
node.add_child(&maybe_child);

let tree = node.get_tree().unwrap();
let tween = node.create_tween().unwrap();
}

After (0.5):

#![allow(unused)]
fn main() {
// Option no longer compiles.
let definitely_child: Gd<Node> = ...; // non-null
node.add_child(&definitely_child);

let tree = node.get_tree(); // Panics if not in tree; otherwise get_tree_or_null().
let tween = node.create_tween();
}

Property APIs

Var trait redesign

#1454, #1458, #1466, #1469: The Var trait has been redesigned to support #[var(pub)], which generates typed Rust getters/setters with clone-based semantics. The trait API changed as follows:

Before (0.4):

#![allow(unused)]
fn main() {
pub trait Var: GodotConvert {
    fn get_property(&self) -> Self::Via;
    fn set_property(&mut self, value: Self::Via);
    fn var_hint() -> PropertyHintInfo { ... }
}
}

After (0.5):

#![allow(unused)]
fn main() {
pub trait Var: GodotConvert {
    type PubType;
    fn var_get(field: &Self) -> Self::Via;
    fn var_set(field: &mut Self, value: Self::Via);
    fn var_pub_get(field: &Self) -> Self::PubType;
    fn var_pub_set(field: &mut Self, value: Self::PubType);
}
}

If you have manual Var implementations, you need to update them. However, for most cases, you can use the new SimpleVar marker trait instead: types that already support ToGodot + FromGodot + Clone can get an automatic Var implementation via blanket impl.

Before (0.4):

#![allow(unused)]
fn main() {
impl Var for MyType {
    fn get_property(&self) -> Self::Via {
        self.to_godot_owned()
    }
    fn set_property(&mut self, value: Self::Via) {
        *self = FromGodot::from_godot(value);
    }
}
}

After (0.5):

#![allow(unused)]
fn main() {
// MyType: ToGodot + FromGodot + Clone.
impl SimpleVar for MyType {}
}

#[var] getter/setter semantics

#1454: The #[var(get, set)] keys are now independent from each other.

AttributeOld behavior (0.4)New behavior (0.5)
#[var]Generates getter + setterSame as before
#[var(get)]Disabled setterDeclares custom get_{field},
setter unchanged
#[var(set)]Disabled getterDeclares custom set_{field},
getter unchanged
#[var(no_get)]New; was implied
by #[var(set)]
Explicitly disables the getter
#[var(no_set)]New; was implied
by #[var(get)]
Explicitly disables the setter
#[var(get = my_fn)]Custom getter fnSame as before
#[var(set = my_fn)]Custom setter fnSame as before

Before (0.4):

#![allow(unused)]
fn main() {
// Disabled setter, only getter:
#[var(get)]
my_field: i32,

// Disabled getter, only setter:
#[var(set)]
other_field: f32,
}

After (0.5):

#![allow(unused)]
fn main() {
// Disabled setter, only getter:
#[var(no_set)]
my_field: i32,

// Disabled getter, only setter:
#[var(no_get)]
other_field: f32,

// Custom getter, generated setter:
#[var(get)]
custom_get_field: i32,
}

#[var(pub)] for Rust-side accessors

#1458, #1466: Auto-generated Rust getters/setters for #[var] fields are being phased out.
In v0.4, #[var] fields implicitly generated public Rust accessor methods (e.g. get_my_field() / set_my_field()). These are now deprecated and will be removed in v0.6.

If you rely on these generated Rust methods, opt in explicitly with #[var(pub)]:

#![allow(unused)]
fn main() {
#[var(pub)]
my_field: i32,
// Generates:   fn get_my_field(&self) -> i32
//              fn set_my_field(&mut self, value: i32)
}

The #[var(pub)] accessors use Clone-based semantics (via Var::PubType), while the internal Godot-facing accessors continue to use GodotConvert::Via.

Compile-time type checks for custom #[var] accessors

#1469: Custom getters and setters declared with #[var(get)] or #[var(set)] are now type-checked at compile time. If your getter's return type or setter's parameter type doesn't match the field's Var::PubType, you'll get a compile error instead of a runtime failure.

#1518: Additionally, #[var(no_get)] combined with #[export] is now a compile error. Reading a #[var(no_get)] field from GDScript now panics with a helpful error message instead of silently returning the default value.

Const-qualified getters

#1497: A heuristic now const-qualifies Godot methods prefixed with get_, is_, or has_, changing their receiver from &mut self to &self. This affects a large number of engine methods, which were unnecessarily declared mutable.

An explicit deny-list handles false positives: methods that actually mutate state remain &mut self. Examples: FileAccess::get_line() which advances a cursor, StreamPeer::get_8(), etc.

This is a technically breaking change, but existing code using mut bindings will continue to compile. Clippy may emit warnings about unnecessary mut.

Before (0.4):

#![allow(unused)]
fn main() {
let mut api: Gd<MultiplayerApi> = ...;
let id = api.get_unique_id(); // Required &mut self.
}

After (0.5):

#![allow(unused)]
fn main() {
let api: Gd<MultiplayerApi> = ...;
let id = api.get_unique_id(); // Now takes &self.
}

GodotShape replaces property hint APIs

#1513, #1514, #1534: The internal representation of how types describe themselves to Godot has been overhauled. A new GodotShape enum (returned by GodotConvert::godot_shape()) replaces several scattered APIs.

The following trait methods have been removed in favor of a unified GodotConvert::godot_shape():

  • Var::var_hint()
  • Export::export_hint()
  • GodotType::property_info()
  • GodotType::property_hint_info()
  • GodotType::godot_type_name()
  • GodotType::class_id()
  • Element::element_type_string()
  • PackedElement::element_type_string()

If you had custom Var or Export implementations that overrode var_hint() or export_hint(), you now need to implement GodotConvert::godot_shape() instead, returning an appropriate GodotShape variant.

This refactor also enables enums in collections. Both engine- and user-defined #[derive(GodotConvert)] enums can now be used as elements in Array<T> or Dictionary<K, V>.

#[func] bounds

#1435: Parameters and return types in #[func] have been updated. Newly supported are user-defined enums.

Some types have also been restricted -- the following no longer implement ToGodot/FromGodot and thus cannot be passed to or returned from #[func]:

u64

#1413: Primary reason for disallowing u64 is that GDScript (and Variant) cannot natively store integers other than i64, and it's very easy to introduce logic errors that silently change the value. See also rationale in the API docs.

If you have existing uses of u64, you can use either:

  • i64 with casting.
  • any smaller unsigned integer type, if the range is enough.
  • a custom type and override GodotConvert::godot_shape() with param metadata.

Raw pointers and RawPtr<P>

#1436: Raw pointers in engine APIs (native structures, c_void, etc.) are now wrapped in RawPtr<P>, replacing direct *const T / *mut T parameters. Construction requires unsafe, which wasn't the case before -- you could previously return a pointer to a local variable from #[func] without ever typing unsafe.

Before (0.4):

#![allow(unused)]
fn main() {
#[func]
fn pass_native_struct(&self, caret_info: *const CaretInfo) -> VarDictionary {
    let info = unsafe { &*caret_info };
    // use &CaretInfo directly.
}

#[func]
fn native_struct_array_ret(&self) -> *const Glyph {
    self.some_raw_ptr
}
}

Besides the native-structure types, the following raw pointer impls for ToGodot/FromGodot have been removed:

  • *const std::ffi::c_void
  • *mut std::ffi::c_void
  • *mut *const u8
  • *mut i32
  • *mut f64
  • *mut u8
  • *const u8

After (0.5):

#![allow(unused)]
fn main() {
use godot::meta::conv::RawPtr;

#[func]
fn pass_native_struct(&self, caret_info: RawPtr<*const CaretInfo>) -> VarDictionary {
    let caret_raw: *const CaretInfo = caret_info.ptr();
    let info = unsafe { &*caret_raw };
    // use &CaretInfo directly.
}

#[func]
fn native_struct_array_ret(&self) -> RawPtr<*const Glyph> {
    // SAFETY: caller must ensure returning pointer to Godot is safe.
    unsafe { RawPtr::new(self.some_raw_ptr) }
}
}

IObject callback renames

#1527: Virtual callbacks in the IObject trait (and related I* traits inheriting from Object) are now prefixed with on_, to avoid naming collisions with same-named methods on Object and to clarify that these are callbacks.

Old nameNew name
get_property()on_get()
set_property()on_set()
get_property_list()on_get_property_list()
validate_property()on_validate_property()

The old names are deprecated and will continue to function until v0.6.

Typed collections

Typed Dictionary<K, V>

#1502, #1507, #1516: Dictionaries are now generic over key and value types: Dictionary<K, V>. This mirrors the typed Array<T> API.

  • Dictionary<K, V> is the typed dictionary.
  • AnyDictionary is a type-erased dictionary (analogous to AnyArray).
  • VarDictionary is now an alias for Dictionary<Variant, Variant>.
  • Engine APIs now accept &AnyDictionary instead of &VarDictionary, so you can pass any &Dictionary<K, V> directly.
  • Typed dictionary iterators are available: dict.iter_shared() returns (K, V) pairs.

This is mostly a backwards-compatible change as VarDictionary still exists, however there are some places which need attention.

Methods that accept values have changed bounds from ToGodot to AsArg<Variant>, as a special case of AsArg<K>/AsArg<V>. Elements whose ToGodot::Pass is not ByValue (e.g. objects, callables) now need to be passed by reference:

Before (0.4):

#![allow(unused)]
fn main() {
dict.set("hello", gd);
}

After (0.5):

#![allow(unused)]
fn main() {
dict.set("hello", &gd);
}

vdict! macro uses => separator

#1523: The vdict! macro now uses => instead of : to separate keys from values. This avoids ambiguity with Rust's token tree parsing and removes the need to wrap complex key expressions in parentheses. It's also more in line with crates like maplit.

Before (0.4):

#![allow(unused)]
fn main() {
let tiles = vdict! {
    (Vector2i::new(1, 2)): Tile::GRASS,
    (Vector2i::new(1, 3)): Tile::WATER,
};
}

After (0.5):

#![allow(unused)]
fn main() {
let tiles = vdict! {
    Vector2i::new(1, 2) => Tile::GRASS,
    Vector2i::new(1, 3) => Tile::WATER,
};
}

In good old godot-rust fashion, the : syntax continues to be supported during a transition period (with deprecation warnings).

iarray! and idict! macros for type inference

#1530: godot-rust v0.5 underwent a significant refactoring to maintain decent Variant support in Dictionary<K, V>. The short story is that we can no longer rely on K/V being Variant, so we had to replace ToGodot with the more general AsArg<K/V> bounds. This comes at a cost: due to more candidates for AsArg conversions, type inference is ambiguous in situations where it cannot be deduced from the context (like function return types).

#![allow(unused)]
fn main() {
let d = dict! { "key": 10 }; // Error: "key" could be GString, StringName or Variant.

// dict! now needs explicit type annotations:
let d: Dictionary<GString, i32> = dict! { "key": 10 };
}

Typing three extra identifiers is of course beyond the acceptable, so we have a solution.

New macros iarray! and idict! ("inferred") create arrays and dictionaries without explicit type hints. They use opinionated but unambiguous type inference. For example, iarray!["hello"] infers Array<GString>, never Array<StringName>. Not all element types are supported as of now.

The existing array! and dict! macros remain unchanged and still require type context (annotation, function parameter, etc.).

#![allow(unused)]
fn main() {
// No type annotation needed:
let ints = iarray![3, 1, 4];        // Array<i32>
let strs = iarray!["a", "b"];       // Array<GString>
let d = idict! { "key" => 10 };     // Dictionary<GString, i32>

// array!/dict! still work with type context:
let ints: Array<i64> = array![3, 1, 4];
}

AnyArray -- covariant typed/untyped array

#1422, #1434: A new AnyArray type provides a type-erased array that can store either typed Array<T> or untyped VarArray. It is covariant: any &Array<T> can be used where &AnyArray is expected, thanks to Deref coercion.

#![allow(unused)]
fn main() {
use godot::builtin::{Array, AnyArray, VarArray};

let typed: Array<i64> = array![1, 2, 3];

// Deref coercion: use typed array where AnyArray is expected.
let any_ref: &AnyArray = &typed;
assert_eq!(any_ref.len(), 3);

// Explicit upcast for owned values.
let any: AnyArray = typed.clone().upcast_any_array();

// Downcast back to typed.
let back: Array<i64> = any.try_cast_array::<i64>()
    .expect("convert back to typed");
}

Engine APIs that previously accepted &VarArray parameters (like Callable::callv()) now take &AnyArray, which means you can pass any &Array<T> directly without manual conversion. If you were explicitly constructing a VarArray to pass to such methods, note that &VarArray coerces to &AnyArray via Deref, so no changes should be necessary in most cases.

For owned values (e.g. returned by virtual functions), you can use the upcast_any_array() method to convert an Array<T> into an AnyArray.

Core API changes

Builder APIs for builtin methods

#1424: Builtin types (GString, Vector2, Color, etc.) now support the _ex() builder pattern for methods with default parameters, consistent with how engine class methods already work.

Before (0.4):

#![allow(unused)]
fn main() {
let pi = GString::num(std::f64::consts::PI, 3);
}

After (0.5):

#![allow(unused)]
fn main() {
let pi = GString::num_ex(std::f64::consts::PI).decimals(3).done();
}

The non-_ex variants remain available and use Godot's default parameter values.

In v0.4, we often emulated the builder pattern -- inconsistently at that:

  • by requiring all parameters
  • with multiple methods
  • with our handcrafted builder

If you used such methods, your code will need to be updated to the new pattern.

Type-safe duplicate_node() and duplicate_resource()

#1492: New generic duplication methods return the concrete type instead of requiring manual downcasts.

#![allow(unused)]
fn main() {
let node: Gd<Node2D> = Node2D::new_alloc();
let copy: Gd<Node2D> = node.duplicate_node(); // Directly typed.

// Builder pattern for fine-grained control:
let copy = node.duplicate_node_ex()
    .flags(DuplicateFlags::SIGNALS | DuplicateFlags::GROUPS)
    .done();

let resource: Gd<Resource> = Resource::new_gd();
let copy: Gd<Resource> = resource.duplicate_resource();

// Deep duplication (Godot 4.5+):
let deep = resource.duplicate_resource_ex()
    .deep(DeepDuplicateMode::ALL)
    .done();
}

The old Node::duplicate() and Resource::duplicate() methods are deprecated in favor of these new type-safe variants.

Geometric API changes

#1447, #1461, #1463: Several geometric types gained new methods:

  • Aabb::get_endpoint() -- returns a corner vertex of the bounding box.
  • Aabb cleanup: additional methods like get_center() and get_support() (previously support()).
  • Rect2 / Rect2i: corner constructors and additional utility methods.

The from_corners() constructors on Rect2, Rect2i, and Aabb have been deprecated in favor of from_position_end(), which better describes the parameters (position + end point, not two arbitrary corners).

Module reorganization

#1531, #1537: The meta, register, and signal modules have been reorganized. If you import specific items from these modules, your import paths may need updating:

Symbol(s)Old module (0.4)New module (0.5)
GodotShape, GodotElementShape, Enumeratorgodot::metagodot::meta::shape
ByValue, ByRef, ByObject, ArgPassinggodot::metagodot::meta::conv
ElementTypegodot::metagodot::meta::inspect
GodotConvert (derive macro)godot::registergodot::meta
Export, Var (derive macros)godot::registergodot::register::property
TypedSignal, ConnectHandle, signal typesgodot::registergodot::signal
PropertyHintInfo, PropertyInfo, MethodInfogodot::metagodot::register::info

Most commonly used symbols remain available in godot::prelude and are not affected.

Trait renames: ArrayElement and PackedArrayElement

#1506: The traits ArrayElement and PackedArrayElement have been renamed to Element and PackedElement, respectively.

If you use these traits as bounds in generic code, update the names accordingly.

Other changes

GFile::read_as_gstring_entire() parameter removed

#1494: The skip_cr parameter has been removed from GFile::read_as_gstring_entire() to provide a uniform API across Godot versions.

Before (0.4):

#![allow(unused)]
fn main() {
let text = gfile.read_as_gstring_entire(true)?;
}

After (0.5):

#![allow(unused)]
fn main() {
let text = gfile.read_as_gstring_entire()?;
}

GString, StringName and NodePath: removed arg() method

The .arg() methods on GString and StringName have been removed. They were used to convert between Godot string types when passing arguments to functions expecting a different string type. The idea is to come up with a more general AsArg conversion method, possibly reserving the .arg() name for that in the future.

To migrate, use T::from():

Before (0.4):

#![allow(unused)]
fn main() {
// Node::is_in_group() takes impl AsArg<StringName>
let group: GString = some_group_name();
node.is_in_group(group.arg());
}

After (0.5):

#![allow(unused)]
fn main() {
let group: GString = some_group_name();
node.is_in_group(&StringName::from(&group));
}

Environment variable renames

#1521: All environment variables now use a uniform GDRUST_ prefix. The old names are still recognized but emit a deprecation warning.

Old nameNew name
GODOT4_BINGDRUST_GODOT_BIN
GODOT4_GDEXTENSION_JSONGDRUST_GODOT_API_JSON
GODOT_RUST_NOWARNGDRUST_SUPPRESSED_WARNINGS
(new in v0.5)GDRUST_MAIN_EXTENSION

Removed deprecated symbols

#1459: All symbols that were deprecated during the v0.4 cycle have been removed. If you have already addressed all deprecation warnings in v0.4.5, you should be covered for the most part. The following table lists the removed symbols and their replacements:

Removed symbolReplacement
VariantArray (type alias)VarArray
ClassName (type alias)ClassId
GodotClass::class_name()GodotClass::class_id()
WithBaseField::apply_deferred()run_deferred() +
run_deferred_gd()
Gd::apply_deferred()Gd::run_deferred() + Gd::run_deferred_gd()
Callable::from_local_fn()Callable::from_fn()
Callable::from_local_static()Callable::from_class_static()
*::hash() (Godot hash value)*::hash_u32()
Array::bsearch_custom()Array::functional_ops()
.bsearch_custom()
ExtensionLibrary::on_level_init()ExtensionLibrary::on_stage_init()
ExtensionLibrary::on_level_deinit()ExtensionLibrary::on_stage_deinit()
ExtensionLibrary::on_main_loop_startup()on_stage_init(InitStage::MainLoop)
ExtensionLibrary::on_main_loop_shutdown()on_stage_deinit(InitStage::MainLoop)
#[class(no_init, base=EditorPlugin)] (warning)Now a hard compile error; use #[class(init)]