Registering classes

Classes are the backbone of data modeling in Godot. If you want to build complex user-defined types in a type-safe way, you won't get around classes. Arrays, dictionaries and simple types only get you so far, and overusing them defeats the purpose of using a statically typed language.

Rust makes class registration straightforward. As mentioned before, Rust syntax is used as a baseline, with gdext-specific additions.

See also GDScript reference for classes.

Table of contents

Defining a Rust struct

In Rust, Godot classes are represented by structs. Structs are defined as usual and can contain any number of fields. To register them with Godot, you need to derive the GodotClass trait.

GodotClass trait

The GodotClass trait marks all classes known in Godot. It is already implemented for engine classes, for example Node or Resource. If you want to register your own classes, you need to implement GodotClass as well.

#[derive(GodotClass)] streamlines this process and takes care of all the boilerplate.
See API docs for detailed information.

Let's define a simple class named Monster:

#![allow(unused)]
fn main() {
#[derive(GodotClass)]
struct Monster {
    name: String,
    hitpoints: i32,
}
}

That's it. Immediately after compiling, this class becomes available in Godot through hot reloading (before Godot 4.2, after restart). It won't be very useful yet, but the above definition is enough to register Monster in the engine.

Auto-registration

#[derive(GodotClass)] automatically registers the class -- you don't need an explicit add_class() registration call or a central list mentioning all classes.

The proc-macro internally registers the class in such a list at startup time.

Selecting a base class

By default, the base class of a Rust class is RefCounted. This is consistent with GDScript when you omit the extends keyword.

RefCounted is quite useful for data bundles. As implied by the name, it allows sharing instances tracked by a reference counter; as such, you don't need to worry about memory management. Resource is a subclass of RefCounted and is useful for data that needs to be serialized to the filesystem.

However, if you want your class to be part of the scene tree, you need to use Node (or one of its derived classes) as a base class.

Here, we use a more concrete node type, Node3D. This is done by specifying #[class(base=Node3D)] on the struct definition:

#![allow(unused)]
fn main() {
#[derive(GodotClass)]
#[class(base=Node3D)]
struct Monster {
    name: String,
    hitpoints: i32,
}
}

The base field

Since Rust does not have inheritance, we need to use composition to achieve the same effect. gdext provides a Base<T> type, which lets us store the instance of the Godot superclass (base class) as a field in our Monster class.

#![allow(unused)]
fn main() {
#[derive(GodotClass)]
#[class(base=Node3D)]
struct Monster {
    name: String,
    hitpoints: i32,
    base: Base<Node3D>,
}
}

The important part is the Base<T> type. T must match the base class you specified in the #[class(base=...)] attribute. You can also use the associated type Self::Base for T.

When you declare a base field in your struct, the #[derive] procedural macro will automatically detect the Base<T> type.[^inference] This lets you access the Node API through provided methods self.base() and self.base_mut(), but more on this later.

Conclusion

You have learned how to define a Rust class and register it with Godot. You now know that different base classes exist and how to select one.

The next chapters cover functions and constructors.



[^inference] You can tweak the type detection using the #[hint] attribute, see the corresponding docs.