Skip to content

Commit f5ddd55

Browse files
committed
Support Result<T, E> as return type in #[func]
Mechanisms: - FuncError + func_bail! macro for explicit error propagation - Error strategies (strats) for flexible error handling approaches - Automatic Result-to-Godot error conversion via error_to_godot trait - ptrcall disabling for fatal Result errors (uses varcall instead) - Call error tracking for introspection and debugging
1 parent 3c5d840 commit f5ddd55

15 files changed

Lines changed: 1025 additions & 18 deletions

File tree

godot-core/src/meta/error/call_error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,11 @@ impl CallError {
326326
err
327327
}
328328

329+
/// `#[func]` returning `Result<T, E>` which hits the `Err(E)` case *and* intends to fail the Godot call.
330+
pub(crate) fn failed_by_user_result(call_ctx: &CallContext, message: String) -> Self {
331+
Self::new(call_ctx, message, None)
332+
}
333+
329334
/// Whether this error was caused by a Rust panic (as opposed to a Godot or godot-rust error).
330335
///
331336
/// If true, the panic hook has already printed the error; callers can avoid printing it again.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
//! The [`ErrorToGodot`] trait for mapping `Result<T, E>` to Godot return types.
9+
//!
10+
//! Built-in strategies are in the [`strat`][super::strat] module.
11+
12+
use crate::meta::{GodotConvert, ToGodot};
13+
14+
/// Defines how a Rust error type maps to Godot when used in `Result<T, E>` return types of `#[func]` methods.
15+
///
16+
/// The associated type [`Mapped`][Self::Mapped] determines what GDScript sees as the function's return type. This type
17+
/// can depend on `T` -- the ok-value type of the `Result` -- because the trait is generic over `T`.
18+
///
19+
/// # Usage
20+
/// Users implement the required method [`result_to_godot`][Self::result_to_godot] and set the `Mapped` associated type.
21+
/// Use a blanket impl over `T` to cover all possible ok-types at once.
22+
///
23+
/// ## Failing function call + error message
24+
/// Use [`strat::Bail`][super::strat::Bail] to make the GDScript function call fail. It lets you use `?` with any `impl Error` type.
25+
/// The declared type on GDScript side is `T`.
26+
///
27+
/// ```no_run
28+
/// # use godot::prelude::*;
29+
/// use godot::meta::error::func_bail;
30+
/// # #[derive(GodotClass)] #[class(init, base=Node)] struct MyNode;
31+
/// #[godot_api]
32+
/// impl MyNode {
33+
/// #[func]
34+
/// fn load_config(&self, path: GString) -> Result<i64, strat::Bail> {
35+
/// let data = std::fs::read_to_string(path.to_string())?;
36+
/// let score = data.trim().parse::<i64>()?;
37+
///
38+
/// if score < 0 {
39+
/// func_bail!("invalid score in {path}");
40+
/// }
41+
///
42+
/// Ok(score)
43+
/// }
44+
/// }
45+
/// ```
46+
///
47+
/// ## `global::Error`
48+
/// Use the dedicated Godot enum [`global::Error`][crate::global::Error]. This only allows unit types for the `Ok` path.
49+
/// ```no_run
50+
/// # use godot::prelude::*;
51+
/// # use godot::global;
52+
/// # #[derive(GodotClass)] #[class(init, base=Node)] struct MyNode;
53+
/// #[godot_api]
54+
/// impl MyNode {
55+
/// #[func]
56+
/// fn do_fallible_work(&self) -> Result<(), global::Error> {
57+
/// // ...
58+
/// Ok(())
59+
/// }
60+
/// }
61+
/// ```
62+
///
63+
/// ## Custom implementation: typed `Array<T>` with 0 or 1 elements
64+
/// Since the trait is generic over `T`, custom implementations can require tighter bounds (such as
65+
/// [`Element`][crate::meta::Element]) and use a typed `Array<T>` as the via type. This example returns
66+
/// a 1-element array on success and an empty array on error. GDScript sees `Array[T]` as the return type.
67+
///
68+
/// ```no_run
69+
/// # use godot::prelude::*;
70+
/// use godot::builtin::Array;
71+
/// use godot::meta::error::ErrorToGodot;
72+
/// use godot::meta::{Element, GodotConvert, ref_to_arg, ToGodot};
73+
///
74+
/// struct MyError(String);
75+
///
76+
/// impl<T: Element> ErrorToGodot<T> for MyError {
77+
/// // GDScript sees `Array[T]` -- a typed array.
78+
/// type Mapped = Array<T>;
79+
///
80+
/// fn result_to_godot(result: Result<&T, &Self>) -> Result<Array<T>, String> {
81+
/// match result {
82+
/// Ok(val) => Ok(array![ref_to_arg(val)]),
83+
/// Err(_) => Ok(Array::new()),
84+
/// }
85+
/// }
86+
/// }
87+
/// ```
88+
///
89+
/// GDScript usage:
90+
/// ```gdscript
91+
/// var result: Array[int] = node.try_something()
92+
/// if result.is_empty():
93+
/// print("Operation failed")
94+
/// else:
95+
/// var value = result[0]
96+
/// ```
97+
///
98+
/// # Built-in strategies
99+
///
100+
/// See the [`strat`] module for all provided implementations and an overview table.
101+
///
102+
/// [`strat`]: crate::meta::error::strat
103+
///
104+
/// # Fatal errors and calling conventions
105+
///
106+
/// Fatal error types (those where [`result_to_godot`][Self::result_to_godot] returns `Err(msg)` on failure) rely on
107+
/// Godot's varcall mechanism to abort the calling GDScript function. However, Godot internally selects between two
108+
/// calling conventions based on static type information in the script, and this choice is **not controllable from GDExtension**:
109+
///
110+
/// - **Varcall** is used when the caller or arguments are untyped (e.g. `var obj: Variant`).
111+
/// Varcall provides an `r_error` output parameter, which gdext uses to signal call failure. The GDScript VM then
112+
/// aborts the calling function — similar to how a panic would behave.
113+
///
114+
/// - **Ptrcall** is used when both the object and all argument/return types are statically known
115+
/// (e.g. `var obj: MyClass`, `var x: int = obj.my_func()`).
116+
/// Ptrcall has **no** error output parameter, so fatal errors cannot abort the calling function.
117+
/// Instead, the error is logged as a Godot error and a default value is returned.
118+
///
119+
/// In practice, this means that a fatal `Result::Err` will abort the GDScript function in most cases (varcall),
120+
/// but will degrade to a logged error + default return value when GDScript has full type information (ptrcall).
121+
/// This is a limitation of Godot's GDExtension API, not something gdext can work around.
122+
pub trait ErrorToGodot<T: ToGodot>: Sized {
123+
/// The type to which `Result<T, Self>` is mapped on Godot side.
124+
type Mapped: GodotConvert;
125+
126+
/// Map a `Result<T, Self>` to a Godot representation or a fatal error message.
127+
///
128+
/// Return `Ok(via)` to pass the value back to GDScript (the call succeeds), or `Err(message)` to abort the
129+
/// GDScript function call and print an error. The latter is the "fatal" mode.
130+
///
131+
// TODO: replace Result<Via, String> with a dedicated ErrorHandling<Via> enum to communicate intent more clearly:
132+
// Return(Via) — pass this value to GDScript; the call succeeds.
133+
// Abort(String) — abort the GDScript call with this error message.
134+
fn result_to_godot(
135+
result: Result<&T, &Self>,
136+
) -> Result<<Self::Mapped as GodotConvert>::Via, String>;
137+
}

godot-core/src/meta/error/io_error.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ use crate::classes::FileAccess;
1212
use crate::global::Error as GodotError;
1313
use crate::obj::Gd;
1414

15-
/// Error that can occur while using `gdext` IO utilities.
15+
/// Error that can occur while using godot-rust I/O utilities.
16+
///
17+
/// Some APIs using this:
18+
/// - [`tools::try_load()`][crate::tools::try_load]
19+
/// - [`tools::try_save()`][crate::tools::try_save]
20+
/// - [`tools::GFile::try_from_unique()`][crate::tools::GFile::try_from_unique]
1621
#[derive(Debug)]
1722
pub struct IoError {
1823
data: ErrorData,

godot-core/src/meta/error/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@
1010
mod call_error;
1111
mod call_error_type;
1212
mod convert_error;
13+
mod error_to_godot;
1314
mod io_error;
1415
mod string_error;
1516

17+
pub mod strat;
18+
1619
pub use call_error::*;
1720
pub use call_error_type::*;
1821
pub use convert_error::*;
22+
pub use error_to_godot::*;
1923
pub use io_error::*;
2024
pub use string_error::*;
25+
26+
pub use crate::func_bail;

0 commit comments

Comments
 (0)