Migrating from godot-rust 0.8 to 0.9.x
In version 0.9, we are attempting to resolve many long-standing problems in the older API. As a result, there are many breaking changes in the public interface. This is a quick guide to the new API for users that have used older versions.
Module organization and naming
Generated API types
Generated types now live under the gdnative::api
module. This makes the top-level namespace easier to navigate in docs. If you have used glob imports like:
..., you should change this to:
Generated property accessors
Generated getters of properties are no longer prefixed with get_
. This does not affect other methods that start with get_
that are not getters, like File::get_8
.
Separation of core types and NativeScript code
Core types, like VariantArray
, Color
, and Dictionary
are moved to the gdnative::core_types
module, while NativeScript supporting code like NativeClass
, Instance
and init
code are moved to the gdnative::nativescript
module. Most of the commonly-used types are re-exported in the prelude
, but if you prefer individual imports, the paths need to be changed accordingly.
API enums
C enums in the API are now generated in submodules of gdnative_bindings
named after their associated objects. Common prefixes are also stripped from the constant names. The constants are accessed as associated constants:
Example
In 0.8, you would write this:
In 0.9, this should now be written as:
Fixed typos
Typos in variant names of VariantOperator
and GodotError
are fixed. Change to the correct names if this breaks your code.
Changes to derive macros
The NativeScript
derive macro now looks for new
instead of _init
as the constructor.
Example
In 0.8, you would write this:
In 0.9, this should now be written as:
Argument casts
Generated methods taking objects, Variant
, and GodotString
are now made generic using impl Trait
in argument position. This make calls much less verbose, but may break inference for some existing code. If you have code that looks like:
..., you should change this to:
Explicit nulls
A side effect of accepting generic arguments is that inference became tricky for Option
. As a solution for this, an explicit Null
type is introduced for use as method arguments. To obtain a Null
value, you may use either GodotObject::null()
or Null::null()
. You may also call null
on specific types since it is a trait method for all API types, e.g. Node::null()
or Object::null()
.
For example, to clear the script on an object, instead of:
..., you should now write:
This is arguably less convenient, but passing explicit nulls should be rare enough a use case that the benefits of having polymorphic arguments are much more significant.
Object semantics
In 0.9, the way Godot objects are represented in the API is largely remodeled to closely match the behavior of Godot. For the sake of illustration, we'll use the type Node
in the following examples.
In 0.8.x, bare objects like Node
are either unsafe or reference-counted pointers. Some of the methods require &mut
receivers, but there is no real guarantee of uniqueness since the pointers may be cloned or aliased freely. This restriction is not really useful for safety, and requires a lot of operations to be unsafe
.
This is changed in 0.9 with the typestate pattern, which will be explained later. Now, there are three representations of Godot objects, with different semantics:
Type | Terminology | Meaning |
---|---|---|
&'a Node | bare reference | reference assumed or guaranteed to be valid and uncontended during 'a |
Ref<Node, Access> | persistent reference | stored reference whose validity is not always known, depending on the typestate Access |
TRef<'a, Node, Access> | temporary reference | reference assumed or guaranteed to be valid and uncontended during 'a , with added typestate tracking |
Note that owned bared objects, like Node
, no longer exist in the new API. They should be replaced with Ref
or TRef
depending on the situation:
- In persistent data structures, like
NativeScript
structs, useRef<Node, Access>
. - When taking the
owner
argument, use&Node
orTRef<'a, Node, Shared>
. The latter form allows for more safe operations, like using as method arguments, thanks to the typestate. - When taking objects from the engine, like as arguments other than
owner
, useRef<Node, Shared>
. - When passing objects to the engine as arguments or return values, use
&Ref<Node, Shared>
,Ref<Node, Unique>
, orTRef<'a, Node, Shared>
. - When passing temporary references around in internal code, use
TRef<'a, Node, Access>
.
All objects are also seen as having interior mutability in Rust parlance, which means that all API methods now take &self
instead of &mut self
. For more information about shared (immutable) and unique (mutable) references, see Accurate mental model for Rust's reference types by dtolnay.
Unlike the previous versions requiring ubiquitous unsafe
s, the new API allows for clearer separation of safe and unsafe code in Rust. In general, unsafe
is only necessary around the border between Godot and Rust, while most of the internal code can now be safe.
To convert a Ref
to a TRef
, use Ref::assume_safe
or Ref::as_ref
depending on the object type and typestate. To convert non-unique TRef
s to Ref
s, use TRef::claim
. Bare references are usually obtained through Deref
, and it's not recommended to use them directly.
For detailed information about the API, see the type-level documentation on Ref
and TRef
.
Example
If you prefer to learn by examples, here is the simplified code for instancing child scenes in the dodge-the-creeps example in 0.8. Note how everything is unsafe
due to Node
s being manually-managed:
In 0.9, this can now be written as (with added type annotations and explanations for clarity):
Casting
The casting API is made more convenient with 0.9, using the SubClass
trait. Casting is now covered by two generic methods implemented on all object reference types: cast
and upcast
. Both methods enforce cast validity statically, which means that the compiler will complain about casts that will always fail at runtime. The generated to_*
methods are removed in favor of upcast
.
Examples
Casting a Node
to Object
In 0.8, you would write either of:
node.to_object()
node.cast::<Object>().unwrap()
In 0.9, this should now be written as node.upcast::<Object>()
. This is a no-op at runtime because Node
is a subclass of Object
.
Casting an Object
to Node
The API for downcasting to concrete types is unchanged. You should still write object.cast::<Node>().unwrap()
.
Casting to a type parameter
In 0.8, you would write this:
In 0.9, casting to a type parameter is a bit more complicated due to the addition of static checks. You should now write:
Note that this function is also polymorphic over the Access
typestate, which is explained the the following section.
Typestates
The typestate pattern is introduced in 0.9 to statically provide fine-grained reference safety guarantees depending on thread access state. There are three typestates in the API:
Shared
, for references that may be shared between multiple threads.Shared
references areSend
andSync
.ThreadLocal
, for references that may only be shared on the current thread.ThreadLocal
references are!Send
and!Sync
. This is because sending aThreadLocal
reference to another thread violates the invariant.Unique
, for references that are globally unique (with the exception of specific engine internals likeObjectDB
).Unique
references areSend
but!Sync
. These references are always safe to use.
Users may convert between typestates using the into_*
and assume_*
methods found on various types that deal with objects or Variant
collections with interior mutability (Dictionary
and VariantArray
). Doing so has no runtime cost. The documentation should be referred to when calling the unsafe assume_*
methods to avoid undefined behavior.
Typestates are encoded in types as the Access
type parameter, like the ones in Ref<T, Access>
and Dictionary<Access>
. These type parameters default to Shared
.
The general guidelines for using typestates are as follows:
- References obtained from the engine are
Shared
by default. This includes unique objects returned by generated API methods, because there isn't enough information to tell them from others. - Constructors return
Unique
references. ThreadLocal
references are never returned directly. They can be created manually from other references, and can be used Godot objects and collections internally kept in Rust objects.
Examples
Creating a Dictionary
and returning it
In 0.8, you would write this:
In 0.9, this should now be written as (with added type annotations and explanations for clarity):
Using an object argument other than owner
In 0.8, you would write this:
In 0.9, this should now be written as (with added type annotations and explanations for clarity):