Constructors
While Rust does not have constructors as a language feature (like C++ or C#), associated functions that return a new object are commonly called "constructors". We extend the term to include slightly deviating signatures, but conceptually constructors are always used to construct new objects.
Godot has a special constructor, which we call the Godot default constructor or simply init
. This is comparable to the _init
method in
GDScript.
Table of contents
Default constructor
The constructor of any GodotClass
object is called init
in gdext. This constructor is necessary to instantiate the object in Godot.
It is invoked by the scene tree or when you write Monster.new()
in GDScript.
There are two options to define the constructor: let gdext generate it or define it manually. It is also possible to opt out of init
if you
don't need Godot to default-construct your object.
Library-generated init
You can use #[class(init)]
to generate a constructor for you. This is limited to simple cases, and it calls Default::default()
for each
field (except the Base<T>
one, which is correctly wired up with the base object).
#![allow(unused)] fn main() { #[derive(GodotClass)] #[class(init, base=Node3D)] struct Monster { name: String, // initialized to "" hitpoints: i32, // initialized to 0 base: Base<Node3D>, // wired up } }
To provide another default value, use #[init(default = value)]
. This should only be used for simple cases, as it may lead to difficult-to-read
code and error messages. This API may also still change.
#![allow(unused)] fn main() { #[derive(GodotClass)] #[class(init, base=Node3D)] struct Monster { name: String, // initialized to "" #[init(default = 100)] hitpoints: i32, // initialized to 100 base: Base<Node3D>, // wired up } }
Manually defined init
We can provide a manually-defined constructor by overriding the trait's associated function init
:
#![allow(unused)] fn main() { #[derive(GodotClass)] #[class(base=Node3D)] // No init here, since we define it ourselves. struct Monster { name: String, hitpoints: i32, base: Base<Node3D>, } #[godot_api] impl INode3D for Monster { fn init(base: Base<Node3D>) -> Self { Self { name: "Nomster".to_string(), hitpoints: 100, base, } } } }
As you can see, the init
function takes a Base<Node3D>
as its one and only parameter. This is the base class instance, which is typically
just forwarded to its corresponding field in the struct, here base
.
The init
method always returns Self
. You may notice that this is currently the only way to construct a Monster
instance. As soon as your
struct contains a base field, you can no longer provide your own constructor, as you can't provide a value for that field. This is by design and
ensures that if you need access to the base, that base comes from Godot directly.
However, fear not: you can still provide all sorts of constructors, they just need to go through dedicated functions that internally call init
.
More on this in the next section.
Disabled init
You don't always need to provide a default constructor to Godot. Reasons to not have a constructor include:
- Your class is not a node that should be added to the tree as part of a scene file.
- You require custom parameters to be provided for your object invariants -- a default value is not meaningful.
- You only need to construct objects from Rust code, not from GDScript or the Godot editor.
To disable the init
constructor, you can use #[class(no_init)]
:
#![allow(unused)] fn main() { #[derive(GodotClass)] #[class(no_init, base=Node3D)] struct Monster { name: String, hitpoints: i32, base: Base<Node3D>, } }
Not providing/generating an init
method and forgetting to use #[class(no_init)]
will result in a compile-time error.
Custom constructors
The default constructor init
is not always useful, as it may leave objects in an incorrect state.
For example, a Monster
will always have the same values for name
and hitpoints
upon construction, which may not be desired.
Let's provide a more suitable constructor, which accepts those attributes as parameters.
#![allow(unused)] fn main() { // Default constructor from before. #[godot_api] impl INode3D for Monster { fn init(base: Base<Node3D>) -> Self { ... } } // New custom constructor. #[godot_api] impl Monster { #[func] // Note: the following is incorrect. fn from_name_hp(name: GString, hitpoints: i32) -> Self { ... } } }
But now, how to fill in the blanks? Self
requires a base object, how to obtain it? In fact, we cannot return Self
here.
When interacting with Godot from Rust, all objects (class instances) need to be transported inside the Gd
smart pointer -- whether
they appear as parameters or return types.
The return types of init
and a few other gdext-provided functions are an exception, because the library requires at this point that you
have a value of the raw object. You never need to return Self
in your own defined #[func]
functions.
For details, consult the chapter about objects or the Gd<T>
API docs.
So we need to return Gd<Self>
instead of Self
.
Objects with a base field
If your class T
contains a Base<...>
field, you cannot create a standalone instance -- you must encapsulate it in Gd<T>
.
You can also not extract a T
from a Gd<T>
smart pointer anymore; since it has potentially been shared with the Godot engine, this would
not be a safe operation.
To construct Gd<Self>
, we can use Gd::from_init_fn()
, which takes a closure. This closure accepts a Base
object
and returns an instance of Self
. In other words, it has the same signature as init
-- this presents an alternative way of constructing
Godot objects, while allowing to pass in addition context.
The result of Gd::from_init_fn()
is a Gd<Self>
object, which can be directly returned by Monster::from_name_hp()
.
#![allow(unused)] fn main() { #[godot_api] impl Monster { #[func] fn from_name_hp(name: GString, hitpoints: i32) -> Gd<Self> { // Function contains a single statement, the `Gd::from_init_fn()` call. Gd::from_init_fn(|base| { // Accept a base of type Base<Node3D> and directly forward it. Self { name: name.into(), // Convert GString -> String. hitpoints, base, } }) } } }
That's it! The just added associated function is now registered in GDScript and effectively works as a constructor:
var monster = Monster.from_name_hp("Nomster", 100)
Objects without a base field
For classes that don't have a base field, you can simply use Gd::from_object()
instead of Gd::from_init_fn()
.
This is often useful for data bundles, which don't define much logic but are an object-oriented way to bundle related data in a single
type. Such classes are typically subclasses of RefCounted
or Resource
.
#![allow(unused)] fn main() { #[derive(GodotClass)] #[class(no_init)] // We only provide a custom constructor. // Since there is no #[class(base)] key, the base class will default to RefCounted. struct MonsterConfig { color: Color, max_hp: i32, tex_coords: Vector2i, } #[godot_api] impl MonsterConfig { // Not named 'new' since MonsterConfig.new() in GDScript refers to default. #[func] fn create(color: Color, max_hp: i32, tex_coords: Vector2i) -> Gd<Self> { Gd::from_object(Self { color, max_hp, tex_coords, }) } } }
Destructors
You do not typically need to declare your own destructors, if you manage memory through RAII. If you do however need custom
cleanup logic, simply declare the Drop
trait for your type:
#![allow(unused)] fn main() { impl Drop for Monster { fn drop(&mut self) { godot_print!("Monster '{}' is being destroyed!", self.name); } } }
Drop::drop()
is invoked as soon as Godot orders the destruction of your Gd<T>
smart pointer -- either if it is manually freed, or if the
last reference to it goes out of scope.
Conclusion
Constructors allow to initialize Rust classes in various ways. You can generate, implement, or disable the default constructor init
, and you
can provide as many custom constructors with different signatures as you like.