From 27f91dfd1b8867867b1dea20e15dbbd38275c487 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 18 Apr 2026 16:01:35 +0100 Subject: [PATCH] Accept any error in `Function::wrap/wrap_mut/wrap_async` Previously wrapped functions were required to return `mlua::Result`. Now it's possible to wrap functions returning any errors as long as they implement `std::error::Error`. Existing code remains compatible with `mlua::Result` as this type is not converted to an external error. --- src/error.rs | 6 +++++- src/function.rs | 36 ++++++++++++++++++++---------------- tests/async.rs | 2 +- tests/error.rs | 13 +++++++++++++ tests/function.rs | 38 +++++++++++++++++++++++++++++++++++--- tests/types.rs | 17 ++++++++++------- 6 files changed, 84 insertions(+), 28 deletions(-) diff --git a/src/error.rs b/src/error.rs index 42aba31..a7ef2a1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -345,7 +345,11 @@ impl Error { /// Wraps an external error object. #[inline] pub fn external>>(err: T) -> Self { - Error::ExternalError(err.into().into()) + let boxed = err.into(); + match boxed.downcast::() { + Ok(err) => *err, + Err(boxed) => Error::ExternalError(boxed.into()), + } } /// Attempts to downcast the external error object to a concrete type by reference. diff --git a/src/function.rs b/src/function.rs index 88be94f..894e8e8 100644 --- a/src/function.rs +++ b/src/function.rs @@ -81,9 +81,10 @@ use std::cell::RefCell; use std::os::raw::{c_int, c_void}; +use std::result::Result as StdResult; use std::{mem, ptr, slice}; -use crate::error::{Error, Result}; +use crate::error::{Error, ExternalError, ExternalResult, Result}; use crate::state::Lua; use crate::table::Table; use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti}; @@ -635,30 +636,32 @@ impl Function { /// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] /// trait. #[inline] - pub fn wrap(func: F) -> impl IntoLua + pub fn wrap(func: F) -> impl IntoLua where - F: LuaNativeFn> + MaybeSend + 'static, + F: LuaNativeFn> + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, + E: ExternalError, { WrappedFunction(Box::new(move |lua, nargs| unsafe { let args = A::from_stack_args(nargs, 1, None, lua)?; - func.call(args)?.push_into_stack_multi(lua) + func.call(args).into_lua_err()?.push_into_stack_multi(lua) })) } /// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait. - pub fn wrap_mut(func: F) -> impl IntoLua + pub fn wrap_mut(func: F) -> impl IntoLua where - F: LuaNativeFnMut> + MaybeSend + 'static, + F: LuaNativeFnMut> + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, + E: ExternalError, { let func = RefCell::new(func); WrappedFunction(Box::new(move |lua, nargs| unsafe { let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; let args = A::from_stack_args(nargs, 1, None, lua)?; - func.call(args)?.push_into_stack_multi(lua) + func.call(args).into_lua_err()?.push_into_stack_multi(lua) })) } @@ -671,6 +674,7 @@ impl Function { pub fn wrap_raw(func: F) -> impl IntoLua where F: LuaNativeFn + MaybeSend + 'static, + F::Output: IntoLuaMulti, A: FromLuaMulti, { WrappedFunction(Box::new(move |lua, nargs| unsafe { @@ -687,6 +691,7 @@ impl Function { pub fn wrap_raw_mut(func: F) -> impl IntoLua where F: LuaNativeFnMut + MaybeSend + 'static, + F::Output: IntoLuaMulti, A: FromLuaMulti, { let func = RefCell::new(func); @@ -701,11 +706,12 @@ impl Function { /// trait. #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn wrap_async(func: F) -> impl IntoLua + pub fn wrap_async(func: F) -> impl IntoLua where - F: LuaNativeAsyncFn> + MaybeSend + 'static, + F: LuaNativeAsyncFn> + MaybeSend + 'static, A: FromLuaMulti, R: IntoLuaMulti, + E: ExternalError, { WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe { let args = match A::from_stack_args(nargs, 1, None, rawlua) { @@ -714,7 +720,7 @@ impl Function { }; let lua = rawlua.lua(); let fut = func.call(args); - Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) + Box::pin(async move { fut.await.into_lua_err()?.push_into_stack_multi(lua.raw_lua()) }) })) } @@ -728,6 +734,7 @@ impl Function { pub fn wrap_raw_async(func: F) -> impl IntoLua where F: LuaNativeAsyncFn + MaybeSend + 'static, + F::Output: IntoLuaMulti, A: FromLuaMulti, { WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe { @@ -789,14 +796,14 @@ impl Future for AsyncCallFuture { /// A trait for types that can be used as Lua functions. pub trait LuaNativeFn { - type Output: IntoLuaMulti; + type Output; fn call(&self, args: A) -> Self::Output; } /// A trait for types with mutable state that can be used as Lua functions. pub trait LuaNativeFnMut { - type Output: IntoLuaMulti; + type Output; fn call(&mut self, args: A) -> Self::Output; } @@ -804,7 +811,7 @@ pub trait LuaNativeFnMut { /// A trait for types that returns a future and can be used as Lua functions. #[cfg(feature = "async")] pub trait LuaNativeAsyncFn { - type Output: IntoLuaMulti; + type Output; fn call(&self, args: A) -> impl Future + MaybeSend + 'static; } @@ -815,7 +822,6 @@ macro_rules! impl_lua_native_fn { where FN: Fn($($A,)*) -> R + MaybeSend + 'static, ($($A,)*): FromLuaMulti, - R: IntoLuaMulti, { type Output = R; @@ -830,7 +836,6 @@ macro_rules! impl_lua_native_fn { where FN: FnMut($($A,)*) -> R + MaybeSend + 'static, ($($A,)*): FromLuaMulti, - R: IntoLuaMulti, { type Output = R; @@ -847,7 +852,6 @@ macro_rules! impl_lua_native_fn { FN: Fn($($A,)*) -> Fut + MaybeSend + 'static, ($($A,)*): FromLuaMulti, Fut: Future + MaybeSend + 'static, - R: IntoLuaMulti, { type Output = R; diff --git a/tests/async.rs b/tests/async.rs index 16ddd9e..55e4159 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -41,7 +41,7 @@ async fn test_async_function_wrap() -> Result<()> { let f = Function::wrap_async(|s: String| async move { tokio::task::yield_now().await; - Ok(s) + Ok::<_, Error>(s) }); lua.globals().set("f", f)?; let res: String = lua.load(r#"f("hello")"#).eval_async().await?; diff --git a/tests/error.rs b/tests/error.rs index 09bdd5b..6f70f77 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -77,6 +77,19 @@ fn test_error_chain() -> Result<()> { Ok(()) } +#[test] +fn test_external_error() { + // `Error::external` should preserve `mlua::Error` + let runtime_err = Error::runtime("test error"); + let converted = Error::external(runtime_err); + assert!(matches!(converted, Error::RuntimeError(ref msg) if msg == "test error")); + + // Other errors should become `ExternalError` + let converted = Error::external(io::Error::other("other error")); + assert!(matches!(converted, Error::ExternalError(_))); + assert!(converted.downcast_ref::().is_some()); +} + #[cfg(feature = "anyhow")] #[test] fn test_error_anyhow() -> Result<()> { diff --git a/tests/function.rs b/tests/function.rs index d01e4fa..8cb75d4 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -1,3 +1,6 @@ +use std::fmt; +use std::result::Result as StdResult; + use mlua::{Error, Function, Lua, LuaString, Result, Table, Variadic}; #[test] @@ -343,7 +346,7 @@ fn test_function_deep_clone() -> Result<()> { fn test_function_wrap() -> Result<()> { let lua = Lua::new(); - let f = Function::wrap(|s: LuaString, n| Ok(s.to_str().unwrap().repeat(n))); + let f = Function::wrap(|s: LuaString, n| Ok::<_, Error>(s.to_str().unwrap().repeat(n))); lua.globals().set("f", f)?; lua.load(r#"assert(f("hello", 2) == "hellohello")"#) .exec() @@ -361,11 +364,40 @@ fn test_function_wrap() -> Result<()> { .exec() .unwrap(); + // Return external error + #[derive(Debug)] + struct MyError(String); + impl fmt::Display for MyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MyError: {}", self.0) + } + } + impl std::error::Error for MyError {} + + let fext = Function::wrap(|s: String| -> StdResult { + if s == "bad" { + return Err(MyError("bad input".into())); + } + Ok(format!("ok: {s}")) + }); + lua.globals().set("fext", fext)?; + lua.load(r#"assert(fext("hello") == "ok: hello")"#) + .exec() + .unwrap(); + lua.load( + r#" + local ok, err = pcall(fext, "bad") + assert(not ok and tostring(err):find("MyError: bad input")) + "#, + ) + .exec() + .unwrap(); + // Mutable callback let mut i = 0; let fmut = Function::wrap_mut(move || { i += 1; - Ok(i) + Ok::<_, Error>(i) }); lua.globals().set("fmut", fmut)?; lua.load(r#"fmut(); fmut(); assert(fmut() == 3)"#).exec().unwrap(); @@ -385,7 +417,7 @@ fn test_function_wrap() -> Result<()> { // Check recursive mut callback error let fmut = Function::wrap_mut(|f: Function| match f.call::<()>(&f) { Err(Error::CallbackError { cause, .. }) => match cause.as_ref() { - Error::RecursiveMutCallback { .. } => Ok(()), + Error::RecursiveMutCallback { .. } => Ok::<_, Error>(()), other => panic!("incorrect result: {other:?}"), }, other => panic!("incorrect result: {other:?}"), diff --git a/tests/types.rs b/tests/types.rs index 0cd775b..6475acd 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -1,6 +1,6 @@ use std::os::raw::c_void; -use mlua::{Function, LightUserData, Lua, LuaString, Number, Result, Thread}; +use mlua::{Error, Function, LightUserData, Lua, LuaString, Number, Result, Thread}; #[test] fn test_lightuserdata() -> Result<()> { @@ -30,7 +30,7 @@ fn test_boolean_type_metatable() -> Result<()> { let lua = Lua::new(); let mt = lua.create_table()?; - mt.set("__add", Function::wrap(|a, b| Ok(a || b)))?; + mt.set("__add", Function::wrap(|a, b| Ok::<_, mlua::Error>(a || b)))?; assert_eq!(lua.type_metatable::(), None); lua.set_type_metatable::(Some(mt.clone())); assert_eq!(lua.type_metatable::().unwrap(), mt); @@ -51,7 +51,7 @@ fn test_lightuserdata_type_metatable() -> Result<()> { mt.set( "__add", Function::wrap(|a: LightUserData, b: LightUserData| { - Ok(LightUserData((a.0 as usize + b.0 as usize) as *mut c_void)) + Ok::<_, Error>(LightUserData((a.0 as usize + b.0 as usize) as *mut c_void)) }), )?; lua.set_type_metatable::(Some(mt.clone())); @@ -79,7 +79,10 @@ fn test_number_type_metatable() -> Result<()> { let lua = Lua::new(); let mt = lua.create_table()?; - mt.set("__call", Function::wrap(|n1: f64, n2: f64| Ok(n1 * n2)))?; + mt.set( + "__call", + Function::wrap(|n1: f64, n2: f64| Ok::<_, Error>(n1 * n2)), + )?; lua.set_type_metatable::(Some(mt.clone())); assert_eq!(lua.type_metatable::().unwrap(), mt); @@ -96,7 +99,7 @@ fn test_string_type_metatable() -> Result<()> { let mt = lua.create_table()?; mt.set( "__add", - Function::wrap(|a: String, b: String| Ok(format!("{a}{b}"))), + Function::wrap(|a: String, b: String| Ok::<_, Error>(format!("{a}{b}"))), )?; lua.set_type_metatable::(Some(mt.clone())); assert_eq!(lua.type_metatable::().unwrap(), mt); @@ -113,7 +116,7 @@ fn test_function_type_metatable() -> Result<()> { let mt = lua.create_table()?; mt.set( "__index", - Function::wrap(|_: Function, key: String| Ok(format!("function.{key}"))), + Function::wrap(|_: Function, key: String| Ok::<_, Error>(format!("function.{key}"))), )?; lua.set_type_metatable::(Some(mt.clone())); assert_eq!(lua.type_metatable::(), Some(mt)); @@ -132,7 +135,7 @@ fn test_thread_type_metatable() -> Result<()> { let mt = lua.create_table()?; mt.set( "__index", - Function::wrap(|_: Thread, key: String| Ok(format!("thread.{key}"))), + Function::wrap(|_: Thread, key: String| Ok::<_, Error>(format!("thread.{key}"))), )?; lua.set_type_metatable::(Some(mt.clone())); assert_eq!(lua.type_metatable::(), Some(mt));