Built-in types

The so-called "built-in types" or just "builtins" are the basic types that Godot provides. Notably, these are not classes. See also basic built-in types in Godot.

Table of contents

List of types

Here is an exhaustive list of all built-in types, by category. We use the GDScript names; below, we explain how they map to Rust.

Simple types

  • Boolean: bool
  • Numeric: int, float

Composite types

  • Variant (able to hold anything): Variant
  • String types: String, StringName, NodePath
  • Ref-counted containers: Array (Array[T]), Dictionary
  • Packed arrays: Packed*Array for following element types:
    Byte, Int32, Int64, Float32, Float64, Vector2, Vector3, Color, String
  • Functional: Callable, Signal

Geometric types

  • Vectors: Vector2, Vector2i, Vector3, Vector3i, Vector4, Vector4i
  • Bounding boxes: Rect2, Rect2i, AABB
  • Matrices: Transform2D, Transform3D, Basis, Projection
  • Rotation: Quaternion
  • Geometric objects: Plane

Miscellaneous

  • Color: Color
  • Resource ID: RID

Rust mapping

Rust types in the gdext API represent the corresponding Godot types in the closest way possible. They are used in parameter and return type position of API functions, for example.

Most builtins have a 1:1 equivalent (e.g. Vector2f, Color etc.). The following list highlights some noteworthy mappings:

GDScript typeRust typeRust example expression
inti64-12345
floatf643.14159
realreal (either f32 or f64)real!(3.14159)
StringGString"Some string".into()
StringNameStringName"MyClass".into()
NodePathNodePathNodes/MyNode".into()
Array[T]Array<T>array![1, 2, 3]
ArrayVariantArray
or Array<Variant>
varray![1, "two", true]
DictionaryDictionarydict!{"key": "value"}
AABBAabbAabb::new(pos, size)
ObjectGd<Object>Object::new_alloc()
SomeClassGd<SomeClass>Resource::new_gd()
SomeClass (nullable)Option<Gd<SomeClass>>None
Variant (also implicit)VariantVariant::nil()

Note that Godot does not have nullability information in its class API yet. This means that we have to conservatively assume that objects can be null, and thus use Option<Gd<T>> instead of Gd<T> for object return types. This often needs unnecessary unwrapping.

Nullable types are being looked into on Godot side. If there is no upstream solution for a while, we may consider our own workarounds, but it may come with manual annotation of many APIs.

String types

Godot provides three string types: String (GString in Rust), StringName, and NodePath. GString is used as a general-purpose string, while StringName is often used for identifiers like class or action names. The idea is that StringName is cheap to construct and compare.1

These types all support From traits to convert to/from Rust String, and from &str. You can thus use "some_string".into() expressions. If you need more explicit typing, use StringName::from("some_string").

StringName in particular provides a direct conversion from C-string literals such as c"string", introduced in Rust 1.77. This can be used for static C-strings, i.e. ones that remain allocated for the entire program lifetime. Don't use them for short-lived ones.

Arrays and dictionaries

Godot's linear collection type is Array<T>. It is generic over its element type T, which can be one of the supported Godot types (generally anything that can be represented by Variant). A special type VariantArray is provided as an alias for Array<Variant>, which is used when the element type is dynamically typed.

Dictionary is a key-value store, where both keys and values are Variant. Godot does currently not support generic dictionaries, although this feature is under discussion.

Arrays and dictionaries can be constructed using three macros:

#![allow(unused)]
fn main() {
let a = array![1, 2, 3];          // Array<i64>
let b = varray![1, "two", true];  // Array<Variant>
let c = dict!{"key": "value"};    // Dictionary
}

Their API is similar, but not identical to Rust's standard types Vec and HashMap. An important difference is that Array and Dictionary are reference-counted, which means that clone() will not create an independent copy, but another reference to the same instance. Furthermore, since internal elements are stored as variants, they are not accessible by reference, which is why the [] operator (Index/IndexMut traits) is absent.

#![allow(unused)]
fn main() {
let a = array![0, 11, 22];

assert_eq!(a.len(), 3);
assert_eq!(a.get(1), Some(11));  // Note: by value, not Some(&11).
assert_eq!(a.at(1), 11);         // Panics on out-of-bounds.

let mut b = a.clone();   // Increment reference-count.
b.set(2, 33);            // Modify new ref.
assert_eq!(a.at(2), 33); // Original array has changed.

b.clear();
assert_eq!(b, Array::new()); // new() creates an empty array.
}
#![allow(unused)]
fn main() {
let c = dict! {
    "str": "hello",
    "int": 42,
    "bool": true,
};

assert_eq!(c.len(), 3);
assert_eq!(c.get("str"), Some(&"hello".into()));

let mut d = c.clone();            // Increment reference-count.
d.insert("float", 3.14);          // Modify new ref.
assert!(c.contains_key("float")); // Original dict has changed.
}

To iterate, you can use iter_shared(). This method works almost like iter() on Rust collections, but the name highlights that you do not have unique access to the collection during iteration, since there might exist another reference. This also means it's your responsibility to ensure that the collection is not modified in unintended ways during iteration (which should be safe, but may lead to data inconsistencies).

#![allow(unused)]
fn main() {
let a = array!["one", "two", "three"];
let d = dict!{"one": 1, "two": 2.0, "three": Vector3::ZERO};

for elem in a.iter_shared() {
    println!("Element: {elem}");
}bu

for (key, value) in d.iter_shared() {
    println!("Key: {key}, value: {value}");
}
}

Packed arrays

Packed*Array types are used for storing elements space-efficiently in contiguous memory ("packed"). The * stands for the element type, e.g. PackedByteArray or PackedVector3Array.

#![allow(unused)]
fn main() {
// Create from slices.
let bytes = PackedByteArray::from(&[0x0A, 0x0B, 0x0C]);
let ints = PackedInt32Array::from(&[1, 2, 3]);

// Access as Rust shared/mutable slices.
let bytes_slice: &[u8] = b.as_slice();
let ints_slice: &mut [i32] = i.as_mut_slice();
}

Unlike Array, packed arrays use copy-on-write instead of reference counting. When you clone a packed array, you get a new independent instance. Cloning is cheap as long as you don't modify either instance. Once you use a write operation (anything with &mut self), the packed array will allocate its own memory and copy the data.



Footnotes

1

When constructing StringName from &str or String, the conversion is rather expensive, since it has to go through a conversion to UTF-32. As Rust recently introduced C-string literals (c"hello"), we can now directly construct from them. This is more efficient, but keeps memory allocated until shutdown, so don't use it for rarely used temporaries. See API docs and issue #531 for more information.