1010use std:: error:: Error ;
1111use std:: fmt;
1212
13- use crate :: builtin:: CowStr ;
1413use crate :: meta:: ToGodot ;
1514use crate :: meta:: error:: ErrorToGodot ;
1615
@@ -22,16 +21,16 @@ use crate::meta::error::ErrorToGodot;
2221/// Its main advantage over other [`ErrorToGodot`] types is that it preserves type safety: GDScript's static analysis sees the return type `T`.
2322/// This is the (more common) happy path, returned from Rust as `Ok(T)`.
2423///
25- /// `FuncError` enables automatic conversions from other errors via `?` operator. This means you can mix different error types within the same
26- /// function body -- each one just propagates via `?` and its message is forwarded to Godot. `String` and `&str` also convert, for cases where
27- /// you want to fail with a direct message.
24+ /// `FuncError` enables automatic conversions from other errors via `?` operator. This means you can mix different error types within the same
25+ /// function body -- each one propagates via `?` and its message is forwarded to Godot. `String` and `&str` also convert, for cases where you
26+ /// want to fail with a direct message.
2827///
29- /// Use [`FuncResult<T>`] as a convenient alias for `Result<T, FuncError>`.
28+ /// Use [`FuncResult<T>`] as an alias for `Result<T, FuncError>`, and the [`func_bail!`] macro for early returns with an error message .
3029///
3130/// # Example
3231/// ```no_run
3332/// # use godot::prelude::*;
34- /// use godot::meta::error::FuncResult;
33+ /// use godot::meta::error::{ FuncResult, func_bail} ;
3534/// # #[derive(GodotClass)] #[class(init, base=Node)] struct PlayerData;
3635///
3736/// #[godot_api]
@@ -44,9 +43,8 @@ use crate::meta::error::ErrorToGodot;
4443/// let text = std::fs::read_to_string(save_path.to_string())?;
4544/// let score = text.trim().parse::<i64>()?;
4645///
47- /// // Strings are also supported via From trait.
4846/// if score < 0 {
49- /// return Err(FuncError::from_message( "Corrupted save file: high score is negative.") );
47+ /// func_bail!( "Corrupted save file {save_path} : high score is negative.");
5048/// }
5149///
5250/// Ok(score)
@@ -80,18 +78,6 @@ impl FuncError {
8078 }
8179 }
8280
83- /// Create a `FuncError` from a plain message string.
84- ///
85- /// Accepts `&'static str` (no allocation) or `String` / `format!(...)` output. For non-static string slices, pass `.to_string()` or
86- /// use `format!()`. Useful when you want to fail a `#[func]` call with a plain message and have no underlying error type to propagate.
87- pub fn from_message ( msg : impl Into < CowStr > ) -> Self {
88- Self {
89- inner : Box :: new ( MessageError {
90- message : msg. into ( ) ,
91- } ) ,
92- }
93- }
94-
9581 /// Attempt to downcast the inner error to a concrete type.
9682 pub fn downcast_ref < E : Error + ' static > ( & self ) -> Option < & E > {
9783 self . inner . downcast_ref :: < E > ( )
@@ -120,7 +106,7 @@ impl fmt::Debug for FuncError {
120106/// |---|---|
121107/// | Any `E: Error + Send + Sync + 'static` | Boxed directly; covers `std::io::Error`, `ParseIntError`, and any custom error type |
122108/// | `Box<dyn Error + Send + Sync + 'static>` | Used as-is |
123- /// | `String` | Wrapped in a message-only error (`StringError` in stdlib) |
109+ /// | `String` | Wrapped in a message-only error |
124110/// | `&str` (any lifetime) | Copied to `String`, then wrapped — the lifetime is not propagated |
125111impl < E : Into < Box < dyn Error + Send + Sync + ' static > > > From < E > for FuncError {
126112 fn from ( err : E ) -> Self {
@@ -152,24 +138,27 @@ impl<T: ToGodot<Via: Clone>> ErrorToGodot<T> for FuncError {
152138pub type FuncResult < T > = Result < T , FuncError > ;
153139
154140// ----------------------------------------------------------------------------------------------------------------------------------------------
155- // Internal helper: wraps a plain message string as an error
141+ // Macro for immediately exiting function.
156142
157- #[ derive( Debug ) ]
158- struct MessageError {
159- message : CowStr ,
160- }
161-
162- impl fmt:: Display for MessageError {
163- fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
164- f. write_str ( & self . message )
165- }
143+ /// Return early from a `#[func]`, creating a [`FuncError`] from an error message.
144+ ///
145+ /// Same principle as [`eyre::bail!`](https://docs.rs/eyre/latest/eyre/macro.bail.html),
146+ /// [`miette::bail!`](https://docs.rs/miette/latest/miette/macro.bail.html), and
147+ /// [`anyhow::bail!`](https://docs.rs/anyhow/latest/anyhow/macro.bail.html).
148+ ///
149+ /// This macro expands to `return Err(FuncError::from(...))`.
150+ /// Accepts a string literal or a `format!`-style format string with arguments. See [`FuncError`] for usage example.
151+ #[ macro_export]
152+ macro_rules! func_bail {
153+ ( $( $arg: tt) * ) => {
154+ return :: std:: result:: Result :: Err ( $crate:: meta:: error:: FuncError :: from(
155+ :: std:: format!( $( $arg) * )
156+ ) )
157+ } ;
166158}
167159
168- impl Error for MessageError { }
169-
170160// ----------------------------------------------------------------------------------------------------------------------------------------------
171161
172- // Verify the From impls documented on the blanket From impl.
173162#[ cfg( test) ]
174163mod tests {
175164 use super :: * ;
@@ -179,7 +168,7 @@ mod tests {
179168
180169 #[ test]
181170 fn from_concrete_error ( ) {
182- // Any E: Error + Send + Sync + 'static -- here using ParseIntError.
171+ // Accepts any E: Error + Send + Sync + 'static -- here ParseIntError.
183172 let err: Result < i32 , _ > = "x" . parse ( ) ;
184173 assert_func_error ( err. unwrap_err ( ) . into ( ) ) ;
185174 }
@@ -221,9 +210,21 @@ mod tests {
221210 }
222211
223212 #[ test]
224- fn from_message_constructor ( ) {
225- let e = FuncError :: from_message ( format ! ( "value was {}" , 42 ) ) ;
226- assert_eq ! ( e. to_string( ) , "value was 42" ) ;
213+ fn macro_literal_returns_early ( ) {
214+ fn run ( ) -> FuncResult < i32 > {
215+ func_bail ! ( "literal message" ) ;
216+ }
217+ let err = run ( ) . unwrap_err ( ) ;
218+ assert_eq ! ( err. to_string( ) , "literal message" ) ;
219+ }
220+
221+ #[ test]
222+ fn macro_format_returns_early ( ) {
223+ fn run ( x : i32 ) -> FuncResult < i32 > {
224+ func_bail ! ( "value was {x}" ) ;
225+ }
226+ let err = run ( 42 ) . unwrap_err ( ) ;
227+ assert_eq ! ( err. to_string( ) , "value was 42" ) ;
227228 }
228229
229230 #[ test]
@@ -235,7 +236,7 @@ mod tests {
235236
236237 #[ test]
237238 fn downcast_ref_wrong_type_returns_none ( ) {
238- let e: FuncError = "not an io error " . into ( ) ;
239+ let e: FuncError = "not an io::Error " . into ( ) ;
239240 assert ! ( e. downcast_ref:: <std:: io:: Error >( ) . is_none( ) ) ;
240241 }
241242}
0 commit comments