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.
This commit is contained in:
Alex Orlenko
2026-04-18 16:01:35 +01:00
parent 75ff11f795
commit 27f91dfd1b
6 changed files with 84 additions and 28 deletions
+5 -1
View File
@@ -345,7 +345,11 @@ impl Error {
/// Wraps an external error object.
#[inline]
pub fn external<T: Into<Box<DynStdError>>>(err: T) -> Self {
Error::ExternalError(err.into().into())
let boxed = err.into();
match boxed.downcast::<Self>() {
Ok(err) => *err,
Err(boxed) => Error::ExternalError(boxed.into()),
}
}
/// Attempts to downcast the external error object to a concrete type by reference.
+20 -16
View File
@@ -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<F, A, R>(func: F) -> impl IntoLua
pub fn wrap<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeFn<A, Output = StdResult<R, E>> + 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<F, A, R>(func: F) -> impl IntoLua
pub fn wrap_mut<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeFnMut<A, Output = StdResult<R, E>> + 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<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A> + 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<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A> + 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<F, A, R>(func: F) -> impl IntoLua
pub fn wrap_async<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeAsyncFn<A, Output = StdResult<R, E>> + 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<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
@@ -789,14 +796,14 @@ impl<R: FromLuaMulti> Future for AsyncCallFuture<R> {
/// A trait for types that can be used as Lua functions.
pub trait LuaNativeFn<A: FromLuaMulti> {
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<A: FromLuaMulti> {
type Output: IntoLuaMulti;
type Output;
fn call(&mut self, args: A) -> Self::Output;
}
@@ -804,7 +811,7 @@ pub trait LuaNativeFnMut<A: FromLuaMulti> {
/// A trait for types that returns a future and can be used as Lua functions.
#[cfg(feature = "async")]
pub trait LuaNativeAsyncFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
type Output;
fn call(&self, args: A) -> impl Future<Output = Self::Output> + 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<Output = R> + MaybeSend + 'static,
R: IntoLuaMulti,
{
type Output = R;
+1 -1
View File
@@ -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?;
+13
View File
@@ -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::<io::Error>().is_some());
}
#[cfg(feature = "anyhow")]
#[test]
fn test_error_anyhow() -> Result<()> {
+35 -3
View File
@@ -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<String, MyError> {
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:?}"),
+10 -7
View File
@@ -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::<bool>(), None);
lua.set_type_metatable::<bool>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<bool>().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::<LightUserData>(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::<Number>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<Number>().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::<LuaString>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<LuaString>().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::<Function>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<Function>(), 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::<Thread>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<Thread>(), Some(mt));