Skip to content

Opt-in support for functions that return Result<T,GodotError> to indicate failable operations #425

Open
@ogapo

Description

@ogapo

In order to support Result, we should first define a GdError enum that implements std::error::Error. It could replace engine::global::Error but I think it would probably make more sense to have our own enum so it's extensible. Ideally this would contain engine error as one option:

#[non_exhaustive]
pub enum GdError {
    Engine(engine::global::Error), // explicitly returned errors from Godot
    ArgMarshaling(MarshalingErrorDetails), // variant argument marshaling error
    ReturnMarshaling(MarshalingErrorDetails), // variant return value marshaling error
    ScriptError(ScriptErrorDetails), // an error occurred within GdScript
}
// impl std::error Error for GdError
// impl Display, etc
// impl From<> for the various internal types

Some of these *Details structs may just be unit types if there's no details available right now (Godot apparently doesn't have a GetLastScriptError API for instance, but would leave the door open for improvement in the future). It may also make sense to make multiple error enums for functions where only some of these are possible (perhaps GdCallError should differ for instance, YMMV) but the idea is the same.

Once this is defined, it would be ideal to have a policy of having a function which returns Result whenever an operation is failable. In particular, anything that triggers GDScript (inline script only, deferred script calls need not offer this guarantee) may potentially fail due to a script error and surfacing that failure to calling code is important to be able to enforce invariants. Only the calling code knows whether that error should be propagated or if it's safe to eat it, or perhaps handle it in some custom manner (possibly custom to that particular call site).

To that end, the following calls are proposed to extend the existing interface:

  • Object::try_call, Object::try_get, Object::try_set
  • Callable::try_callv
  • PackedScene::try_instantiate(&self)
  • If Object::emit_signal is inline, it should have a try, but I think it's deferred (not sure).
  • There's probably more candidates but these seem like the majority of the ways Rust invokes gdscript and would represent a great start IMHO.

These should all return the same value as the regular non-try function in the Ok case and GdError in the Err case. To make sure the code paths get exercise, the non-try variant could be implemented in terms of the try_* variant with .unwrap().

The above changes would allow Rust code to determine when scripts fail. To compliment this, it would also be good to accept Rust functions exposed to Godot that return values of the form Result<T : ToGodot, E : std::error::Error>. When called via Godot, this would simply return the Ok type or behave in the same was as though there was a trapped panic (in today's behavior) in the event of an Err.

#[godot_api]
impl MyStruct {
    #[func]
    pub fn foo(&self) -> Result<(),GdError> {
        self.base.try_call(StringName::from("bar"), &[])?; // propagate any script errors to the calling script
        Ok(())
    }
}

This seems like trivial behavior, but it has a few advantages:

  1. Not all callsites for marshaled functions necessarily come from Godot. Rust callsites can still freely handle the Result so enabling that function signature would have ergonomic value.
  2. It means that in the fairly common case where Godot calls into Rust which then calls back into Godot (via call/set/get/etc), the Rust function can propagate the try_* error results with the try operator (?) if it doesn't want to do specific handling.
  3. The generated glue no longer needs to trap panics in Rust functions that return Result since there's already a mechanism for propagating failures. panic trapping can remain the default for other function signatures but this is consistent with Rust's pay-for-what-you-use paradigm.

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: engineGodot classes (nodes, resources, ...)c: registerRegister classes, functions and other symbols to GDScriptfeatureAdds functionality to the library

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions