Skip to main content

Unexpected

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] via Object::try_call() reflection, the caller will always see Unexpected errors manifest as Err in the return type. This is the only way to reliably catch Unexpected errors, and works only because godot-rust maintains internal state.
  • GDScript: If an Unexpected error 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 Variant or using reflection via Object.call().
    • it runs in a Godot debug/editor build.
  • Everything else returns Godot’s default value for the type T (e.g. null for 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

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,

Attempt to downcast the inner error to a concrete type.

Trait Implementations§

§

impl Debug for Unexpected

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl Display for Unexpected

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl<T> ErrorToGodot<T> for Unexpected
where T: ToGodot + Clone,

§

type Mapped = T

The type to which Result<T, Self> is mapped on Godot side.
§

fn result_to_godot(result: Result<&T, &Unexpected>) -> CallOutcome<T>

Map a Result<T, Self> to a Godot return value or an unexpected-error message.
§

impl<E> From<E> for Unexpected
where E: Into<Box<dyn Error + Sync + Send>>,

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 typeHow it converts
Any E: Error + Send + Sync + 'staticBoxed directly; covers std::io::Error, ParseIntError, and any custom error type
Box<dyn Error + Send + Sync + 'static>Used as-is
StringWrapped in a message-only error
&str (any lifetime)Copied to String, then wrapped – the lifetime is not propagated
§

fn from(err: E) -> Unexpected

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<!> for T

Source§

fn from(t: !) -> T

Converts to this type from the input type.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.