|
| 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 | +/// For usage examples of built-in strategies, see each type in the [`strat`][crate::meta::error::strat] module. |
| 24 | +/// |
| 25 | +/// ## Custom implementation: typed `Array<T>` with 0 or 1 elements |
| 26 | +/// Since the trait is generic over `T`, custom implementations can require tighter bounds (such as |
| 27 | +/// [`Element`][crate::meta::Element]) and use a typed `Array<T>` as the via type. This example returns |
| 28 | +/// a 1-element array on success and an empty array on error. GDScript sees `Array[T]` as the return type. |
| 29 | +/// |
| 30 | +/// ```no_run |
| 31 | +/// # use godot::prelude::*; |
| 32 | +/// use godot::builtin::Array; |
| 33 | +/// use godot::meta::error::ErrorToGodot; |
| 34 | +/// use godot::meta::{Element, GodotConvert, ref_to_arg, ToGodot}; |
| 35 | +/// |
| 36 | +/// struct MyError(String); |
| 37 | +/// |
| 38 | +/// impl<T: Element> ErrorToGodot<T> for MyError { |
| 39 | +/// // GDScript sees `Array[T]` -- a typed array. |
| 40 | +/// type Mapped = Array<T>; |
| 41 | +/// |
| 42 | +/// fn result_to_godot(result: Result<&T, &Self>) -> Result<Array<T>, String> { |
| 43 | +/// match result { |
| 44 | +/// Ok(val) => Ok(array![ref_to_arg(val)]), |
| 45 | +/// Err(_) => Ok(Array::new()), |
| 46 | +/// } |
| 47 | +/// } |
| 48 | +/// } |
| 49 | +/// ``` |
| 50 | +/// |
| 51 | +/// GDScript usage: |
| 52 | +/// ```gdscript |
| 53 | +/// var result: Array[int] = node.try_something() |
| 54 | +/// if result.is_empty(): |
| 55 | +/// print("Operation failed") |
| 56 | +/// else: |
| 57 | +/// var value = result[0] |
| 58 | +/// ``` |
| 59 | +/// |
| 60 | +/// # Built-in strategies |
| 61 | +/// |
| 62 | +/// See the [`strat`] module for all provided implementations and an overview table. |
| 63 | +/// |
| 64 | +/// [`strat`]: crate::meta::error::strat |
| 65 | +/// |
| 66 | +/// # Fatal errors and calling conventions |
| 67 | +/// |
| 68 | +/// Fatal error types (those where [`result_to_godot`][Self::result_to_godot] returns `Err(msg)` on failure) rely on |
| 69 | +/// Godot's varcall mechanism to abort the calling GDScript function. However, Godot internally selects between two |
| 70 | +/// calling conventions based on static type information in the script, and this choice is **not controllable from GDExtension**: |
| 71 | +/// |
| 72 | +/// - **Varcall** is used when the caller or arguments are untyped (e.g. `var obj: Variant`). |
| 73 | +/// Varcall provides an `r_error` output parameter, which gdext uses to signal call failure. The GDScript VM then |
| 74 | +/// aborts the calling function — similar to how a panic would behave. |
| 75 | +/// |
| 76 | +/// - **Ptrcall** is used when both the object and all argument/return types are statically known |
| 77 | +/// (e.g. `var obj: MyClass`, `var x: int = obj.my_func()`). |
| 78 | +/// Ptrcall has **no** error output parameter, so fatal errors cannot abort the calling function. |
| 79 | +/// Instead, the error is logged as a Godot error and a default value is returned. |
| 80 | +/// |
| 81 | +/// In practice, this means that a fatal `Result::Err` will abort the GDScript function in most cases (varcall), |
| 82 | +/// but will degrade to a logged error + default return value when GDScript has full type information (ptrcall). |
| 83 | +/// This is a limitation of Godot's GDExtension API, not something gdext can work around. |
| 84 | +pub trait ErrorToGodot<T: ToGodot>: Sized { |
| 85 | + /// The type to which `Result<T, Self>` is mapped on Godot side. |
| 86 | + type Mapped: GodotConvert; |
| 87 | + |
| 88 | + /// Map a `Result<T, Self>` to a Godot representation or a fatal error message. |
| 89 | + /// |
| 90 | + /// Return `Ok(via)` to pass the value back to GDScript (the call succeeds), or `Err(message)` to abort the |
| 91 | + /// GDScript function call and print an error. The latter is the "fatal" mode. |
| 92 | + /// |
| 93 | + // TODO: replace Result<Via, String> with a dedicated ErrorHandling<Via> enum to communicate intent more clearly: |
| 94 | + // Return(Via) — pass this value to GDScript; the call succeeds. |
| 95 | + // Abort(String) — abort the GDScript call with this error message. |
| 96 | + fn result_to_godot( |
| 97 | + result: Result<&T, &Self>, |
| 98 | + ) -> Result<<Self::Mapped as GodotConvert>::Via, String>; |
| 99 | +} |
0 commit comments