Object references
Every Unreal object Rust code touches, an actor, a component, an asset, a class’s default object, is reached through a reference. Unreal owns the objects and destroys them when its garbage collector decides; Rust code holds references to them and checks them before use.
UObjectRef
#![allow(unused)]
fn main() {
let target: UObjectRef<Actor> = self.current_target();
}
UObjectRef<T> is a handle to an object of class T: eight bytes, Copy, cheap to pass around and to store. It does not keep the object alive, and the object may be destroyed while you hold it.
| Method | What it does |
|---|---|
is_valid() | Whether the handle points to an object (it can still be pending destruction). |
checked() | Verifies the object is alive and gives access to its functions, or an ObjectDestroyed error. |
cast::<U>() | The same object as class U, checked at runtime, or an InvalidCast error naming the object and the class expected. |
upcast_to::<U>() | The same object as an ancestor class U, checked at compile time. |
is_a::<U>() | Whether the object is of class U or derives from it. |
get_name() | The object’s name. |
pin() | A Pinned<T> that keeps the object alive. |
UObjectRef::null() | A handle to nothing, also its Default. |
Calling functions: checked
Engine functions are called on a checked reference:
#![allow(unused)]
fn main() {
let me = self.as_ref().checked()?;
let location = me.k2_get_actor_location().to_dvec3();
me.k2_destroy_actor();
}
checked() verifies the object is still alive once, and the calls after it go straight to the engine. A call on a destroyed object would crash the engine in C++; in Rust, checked() returns an error first, and the code decides what to do:
#![allow(unused)]
fn main() {
let Ok(target) = self.current_target().checked() else {
return;
};
}
Casting
#![allow(unused)]
fn main() {
if let Ok(character) = other_actor.cast::<Character>() {
}
let as_object: UObjectRef<Object> = actor.upcast_to::<Object>();
}
cast is C++’s Cast<T>: a runtime check. upcast_to is free and cannot fail, because the compiler knows Character is an Actor. The engine functions that take a world context or a base class (UObject* WorldContextObject, AActor*) take the upcast:
#![allow(unused)]
fn main() {
let world_context = self.as_ref().upcast_to();
let player = GameplayStatics::get_player_pawn(world_context, 0);
}
An object’s class and outer come typed as well: actor.class()? is a SubclassOf<T> of the reference’s own type, and component.outer::<Actor>()? is the outer, checked to be an actor.
For your own Rust classes, MyClass::from_obj(reference) does the cast and gives the Rust class with its getters and methods. See Declaring a class.
Keeping an object alive
The garbage collector keeps an object alive while something it knows about references it. A UObjectRef in a Rust-only field or a local variable is invisible to it. To keep an object:
-
Hold it in a
#[uproperty]. The usual way, and what C++ does withUPROPERTY()pointers.#![allow(unused)] fn main() { #[uproperty] spawned_weapon: UObjectRef<Weapon>, } -
Pin it.
Pinned<T>is a garbage collection root: the object lives until thePinnedis dropped. For objects Rust code alone keeps, outside any class.#![allow(unused)] fn main() { let pinned: Pinned<Actor> = actor.pin()?; let location = pinned.k2_get_actor_location(); } -
Watch it.
TWeakObjectPtr<T>does not keep the object, and tells you when it is gone:#![allow(unused)] fn main() { let weak = TWeakObjectPtr::from_ref(&actor); if let Some(actor) = weak.get() { } }
Classes: SubclassOf
A class is not an object reference. SubclassOf<T> is Unreal’s TSubclassOf<T>, a class that is T or derives from it:
#![allow(unused)]
fn main() {
#[uproperty(EditAnywhere)]
projectile_class: SubclassOf<Projectile>,
}
| Expression | What it is |
|---|---|
SubclassOf::<Projectile>::base() | The Projectile class itself. |
class.upcast_to::<Actor>() | The same class, as a SubclassOf<Actor>, for engine functions that take any actor class. |
class.is_null() | Whether no class is set (a Blueprint child that did not fill the property). |
Engine functions that take a class filter (get_all_actors_of_class, get_overlapping_actors) take a SubclassOf:
#![allow(unused)]
fn main() {
let units = GameplayStatics::get_all_actors_of_class(
self.as_ref().upcast_to(),
SubclassOf::<Unit>::base().upcast_to(),
);
}
Threads
References are Send, so a background thread can carry them, but Unreal objects belong to the game thread: call the engine only from there. See Work off the game thread.