|
| 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 | +} |
0 commit comments