Module builtin
Expand description
Built-in types like Vector2
, GString
and Variant
.
Please read the book chapter about builtin types.
§API design
Our goal is to strive for a middle ground between idiomatic Rust and existing Godot APIs, achieving a decent balance between ergonomics,
correctness and performance. We leverage Rust’s type system (such as Option<T>
or enum
) where it helps expressivity.
We have been using a few guiding principles. Those apply to builtins in particular, but some are relevant in other modules, too.
§1. Copy
for value types
Value types are types with public fields and no hidden state. This includes all geometric types, colors and RIDs.
All value types implement the Copy
trait and thus have no custom Drop
impl.
§2. By-value (self
) vs. by-reference (&self
) receivers
Most Copy
builtins use by-value receivers. The exception are matrix-like types (e.g., Basis
, Transform2D
, Transform3D
, Projection
),
whose methods operate on &self
instead. This is close to how the underlying glam
library handles it.
§3. Default
trait only when the default value is common and useful
Default
is deliberately not implemented for every type. Rationale:
- For some types, the default representation (as per Godot) does not constitute a useful value. This goes against Rust’s
Default
docs, which explicitly mention “A trait for giving a type a useful default value”. For example,Plane()
in GDScript creates a degenerate plane which cannot participate in geometric operations. - Not providing
Default
makes users double-check if the value they want is indeed what they intended. While it seems convenient, not having implicit default or “null” values is a design choice of Rust, avoiding the Billion Dollar Mistake. In many situations,Option
orOnReady
is a better alternative. - For cases where the Godot default is truly desired, we provide an
invalid()
constructor, e.g.Callable::invalid()
orPlane::invalid()
. This makes it explicit that you’re constructing a value that first has to be modified before becoming useful. When used in class fields,#[init(val = ...)]
can help you initialize such values. - Outside builtins, we do not implement
Gd::default()
for manually managed types, as this makes it very easy to overlook initialization (e.g. in#[derive(Default)]
) and leak memory. AGd::new_alloc()
is very explicit.
§4. Prefer explicit conversions over From
trait
From
is quite popular in Rust, but unlike traits such as Debug
, the convenience of From
can come at a cost. Like every feature, adding
an impl From
needs to be justified – not the other way around: there doesn’t need to be a particular reason why it’s not added. But
there are in fact some trade-offs to consider:
From
next to named conversion methods/constructors adds another way to do things. While it’s sometimes good to have choice, multiple ways to achieve the same has downsides: users wonder if a subtle difference exists, or if all options are in fact identical. It’s unclear which one is the “preferred” option. Recognizing other people’s code becomes harder, because there tend to be dialects.- It’s often a purely stylistic choice, without functional benefits. Someone may want to write
(1, 2).into()
instead ofVector2::new(1, 2)
. This is not strong enough of a reason – if brevity is of concern, a functionvec2(1, 2)
does the job better. From
is less explicit than a named conversion function. If you seestring.to_variant()
orcolor.to_hsv()
, you immediately know the target type.string.into()
andcolor.into()
lose that aspect. Even with(1, 2).into()
, you’d first have to check whetherFrom
is only converting the tuple, or if it also provides ani32
-to-f32
cast, thus resulting inVector2
instead ofVector2i
. This problem doesn’t exist with named constructor functions.- The
From
trait doesn’t play nicely with type inference. If you writelet v = string.to_variant()
, rustc can infer the type ofv
based on the right-hand expression alone. With.into()
, you need follow-up code to determine the type, which may or may not work. Temporarily commenting out such non-local code breaks the declaration line, too. To make matters worse, turbofish.into::<Type>()
isn’t possible either. - Rust itself requires that
From
conversions are infallible, lossless, value-preserving and obvious. This rules out a lot of scenarios such asBasis::to_quaternion()
(which only maintains the rotation part, not scale) orColor::try_to_hsv()
(which is fallible and lossy).
One main reason to support From
is to allow generic programming, in particular impl Into<T>
parameters. This is also the reason
why the string types have historically implemented the trait. But this became less relevant with the advent of
AsArg<T>
taking that role, and thus may change in the future.
§5. Option
for fallible operations
GDScript often uses degenerate types and custom null states to express that an operation isn’t successful. This isn’t always consistent:
Rect2::intersection()
returns an empty rectangle (i.e. you need to check its size).Plane::intersects_ray()
returns aVariant
which is NIL in case of no intersection. While this is a better way to deal with it, it’s not immediately obvious that the result is a point (Vector2
), and comes with extra marshaling overhead.
Rust uses Option
in such cases, making the error state explicit and preventing that the result is accidentally interpreted as valid.
§6. Public fields and soft invariants
Some geometric types are subject to “soft invariants”. These invariants are not enforced at all times but are essential for certain operations. For example, bounding boxes must have non-negative volume for operations like intersection or containment checks. Planes must have a non-zero normal vector.
We cannot make them hard invariants (no invalid value may ever exist), because that would disallow the convenient public fields, and
it would also mean every value coming over the FFI boundary (e.g. an #[export]
field set in UI) would constantly need to be validated
and reset to a different “sane” value.
For geometric operations, Godot often doesn’t specify the behavior if values are degenerate, which can propagate bugs that then lead to follow-up problems. godot-rust instead provides best-effort validations during an operation, which cause panics if such invalid states are detected (at least in Debug mode). Consult the docs of a concrete type to see its guarantees.
§7. RIIR for some, but not all builtins
Builtins use varying degrees of Rust vs. engine code for their implementations. This may change over time and is generally an implementation detail.
- 100% Rust, often supported by the
glam
library:- all vector types (
Vector2
,Vector2i
,Vector3
,Vector3i
,Vector4
,Vector4i
) - all bounding boxes (
Rect2
,Rect2i
,Aabb
) - 2D/3D matrices (
Basis
,Transform2D
,Transform3D
) Plane
Rid
(just an integer)
- all vector types (
- Partial Rust:
Color
,Quaternion
,Projection
- Only Godot FFI: all others (containers, strings, callables, variant, …)
The rationale here is that operations which are absolutely ubiquitous in game development, such as vector/matrix operations, benefit a lot from being directly implemented in Rust. This avoids FFI calls, which aren’t necessarily slow, but remove a lot of optimization potential for rustc/LLVM.
Other types, that are used less in bulk and less often in performance-critical paths (e.g. Projection
), partially fall back to Godot APIs.
Some operations are reasonably complex to implement in Rust, and we’re not a math library, nor do we want to depend on one besides glam
.
An ever-increasing maintenance burden for geometry re-implementations is also detrimental.
TLDR: it’s a trade-off between performance, maintenance effort and correctness – the current combination of glam
and Godot seems to be a
relatively well-working sweet spot.
§8. glam
types are not exposed in public API
While Godot and glam
share common operations, there are also lots of differences and Godot specific APIs.
As a result, godot-rust defines its own vector and matrix types, making glam
an implementation details.
Alternatives considered:
-
Re-export types of an existing vector algebra crate (like
glam
). Thegdnative
crate started out this way, using types fromeuclid
, but became impractical. Even with extension traits, there would be lots of compromises, where existing and Godot APIs differ slightly.Furthermore, it would create a strong dependency on a volatile API outside our control.
glam
had 9 SemVer-breaking versions over the timespan of two years (2022-2024). While it’s often easy to migrate and the changes notably improve the library, this would mean that any breaking change would also become breaking for godot-rust, requiring a SemVer bump. By abstracting this, we can have our own timeline. -
We could opaquely wrap types, i.e.
Vector2
would contain a privateglam::Vec2
. This would prevent direct field access, which is extremely inconvenient for vectors. And it would still require us to redefine the front-end of the entire API.
Eventually, we might add support for mint
to allow conversions to other linear algebra libraries in the
ecosystem. (Note that mint
intentionally offers no math operations, see e.g. mint#75).
Modules§
- Iterator types for arrays and dictionaries.
- Math-related functions and traits like
ApproxEq
. - Specialized types related to Godot’s various string implementations.
Macros§
- Constructs
Array
literals, similar to Rust’s standardvec!
macro. - Constructs
Dictionary
literals, close to Godot’s own syntax. - A macro to coerce float-literals into the
real
type. - Array of reals.
- Access vector components in different order.
- Constructs
VariantArray
literals, similar to Rust’s standardvec!
macro.
Structs§
- Axis-aligned bounding box in 3D space.
- Godot’s
Array
type. - A 3x3 matrix, typically used as an orthogonal basis for
Transform3D
. - A
Callable
represents a function in Godot. - Color built-in type, in floating-point RGBA format.
- HSVA floating-number Color representation.
- Godot’s
Dictionary
type. - Godot’s reference counted string type.
- A pre-parsed scene tree path.
- Implements Godot’s
PackedByteArray
type, which is a space-efficient array ofu8
s. - Implements Godot’s
PackedColorArray
type, which is a space-efficient array ofColor
s. - Implements Godot’s
PackedFloat32Array
type, which is a space-efficient array off32
s. - Implements Godot’s
PackedFloat64Array
type, which is a space-efficient array off64
s. - Implements Godot’s
PackedInt32Array
type, which is a space-efficient array ofi32
s. - Implements Godot’s
PackedInt64Array
type, which is a space-efficient array ofi64
s. - Implements Godot’s
PackedStringArray
type, which is a space-efficient array ofGString
s. - Implements Godot’s
PackedVector2Array
type, which is a space-efficient array ofVector2
s. - Implements Godot’s
PackedVector3Array
type, which is a space-efficient array ofVector3
s. - Implements Godot’s
PackedVector4Array
type, which is a space-efficient array ofVector4
s. - 3D plane in Hessian normal form.
- A 4x4 matrix used for 3D projective transformations.
- Unit quaternion to represent 3D rotations.
- 2D axis-aligned bounding box.
- 2D axis-aligned integer bounding box.
- A
Signal
represents a signal of an Object instance in Godot. - A string optimized for unique names.
- Affine 2D transform (2x3 matrix).
- Affine 3D transform (3x4 matrix).
- Godot variant type, able to store a variety of different types.
- Godot enum name:
Variant.Operator
. - Godot enum name:
Variant.Type
. - Vector used for 2D math using floating point coordinates.
- Vector used for 3D math using floating point coordinates.
- Vector used for 4D math using floating point coordinates.
- Vector used for 2D math using integer coordinates.
- Vector used for 3D math using integer coordinates.
- Vector used for 4D math using integer coordinates.
Enums§
- Defines how individual color channels are laid out in memory.
- This enum is exhaustive; you should not expect future Godot versions to add new enumerators.
- This enum is exhaustive; you should not expect future Godot versions to add new enumerators.
- The eye to create a projection for, when creating a projection adjusted for head-mounted displays.
- A projection’s clipping plane.
- A RID (“resource ID”) is an opaque handle that refers to a Godot
Resource
. - This enum is exhaustive; you should not expect future Godot versions to add new enumerators.
- Enumerates the axes in a
Vector2
. - Enumerates the axes in a
Vector3
. - Enumerates the axes in a
Vector4
.
Traits§
- Convenience conversion between
real
andf32
/f64
. - Represents a custom callable object defined in Rust.
Type Aliases§
- A Godot
Array
without an assigned type. - Floating point type used for many structs and functions in Godot.