Struct Unexpected
pub struct Unexpected { /* private fields */ }Expand description
Ergonomic catch-all error type for #[func] methods.
strat::Unexpected is intended for potential bugs that are fixed during development. They should not appear in Release builds.
Do not use this for runtime errors that are expected (e.g. loading a savegame that can be corrupted).
When this error is returned, Godot logs it with godot_error!. The calling code cannot reliably handle it.
This strategy is comparable to panics in Rust: the calling code is more ergonomic in the happy path, assuming that there are no errors.
In case that Err(strat::Unexpected) is returned, the calling code either aborts the function (debug varcall only) or continues with a
default value of the declared return type, which may introduce silent logic errors. GDScript has best-effort detection of such errors in Debug
mode; see below. The Rust object and its state remain valid after an error and can be used in subsequent calls.
This is currently the only ErrorToGodot impl that preserves type safety: for a Rust #[func] returning Result<T, strat::Unexpected>,
GDScript’s static analysis sees the return type T. If you want to handle errors at runtime, choose another ErrorToGodot impl.
strat::Unexpected enables automatic conversions from other errors via ? operator. This means you can mix different error types within the
same function body – each one propagates via ? and its message is forwarded to Godot. Use the func_bail! macro for early returns with
an error message.
§Example
use godot::prelude::*;
#[godot_api]
impl PlayerCharacter {
// Verifies that required nodes are present in the developer-authored scene tree.
// Missing nodes are a scene setup bug, not an expected runtime condition.
#[func]
fn init_player(&mut self) -> Result<(), strat::Unexpected> {
// Node missing = scene setup bug.
let Some(hand) = self.base().try_get_node_as::<Node3D>("Skeleton3D/Hand") else {
func_bail!("Player {}: 3D model is missing a hand", self.id);
};
// HashMap loaded at startup; missing or unparseable values are a code bug.
let max_health = self.config_map
.get("max_health")
.ok_or("'max_health' key missing in config")? // &str
.parse::<i64>()?; // ParseIntError
// Initialize self with hand + max_health...
Ok(())
}
}This example uses Node::try_get_node_as(), the fallible counterpart to
Node::get_node_as(). The latter panics on failure. From GDScript, the observable behavior is
the same, however the try_* + ? approach allows more control over error propagation and works entirely without a Rust panic.
§Behavior on the call site
How a caller sees the Err variant of a returned Result<T, strat::Unexpected> depends:
- Rust: When calling a
#[func]viaObject::try_call()reflection, the caller will always seeUnexpectederrors manifest asErrin the return type. This is the only way to reliably catchUnexpectederrors, and works only because godot-rust maintains internal state. - GDScript: If an
Unexpectederror is returned from Rust, the calling GDScript function will abort/fail if both conditions are true:- the call uses varcall (not ptrcall): invoked on an untyped
Variantor using reflection viaObject.call(). - it runs in a Godot debug/editor build.
- the call uses varcall (not ptrcall): invoked on an untyped
- Everything else returns Godot’s default value for the type
T(e.g.nullfor objects/variants, 0 for ints, etc.). This also applies to other languages calling such a method (C#, godot-cpp, etc.).
§std::error::Error and trait coherence
This type intentionally does not implement std::error::Error – the reason is a Rust coherence constraint.
If Unexpected implemented Error, the blanket impl<E: Into<Box<dyn Error + ...>>> From<E> for Unexpected would conflict with the standard
library’s impl<T> From<T> for T, because Unexpected would satisfy both E = Unexpected and Unexpected: Into<Box<dyn Error>>.
Omitting the Error impl sidesteps this conflict and is the same technique used by anyhow::Error.
Because Unexpected is typically the last error in a chain – returned to Godot via #[func] – the lack of direct error APIs is usually
not a big problem.
Implementations§
§impl Unexpected
impl Unexpected
pub fn new(err: impl Error + Send + Sync + 'static) -> Unexpected
pub fn new(err: impl Error + Send + Sync + 'static) -> Unexpected
Create a Unexpected from any type implementing std::error::Error.
pub fn downcast_ref<E>(&self) -> Option<&E>where
E: Error + 'static,
pub fn downcast_ref<E>(&self) -> Option<&E>where
E: Error + 'static,
Attempt to downcast the inner error to a concrete type.
Trait Implementations§
§impl Debug for Unexpected
impl Debug for Unexpected
§impl Display for Unexpected
impl Display for Unexpected
§impl<T> ErrorToGodot<T> for Unexpected
impl<T> ErrorToGodot<T> for Unexpected
§fn result_to_godot(result: Result<&T, &Unexpected>) -> CallOutcome<T>
fn result_to_godot(result: Result<&T, &Unexpected>) -> CallOutcome<T>
Result<T, Self> to a Godot return value or an unexpected-error message.§impl<E> From<E> for Unexpected
Enables the ? operator for any E: Into<Box<dyn Error + Send + Sync + 'static>>.
impl<E> From<E> for Unexpected
Enables the ? operator for any E: Into<Box<dyn Error + Send + Sync + 'static>>.
The following types satisfy this bound and can therefore be used with ? or passed to Unexpected::new():
| Source type | How it converts |
|---|---|
Any E: Error + Send + Sync + 'static | Boxed directly; covers std::io::Error, ParseIntError, and any custom error type |
Box<dyn Error + Send + Sync + 'static> | Used as-is |
String | Wrapped in a message-only error |
&str (any lifetime) | Copied to String, then wrapped – the lifetime is not propagated |