Scope support (including async)
This commit is contained in:
parent
2bd5c2f6ca
commit
d366ce0dd4
|
@ -54,7 +54,7 @@ luajit-src = { version = "210.0.0", optional = true }
|
|||
[dev-dependencies]
|
||||
rustyline = "6.0"
|
||||
criterion = "0.3"
|
||||
trybuild = "1.0"
|
||||
trybuild = "1.0.27"
|
||||
futures = "0.3.4"
|
||||
hyper = "0.13"
|
||||
tokio = { version = "0.2.18", features = ["full"] }
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::lua::Lua;
|
|||
use crate::string::String;
|
||||
use crate::table::Table;
|
||||
use crate::thread::Thread;
|
||||
use crate::types::{LightUserData, Number, MaybeSend};
|
||||
use crate::types::{LightUserData, MaybeSend, Number};
|
||||
use crate::userdata::{AnyUserData, UserData};
|
||||
use crate::value::{FromLua, Nil, ToLua, Value};
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ pub use self::lua::{
|
|||
pub use self::lua::{lua_KContext, lua_KFunction};
|
||||
|
||||
#[cfg(any(feature = "lua51", feature = "luajit"))]
|
||||
pub use self::lua::lua_setfenv;
|
||||
pub use self::lua::{lua_getfenv, lua_setfenv};
|
||||
|
||||
// C API functions
|
||||
pub use self::lua::{
|
||||
|
|
|
@ -59,6 +59,7 @@ mod ffi;
|
|||
mod function;
|
||||
mod lua;
|
||||
mod multi;
|
||||
mod scope;
|
||||
mod stdlib;
|
||||
mod string;
|
||||
mod table;
|
||||
|
@ -74,6 +75,7 @@ pub use crate::error::{Error, ExternalError, ExternalResult, Result};
|
|||
pub use crate::function::Function;
|
||||
pub use crate::lua::{Chunk, Lua};
|
||||
pub use crate::multi::Variadic;
|
||||
pub use crate::scope::Scope;
|
||||
pub use crate::stdlib::StdLib;
|
||||
pub use crate::string::String;
|
||||
pub use crate::table::{Table, TableExt, TablePairs, TableSequence};
|
||||
|
|
50
src/lua.rs
50
src/lua.rs
|
@ -10,11 +10,12 @@ use std::{mem, ptr, str};
|
|||
use crate::error::{Error, Result};
|
||||
use crate::ffi;
|
||||
use crate::function::Function;
|
||||
use crate::scope::Scope;
|
||||
use crate::stdlib::StdLib;
|
||||
use crate::string::String;
|
||||
use crate::table::Table;
|
||||
use crate::thread::Thread;
|
||||
use crate::types::{Callback, Integer, LightUserData, LuaRef, Number, RegistryKey, MaybeSend};
|
||||
use crate::types::{Callback, Integer, LightUserData, LuaRef, MaybeSend, Number, RegistryKey};
|
||||
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
|
||||
use crate::util::{
|
||||
assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state,
|
||||
|
@ -611,6 +612,53 @@ impl Lua {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calls the given function with a `Scope` parameter, giving the function the ability to create
|
||||
/// userdata and callbacks from rust types that are !Send or non-'static.
|
||||
///
|
||||
/// The lifetime of any function or userdata created through `Scope` lasts only until the
|
||||
/// completion of this method call, on completion all such created values are automatically
|
||||
/// dropped and Lua references to them are invalidated. If a script accesses a value created
|
||||
/// through `Scope` outside of this method, a Lua error will result. Since we can ensure the
|
||||
/// lifetime of values created through `Scope`, and we know that `Lua` cannot be sent to another
|
||||
/// thread while `Scope` is live, it is safe to allow !Send datatypes and whose lifetimes only
|
||||
/// outlive the scope lifetime.
|
||||
///
|
||||
/// Inside the scope callback, all handles created through Scope will share the same unique 'lua
|
||||
/// lifetime of the parent `Lua`. This allows scoped and non-scoped values to be mixed in
|
||||
/// API calls, which is very useful (e.g. passing a scoped userdata to a non-scoped function).
|
||||
/// However, this also enables handles to scoped values to be trivially leaked from the given
|
||||
/// callback. This is not dangerous, though! After the callback returns, all scoped values are
|
||||
/// invalidated, which means that though references may exist, the Rust types backing them have
|
||||
/// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It
|
||||
/// would be impossible to prevent handles to scoped values from escaping anyway, since you
|
||||
/// would always be able to smuggle them through Lua state.
|
||||
pub fn scope<'lua, 'scope, R, F>(&'lua self, f: F) -> Result<R>
|
||||
where
|
||||
'lua: 'scope,
|
||||
R: 'static,
|
||||
F: FnOnce(&Scope<'lua, 'scope>) -> Result<R>,
|
||||
{
|
||||
f(&Scope::new(self))
|
||||
}
|
||||
|
||||
/// An asynchronous version of [`scope`] that allows to create scoped async functions and
|
||||
/// execute them.
|
||||
///
|
||||
/// [`scope`]: #method.scope
|
||||
#[cfg(feature = "async")]
|
||||
pub fn async_scope<'lua, 'scope, R, F, FR>(
|
||||
&'lua self,
|
||||
f: F,
|
||||
) -> LocalBoxFuture<'scope, Result<R>>
|
||||
where
|
||||
'lua: 'scope,
|
||||
R: 'static,
|
||||
F: FnOnce(Scope<'lua, 'scope>) -> FR,
|
||||
FR: 'scope + Future<Output = Result<R>>,
|
||||
{
|
||||
Box::pin(f(Scope::new(self)))
|
||||
}
|
||||
|
||||
/// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal
|
||||
/// behavior.
|
||||
///
|
||||
|
|
|
@ -0,0 +1,601 @@
|
|||
use std::any::Any;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ffi;
|
||||
use crate::function::Function;
|
||||
use crate::lua::Lua;
|
||||
use crate::types::{Callback, LuaRef, MaybeSend};
|
||||
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
|
||||
use crate::util::{
|
||||
assert_stack, init_userdata_metatable, protect_lua_closure, push_string, push_userdata,
|
||||
take_userdata, StackGuard,
|
||||
};
|
||||
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti, Value};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
use {
|
||||
crate::types::AsyncCallback,
|
||||
futures_core::future::Future,
|
||||
futures_util::future::{self, TryFutureExt},
|
||||
std::os::raw::c_char,
|
||||
};
|
||||
|
||||
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
|
||||
/// callbacks that are not required to be Send or 'static.
|
||||
///
|
||||
/// See [`Lua::scope`] for more details.
|
||||
///
|
||||
/// [`Lua::scope`]: struct.Lua.html#method.scope
|
||||
pub struct Scope<'lua, 'scope> {
|
||||
lua: &'lua Lua,
|
||||
destructors: RefCell<Vec<(LuaRef<'lua>, fn(LuaRef<'lua>) -> Vec<Box<dyn Any>>)>>,
|
||||
_scope_invariant: PhantomData<Cell<&'scope ()>>,
|
||||
}
|
||||
|
||||
impl<'lua, 'scope> Scope<'lua, 'scope> {
|
||||
pub(crate) fn new(lua: &'lua Lua) -> Scope<'lua, 'scope> {
|
||||
Scope {
|
||||
lua,
|
||||
destructors: RefCell::new(Vec::new()),
|
||||
_scope_invariant: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a Rust function or closure, creating a callable Lua function handle to it.
|
||||
///
|
||||
/// This is a version of [`Lua::create_function`] that creates a callback which expires on
|
||||
/// scope drop. See [`Lua::scope`] for more details.
|
||||
///
|
||||
/// [`Lua::create_function`]: struct.Lua.html#method.create_function
|
||||
/// [`Lua::scope`]: struct.Lua.html#method.scope
|
||||
pub fn create_function<'callback, A, R, F>(&'callback self, func: F) -> Result<Function<'lua>>
|
||||
where
|
||||
A: FromLuaMulti<'callback>,
|
||||
R: ToLuaMulti<'callback>,
|
||||
F: 'scope + Fn(&'callback Lua, A) -> Result<R>,
|
||||
{
|
||||
// Safe, because 'scope must outlive 'callback (due to Self containing 'scope), however the
|
||||
// callback itself must be 'scope lifetime, so the function should not be able to capture
|
||||
// anything of 'callback lifetime. 'scope can't be shortened due to being invariant, and
|
||||
// the 'callback lifetime here can't be enlarged due to coming from a universal
|
||||
// quantification in Lua::scope.
|
||||
//
|
||||
// I hope I got this explanation right, but in any case this is tested with compiletest_rs
|
||||
// to make sure callbacks can't capture handles with lifetime outside the scope, inside the
|
||||
// scope, and owned inside the callback itself.
|
||||
unsafe {
|
||||
self.create_callback(Box::new(move |lua, args| {
|
||||
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a Rust mutable closure, creating a callable Lua function handle to it.
|
||||
///
|
||||
/// This is a version of [`Lua::create_function_mut`] that creates a callback which expires
|
||||
/// on scope drop. See [`Lua::scope`] and [`Scope::create_function`] for more details.
|
||||
///
|
||||
/// [`Lua::create_function_mut`]: struct.Lua.html#method.create_function_mut
|
||||
/// [`Lua::scope`]: struct.Lua.html#method.scope
|
||||
/// [`Scope::create_function`]: #method.create_function
|
||||
pub fn create_function_mut<'callback, A, R, F>(
|
||||
&'callback self,
|
||||
func: F,
|
||||
) -> Result<Function<'lua>>
|
||||
where
|
||||
A: FromLuaMulti<'callback>,
|
||||
R: ToLuaMulti<'callback>,
|
||||
F: 'scope + FnMut(&'callback Lua, A) -> Result<R>,
|
||||
{
|
||||
let func = RefCell::new(func);
|
||||
self.create_function(move |lua, args| {
|
||||
(&mut *func
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| Error::RecursiveMutCallback)?)(lua, args)
|
||||
})
|
||||
}
|
||||
|
||||
/// Wraps a Rust async function or closure, creating a callable Lua function handle to it.
|
||||
///
|
||||
/// This is a version of [`Lua::create_async_function`] that creates a callback which expires on
|
||||
/// scope drop. See [`Lua::scope`] and [`Lua::async_scope`] for more details.
|
||||
///
|
||||
/// [`Lua::create_async_function`]: struct.Lua.html#method.create_async_function
|
||||
/// [`Lua::scope`]: struct.Lua.html#method.scope
|
||||
/// [`Lua::async_scope`]: struct.Lua.html#method.async_scope
|
||||
#[cfg(feature = "async")]
|
||||
pub fn create_async_function<'callback, A, R, F, FR>(
|
||||
&'callback self,
|
||||
func: F,
|
||||
) -> Result<Function<'lua>>
|
||||
where
|
||||
A: FromLuaMulti<'callback>,
|
||||
R: ToLuaMulti<'callback>,
|
||||
F: 'scope + Fn(&'callback Lua, A) -> FR,
|
||||
FR: 'callback + Future<Output = Result<R>>,
|
||||
{
|
||||
unsafe {
|
||||
self.create_async_callback(Box::new(move |lua, args| {
|
||||
let args = match A::from_lua_multi(args, lua) {
|
||||
Ok(args) => args,
|
||||
Err(e) => return Box::pin(future::err(e)),
|
||||
};
|
||||
Box::pin(func(lua, args).and_then(move |ret| future::ready(ret.to_lua_multi(lua))))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a Lua userdata object from a custom userdata type.
|
||||
///
|
||||
/// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on
|
||||
/// scope drop, and does not require that the userdata type be Send (but still requires that the
|
||||
/// UserData be 'static).
|
||||
/// See [`Lua::scope`] for more details.
|
||||
///
|
||||
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
|
||||
/// [`Lua::scope`]: struct.Lua.html#method.scope
|
||||
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
|
||||
where
|
||||
T: 'static + UserData,
|
||||
{
|
||||
// Safe even though T may not be Send, because the parent Lua cannot be sent to another
|
||||
// thread while the Scope is alive (or the returned AnyUserData handle even).
|
||||
unsafe {
|
||||
let u = self.lua.make_userdata(data)?;
|
||||
self.destructors.borrow_mut().push((u.0.clone(), |u| {
|
||||
let state = u.lua.state;
|
||||
assert_stack(state, 2);
|
||||
u.lua.push_ref(&u);
|
||||
// We know the destructor has not run yet because we hold a reference to the
|
||||
// userdata.
|
||||
vec![Box::new(take_userdata::<RefCell<T>>(state))]
|
||||
}));
|
||||
Ok(u)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a Lua userdata object from a custom userdata type.
|
||||
///
|
||||
/// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on
|
||||
/// scope drop, and does not require that the userdata type be Send or 'static. See
|
||||
/// [`Lua::scope`] for more details.
|
||||
///
|
||||
/// Lifting the requirement that the UserData type be 'static comes with some important
|
||||
/// limitations, so if you only need to eliminate the Send requirement, it is probably better to
|
||||
/// use [`Scope::create_userdata`] instead.
|
||||
///
|
||||
/// The main limitation that comes from using non-'static userdata is that the produced userdata
|
||||
/// will no longer have a `TypeId` associated with it, becuase `TypeId` can only work for
|
||||
/// 'static types. This means that it is impossible, once the userdata is created, to get a
|
||||
/// reference to it back *out* of an `AnyUserData` handle. This also implies that the
|
||||
/// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept
|
||||
/// `AnyUserData` as a first parameter) are vastly less useful. Also, there is no way to re-use
|
||||
/// a single metatable for multiple non-'static types, so there is a higher cost associated with
|
||||
/// creating the userdata metatable each time a new userdata is created.
|
||||
///
|
||||
/// [`Scope::create_userdata`]: #method.create_userdata
|
||||
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
|
||||
/// [`Lua::scope`]: struct.Lua.html#method.scope
|
||||
/// [`UserDataMethods`]: trait.UserDataMethods.html
|
||||
pub fn create_nonstatic_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
|
||||
where
|
||||
T: 'scope + UserData,
|
||||
{
|
||||
let data = Rc::new(RefCell::new(data));
|
||||
|
||||
// 'callback outliving 'scope is a lie to make the types work out, required due to the
|
||||
// inability to work with the more correct callback type that is universally quantified over
|
||||
// 'lua. This is safe though, because `UserData::add_methods` does not get to pick the 'lua
|
||||
// lifetime, so none of the static methods UserData types can add can possibly capture
|
||||
// parameters.
|
||||
fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>(
|
||||
scope: &Scope<'lua, 'scope>,
|
||||
data: Rc<RefCell<T>>,
|
||||
method: NonStaticMethod<'callback, T>,
|
||||
) -> Result<Function<'lua>> {
|
||||
// On methods that actually receive the userdata, we fake a type check on the passed in
|
||||
// userdata, where we pretend there is a unique type per call to
|
||||
// `Scope::create_nonstatic_userdata`. You can grab a method from a userdata and call
|
||||
// it on a mismatched userdata type, which when using normal 'static userdata will fail
|
||||
// with a type mismatch, but here without this check would proceed as though you had
|
||||
// called the method on the original value (since we otherwise completely ignore the
|
||||
// first argument).
|
||||
let check_data = data.clone();
|
||||
let check_ud_type = move |lua: &'callback Lua, value| {
|
||||
if let Some(value) = value {
|
||||
if let Value::UserData(u) = value {
|
||||
unsafe {
|
||||
assert_stack(lua.state, 1);
|
||||
lua.push_ref(&u.0);
|
||||
ffi::lua_getuservalue(lua.state, -1);
|
||||
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
|
||||
{
|
||||
ffi::lua_rawgeti(lua.state, -1, 1);
|
||||
ffi::lua_remove(lua.state, -2);
|
||||
}
|
||||
return ffi::lua_touserdata(lua.state, -1)
|
||||
== check_data.as_ptr() as *mut c_void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
};
|
||||
|
||||
match method {
|
||||
NonStaticMethod::Method(method) => {
|
||||
let method_data = data.clone();
|
||||
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
|
||||
if !check_ud_type(lua, args.pop_front()) {
|
||||
return Err(Error::UserDataTypeMismatch);
|
||||
}
|
||||
let data = method_data
|
||||
.try_borrow()
|
||||
.map_err(|_| Error::UserDataBorrowError)?;
|
||||
method(lua, &*data, args)
|
||||
});
|
||||
unsafe { scope.create_callback(f) }
|
||||
}
|
||||
NonStaticMethod::MethodMut(method) => {
|
||||
let method = RefCell::new(method);
|
||||
let method_data = data.clone();
|
||||
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
|
||||
if !check_ud_type(lua, args.pop_front()) {
|
||||
return Err(Error::UserDataTypeMismatch);
|
||||
}
|
||||
let mut method = method
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| Error::RecursiveMutCallback)?;
|
||||
let mut data = method_data
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| Error::UserDataBorrowMutError)?;
|
||||
(&mut *method)(lua, &mut *data, args)
|
||||
});
|
||||
unsafe { scope.create_callback(f) }
|
||||
}
|
||||
NonStaticMethod::Function(function) => unsafe { scope.create_callback(function) },
|
||||
NonStaticMethod::FunctionMut(function) => {
|
||||
let function = RefCell::new(function);
|
||||
let f = Box::new(move |lua, args| {
|
||||
(&mut *function
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| Error::RecursiveMutCallback)?)(
|
||||
lua, args
|
||||
)
|
||||
});
|
||||
unsafe { scope.create_callback(f) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut ud_methods = NonStaticUserDataMethods::default();
|
||||
T::add_methods(&mut ud_methods);
|
||||
|
||||
unsafe {
|
||||
let lua = self.lua;
|
||||
let _sg = StackGuard::new(lua.state);
|
||||
assert_stack(lua.state, 6);
|
||||
|
||||
push_userdata(lua.state, ())?;
|
||||
#[cfg(feature = "lua53")]
|
||||
ffi::lua_pushlightuserdata(lua.state, data.as_ptr() as *mut c_void);
|
||||
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
|
||||
protect_lua_closure(lua.state, 0, 1, |state| {
|
||||
// Lua 5.2/5.1 allows to store only table. Then we will wrap the value.
|
||||
ffi::lua_createtable(state, 1, 0);
|
||||
ffi::lua_pushlightuserdata(state, data.as_ptr() as *mut c_void);
|
||||
ffi::lua_rawseti(state, -2, 1);
|
||||
})?;
|
||||
ffi::lua_setuservalue(lua.state, -2);
|
||||
|
||||
protect_lua_closure(lua.state, 0, 1, move |state| {
|
||||
ffi::lua_newtable(state);
|
||||
})?;
|
||||
|
||||
for (k, m) in ud_methods.meta_methods {
|
||||
push_string(lua.state, k.name())?;
|
||||
lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?))?;
|
||||
|
||||
protect_lua_closure(lua.state, 3, 1, |state| {
|
||||
ffi::lua_rawset(state, -3);
|
||||
})?;
|
||||
}
|
||||
|
||||
if ud_methods.methods.is_empty() {
|
||||
init_userdata_metatable::<()>(lua.state, -1, None)?;
|
||||
} else {
|
||||
protect_lua_closure(lua.state, 0, 1, |state| {
|
||||
ffi::lua_newtable(state);
|
||||
})?;
|
||||
for (k, m) in ud_methods.methods {
|
||||
push_string(lua.state, &k)?;
|
||||
lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?))?;
|
||||
protect_lua_closure(lua.state, 3, 1, |state| {
|
||||
ffi::lua_rawset(state, -3);
|
||||
})?;
|
||||
}
|
||||
|
||||
init_userdata_metatable::<()>(lua.state, -2, Some(-1))?;
|
||||
ffi::lua_pop(lua.state, 1);
|
||||
}
|
||||
|
||||
ffi::lua_setmetatable(lua.state, -2);
|
||||
|
||||
Ok(AnyUserData(lua.pop_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe, because the callback can improperly capture any value with 'callback scope, such as
|
||||
// improperly capturing an argument. Since the 'callback lifetime is chosen by the user and the
|
||||
// lifetime of the callback itself is 'scope (non-'static), the borrow checker will happily pick
|
||||
// a 'callback that outlives 'scope to allow this. In order for this to be safe, the callback
|
||||
// must NOT capture any parameters.
|
||||
unsafe fn create_callback<'callback>(
|
||||
&self,
|
||||
f: Callback<'callback, 'scope>,
|
||||
) -> Result<Function<'lua>> {
|
||||
let f = mem::transmute::<Callback<'callback, 'scope>, Callback<'lua, 'static>>(f);
|
||||
let f = self.lua.create_callback(f)?;
|
||||
|
||||
let mut destructors = self.destructors.borrow_mut();
|
||||
destructors.push((f.0.clone(), |f| {
|
||||
let state = f.lua.state;
|
||||
assert_stack(state, 3);
|
||||
f.lua.push_ref(&f);
|
||||
|
||||
// We know the destructor has not run yet because we hold a reference to the callback.
|
||||
|
||||
ffi::lua_getupvalue(state, -1, 1);
|
||||
let ud1 = take_userdata::<Callback>(state);
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_setupvalue(state, -2, 1);
|
||||
|
||||
ffi::lua_getupvalue(state, -1, 2);
|
||||
let ud2 = take_userdata::<Lua>(state);
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_setupvalue(state, -2, 2);
|
||||
|
||||
ffi::lua_pop(state, 1);
|
||||
vec![Box::new(ud1), Box::new(ud2)]
|
||||
}));
|
||||
Ok(f)
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
unsafe fn create_async_callback<'callback>(
|
||||
&self,
|
||||
f: AsyncCallback<'callback, 'scope>,
|
||||
) -> Result<Function<'lua>> {
|
||||
let f = mem::transmute::<AsyncCallback<'callback, 'scope>, AsyncCallback<'lua, 'static>>(f);
|
||||
let f = self.lua.create_async_callback(f)?;
|
||||
|
||||
let mut destructors = self.destructors.borrow_mut();
|
||||
destructors.push((f.0.clone(), |f| {
|
||||
let state = f.lua.state;
|
||||
assert_stack(state, 4);
|
||||
f.lua.push_ref(&f);
|
||||
|
||||
// We know the destructor has not run yet because we hold a reference to the callback.
|
||||
|
||||
// First, get the environment table
|
||||
#[cfg(any(feature = "lua53", feature = "lua52"))]
|
||||
ffi::lua_getupvalue(state, -1, 1);
|
||||
#[cfg(any(feature = "lua51", feature = "luajit"))]
|
||||
ffi::lua_getfenv(state, -1);
|
||||
|
||||
// Then, get the get_poll() closure using the corresponding key
|
||||
let key = "get_poll";
|
||||
ffi::lua_pushlstring(state, key.as_ptr() as *const c_char, key.len());
|
||||
ffi::lua_rawget(state, -2);
|
||||
|
||||
// Finally, destroy all upvalues
|
||||
ffi::lua_getupvalue(state, -1, 1);
|
||||
let ud1 = take_userdata::<AsyncCallback>(state);
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_setupvalue(state, -2, 1);
|
||||
|
||||
ffi::lua_getupvalue(state, -1, 2);
|
||||
let ud2 = take_userdata::<Lua>(state);
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_setupvalue(state, -2, 2);
|
||||
|
||||
ffi::lua_pop(state, 1);
|
||||
|
||||
vec![Box::new(ud1), Box::new(ud2)]
|
||||
}));
|
||||
|
||||
Ok(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua, 'scope> Drop for Scope<'lua, 'scope> {
|
||||
fn drop(&mut self) {
|
||||
// We separate the action of invalidating the userdata in Lua and actually dropping the
|
||||
// userdata type into two phases. This is so that, in the event a userdata drop panics, we
|
||||
// can be sure that all of the userdata in Lua is actually invalidated.
|
||||
|
||||
// All destructors are non-panicking, so this is fine
|
||||
let to_drop = self
|
||||
.destructors
|
||||
.get_mut()
|
||||
.drain(..)
|
||||
.flat_map(|(r, dest)| dest(r))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
drop(to_drop);
|
||||
}
|
||||
}
|
||||
|
||||
enum NonStaticMethod<'lua, T> {
|
||||
Method(Box<dyn Fn(&'lua Lua, &T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
|
||||
MethodMut(Box<dyn FnMut(&'lua Lua, &mut T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
|
||||
Function(Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
|
||||
FunctionMut(Box<dyn FnMut(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
|
||||
}
|
||||
|
||||
struct NonStaticUserDataMethods<'lua, T: UserData> {
|
||||
methods: Vec<(Vec<u8>, NonStaticMethod<'lua, T>)>,
|
||||
meta_methods: Vec<(MetaMethod, NonStaticMethod<'lua, T>)>,
|
||||
}
|
||||
|
||||
impl<'lua, T: UserData> Default for NonStaticUserDataMethods<'lua, T> {
|
||||
fn default() -> NonStaticUserDataMethods<'lua, T> {
|
||||
NonStaticUserDataMethods {
|
||||
methods: Vec::new(),
|
||||
meta_methods: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'lua, T> {
|
||||
fn add_method<S, A, R, M>(&mut self, name: &S, method: M)
|
||||
where
|
||||
S: AsRef<[u8]> + ?Sized,
|
||||
A: FromLuaMulti<'lua>,
|
||||
R: ToLuaMulti<'lua>,
|
||||
M: 'static + MaybeSend + Fn(&'lua Lua, &T, A) -> Result<R>,
|
||||
{
|
||||
self.methods.push((
|
||||
name.as_ref().to_vec(),
|
||||
NonStaticMethod::Method(Box::new(move |lua, ud, args| {
|
||||
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
fn add_method_mut<S, A, R, M>(&mut self, name: &S, mut method: M)
|
||||
where
|
||||
S: AsRef<[u8]> + ?Sized,
|
||||
A: FromLuaMulti<'lua>,
|
||||
R: ToLuaMulti<'lua>,
|
||||
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
|
||||
{
|
||||
self.methods.push((
|
||||
name.as_ref().to_vec(),
|
||||
NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
|
||||
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn add_async_method<S, A, R, M, MR>(&mut self, _name: &S, _method: M)
|
||||
where
|
||||
T: Clone,
|
||||
S: AsRef<[u8]> + ?Sized,
|
||||
A: FromLuaMulti<'lua>,
|
||||
R: ToLuaMulti<'lua>,
|
||||
M: 'static + MaybeSend + Fn(&'lua Lua, T, A) -> MR,
|
||||
MR: 'lua + Future<Output = Result<R>>,
|
||||
{
|
||||
// The panic should never happen as async non-static code wouldn't compile
|
||||
// Non-static lifetime must be bounded to 'lua lifetime
|
||||
mlua_panic!("asynchronous methods are not supported for non-static userdata")
|
||||
}
|
||||
|
||||
fn add_function<S, A, R, F>(&mut self, name: &S, function: F)
|
||||
where
|
||||
S: AsRef<[u8]> + ?Sized,
|
||||
A: FromLuaMulti<'lua>,
|
||||
R: ToLuaMulti<'lua>,
|
||||
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>,
|
||||
{
|
||||
self.methods.push((
|
||||
name.as_ref().to_vec(),
|
||||
NonStaticMethod::Function(Box::new(move |lua, args| {
|
||||
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
fn add_function_mut<S, A, R, F>(&mut self, name: &S, mut function: F)
|
||||
where
|
||||
S: AsRef<[u8]> + ?Sized,
|
||||
A: FromLuaMulti<'lua>,
|
||||
R: ToLuaMulti<'lua>,
|
||||
F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result<R>,
|
||||
{
|
||||
self.methods.push((
|
||||
name.as_ref().to_vec(),
|
||||
NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
|
||||
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn add_async_function<S, A, R, F, FR>(&mut self, _name: &S, _function: F)
|
||||
where
|
||||
T: Clone,
|
||||
S: AsRef<[u8]> + ?Sized,
|
||||
A: FromLuaMulti<'lua>,
|
||||
R: ToLuaMulti<'lua>,
|
||||
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> FR,
|
||||
FR: 'lua + Future<Output = Result<R>>,
|
||||
{
|
||||
// The panic should never happen as async non-static code wouldn't compile
|
||||
// Non-static lifetime must be bounded to 'lua lifetime
|
||||
mlua_panic!("asynchronous functions are not supported for non-static userdata")
|
||||
}
|
||||
|
||||
fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M)
|
||||
where
|
||||
A: FromLuaMulti<'lua>,
|
||||
R: ToLuaMulti<'lua>,
|
||||
M: 'static + MaybeSend + Fn(&'lua Lua, &T, A) -> Result<R>,
|
||||
{
|
||||
self.meta_methods.push((
|
||||
meta,
|
||||
NonStaticMethod::Method(Box::new(move |lua, ud, args| {
|
||||
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, mut method: M)
|
||||
where
|
||||
A: FromLuaMulti<'lua>,
|
||||
R: ToLuaMulti<'lua>,
|
||||
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
|
||||
{
|
||||
self.meta_methods.push((
|
||||
meta,
|
||||
NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
|
||||
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F)
|
||||
where
|
||||
A: FromLuaMulti<'lua>,
|
||||
R: ToLuaMulti<'lua>,
|
||||
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>,
|
||||
{
|
||||
self.meta_methods.push((
|
||||
meta,
|
||||
NonStaticMethod::Function(Box::new(move |lua, args| {
|
||||
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, mut function: F)
|
||||
where
|
||||
A: FromLuaMulti<'lua>,
|
||||
R: ToLuaMulti<'lua>,
|
||||
F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result<R>,
|
||||
{
|
||||
self.meta_methods.push((
|
||||
meta,
|
||||
NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
|
||||
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
}
|
107
tests/async.rs
107
tests/async.rs
|
@ -1,5 +1,7 @@
|
|||
#![cfg(feature = "async")]
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{
|
||||
atomic::{AtomicI64, Ordering},
|
||||
Arc,
|
||||
|
@ -286,8 +288,8 @@ async fn test_async_userdata() -> Result<()> {
|
|||
r#"
|
||||
assert(userdata:get_value() == 11)
|
||||
userdata:set_value(12)
|
||||
assert(userdata:get_value() == 12)
|
||||
assert(userdata.sleep(5) == "elapsed:5ms")
|
||||
assert(userdata:get_value() == 12)
|
||||
"#,
|
||||
)
|
||||
.exec_async()
|
||||
|
@ -295,3 +297,106 @@ async fn test_async_userdata() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_scope() -> Result<()> {
|
||||
let ref lua = Lua::new();
|
||||
|
||||
let ref rc = Rc::new(Cell::new(0));
|
||||
|
||||
let fut = lua.async_scope(|scope| async move {
|
||||
let f = scope.create_async_function(move |_, n: u64| {
|
||||
let rc2 = rc.clone();
|
||||
async move {
|
||||
rc2.set(42);
|
||||
Delay::new(Duration::from_millis(n)).await;
|
||||
assert_eq!(Rc::strong_count(&rc2), 2);
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
|
||||
lua.globals().set("f", f.clone())?;
|
||||
|
||||
assert_eq!(Rc::strong_count(rc), 1);
|
||||
let _ = f.call_async::<u64, ()>(10).await?;
|
||||
assert_eq!(Rc::strong_count(rc), 1);
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
assert_eq!(Rc::strong_count(rc), 1);
|
||||
let _ = fut.await?;
|
||||
|
||||
match lua
|
||||
.globals()
|
||||
.get::<_, Function>("f")?
|
||||
.call_async::<_, ()>(10)
|
||||
.await
|
||||
{
|
||||
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
|
||||
Error::CallbackDestructed => {}
|
||||
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
|
||||
},
|
||||
r => panic!("improper return for destructed function: {:?}", r),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_scope_userdata() -> Result<()> {
|
||||
#[derive(Clone)]
|
||||
struct MyUserData(Arc<AtomicI64>);
|
||||
|
||||
impl UserData for MyUserData {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_async_method("get_value", |_, data, ()| async move {
|
||||
Delay::new(Duration::from_millis(10)).await;
|
||||
Ok(data.0.load(Ordering::Relaxed))
|
||||
});
|
||||
|
||||
methods.add_async_method("set_value", |_, data, n| async move {
|
||||
Delay::new(Duration::from_millis(10)).await;
|
||||
data.0.store(n, Ordering::Relaxed);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
methods.add_async_function("sleep", |_, n| async move {
|
||||
Delay::new(Duration::from_millis(n)).await;
|
||||
Ok(format!("elapsed:{}ms", n))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let ref lua = Lua::new();
|
||||
|
||||
let ref arc = Arc::new(AtomicI64::new(11));
|
||||
|
||||
lua.async_scope(|scope| async move {
|
||||
let ud = scope.create_userdata(MyUserData(arc.clone()))?;
|
||||
lua.globals().set("userdata", ud)?;
|
||||
lua.load(
|
||||
r#"
|
||||
assert(userdata:get_value() == 11)
|
||||
userdata:set_value(12)
|
||||
assert(userdata.sleep(5) == "elapsed:5ms")
|
||||
assert(userdata:get_value() == 12)
|
||||
"#,
|
||||
)
|
||||
.exec_async()
|
||||
.await
|
||||
})
|
||||
.await?;
|
||||
|
||||
assert_eq!(Arc::strong_count(arc), 1);
|
||||
|
||||
match lua.load("userdata:get_value()").exec_async().await {
|
||||
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
|
||||
Error::CallbackDestructed => {}
|
||||
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
|
||||
},
|
||||
r => panic!("improper return for destructed userdata: {:?}", r),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
use mlua::{Lua, UserData, UserDataMethods};
|
||||
|
||||
fn main() {
|
||||
let ref lua = Lua::new();
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MyUserData<'a>(&'a i64);
|
||||
|
||||
impl<'a> UserData for MyUserData<'a> {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_async_method("print", |_, data, ()| async move {
|
||||
println!("{}", data.0);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
|
||||
--> $DIR/scope_async_userdata.rs:11:72
|
||||
|
|
||||
11 | methods.add_async_method("print", |_, data, ()| async move {
|
||||
| ________________________________________________________________________^
|
||||
12 | | println!("{}", data.0);
|
||||
13 | | Ok(())
|
||||
14 | | });
|
||||
| |_____________^
|
||||
|
|
||||
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the impl at 9:10...
|
||||
--> $DIR/scope_async_userdata.rs:9:10
|
||||
|
|
||||
9 | impl<'a> UserData for MyUserData<'a> {
|
||||
| ^^
|
||||
note: ...so that the types are compatible
|
||||
--> $DIR/scope_async_userdata.rs:11:72
|
||||
|
|
||||
11 | methods.add_async_method("print", |_, data, ()| async move {
|
||||
| ________________________________________________________________________^
|
||||
12 | | println!("{}", data.0);
|
||||
13 | | Ok(())
|
||||
14 | | });
|
||||
| |_____________^
|
||||
= note: expected `main::MyUserData<'_>`
|
||||
found `main::MyUserData<'a>`
|
||||
note: but, the lifetime must be valid for the lifetime `'lua` as defined on the method body at 10:24...
|
||||
--> $DIR/scope_async_userdata.rs:10:24
|
||||
|
|
||||
10 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
| ^^^^
|
||||
note: ...so that the type `impl std::future::Future` will meet its required lifetime bounds
|
||||
--> $DIR/scope_async_userdata.rs:11:21
|
||||
|
|
||||
11 | methods.add_async_method("print", |_, data, ()| async move {
|
||||
| ^^^^^^^^^^^^^^^^
|
|
@ -9,49 +9,3 @@ error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutabili
|
|||
= note: required because it appears within the type `mlua::lua::Lua`
|
||||
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
|
||||
|
||||
error[E0277]: the type `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
--> $DIR/lua_norefunwindsafe.rs:7:5
|
||||
|
|
||||
7 | catch_unwind(|| lua.create_table().unwrap());
|
||||
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
|
|
||||
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<usize>`
|
||||
= note: required because it appears within the type `std::cell::Cell<usize>`
|
||||
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
|
||||
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `mlua::lua::Lua`
|
||||
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
|
||||
|
||||
error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
--> $DIR/lua_norefunwindsafe.rs:7:5
|
||||
|
|
||||
7 | catch_unwind(|| lua.create_table().unwrap());
|
||||
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
|
|
||||
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<mlua::lua::ExtraData>`
|
||||
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
|
||||
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
|
||||
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `mlua::lua::Lua`
|
||||
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
|
||||
|
||||
error[E0277]: the type `std::cell::UnsafeCell<isize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
--> $DIR/lua_norefunwindsafe.rs:7:5
|
||||
|
|
||||
7 | catch_unwind(|| lua.create_table().unwrap());
|
||||
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<isize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
|
|
||||
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<isize>`
|
||||
= note: required because it appears within the type `std::cell::Cell<isize>`
|
||||
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
|
||||
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
|
||||
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `mlua::lua::Lua`
|
||||
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
|
||||
|
|
|
@ -11,55 +11,3 @@ error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutabili
|
|||
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
|
||||
= note: required because it appears within the type `mlua::table::Table<'_>`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
|
||||
|
||||
error[E0277]: the type `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
--> $DIR/ref_nounwindsafe.rs:8:5
|
||||
|
|
||||
8 | catch_unwind(move || table.set("a", "b").unwrap());
|
||||
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
|
|
||||
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<usize>`
|
||||
= note: required because it appears within the type `std::cell::Cell<usize>`
|
||||
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
|
||||
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `mlua::lua::Lua`
|
||||
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
|
||||
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
|
||||
= note: required because it appears within the type `mlua::table::Table<'_>`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
|
||||
|
||||
error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
--> $DIR/ref_nounwindsafe.rs:8:5
|
||||
|
|
||||
8 | catch_unwind(move || table.set("a", "b").unwrap());
|
||||
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
|
|
||||
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<mlua::lua::ExtraData>`
|
||||
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
|
||||
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
|
||||
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `mlua::lua::Lua`
|
||||
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
|
||||
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
|
||||
= note: required because it appears within the type `mlua::table::Table<'_>`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
|
||||
|
||||
error[E0277]: the type `std::cell::UnsafeCell<isize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
--> $DIR/ref_nounwindsafe.rs:8:5
|
||||
|
|
||||
8 | catch_unwind(move || table.set("a", "b").unwrap());
|
||||
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<isize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
||||
|
|
||||
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<isize>`
|
||||
= note: required because it appears within the type `std::cell::Cell<isize>`
|
||||
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
|
||||
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
|
||||
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
|
||||
= note: required because it appears within the type `mlua::lua::Lua`
|
||||
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
|
||||
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
|
||||
= note: required because it appears within the type `mlua::table::Table<'_>`
|
||||
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
use mlua::{Lua, Table};
|
||||
|
||||
fn main() {
|
||||
let lua = Lua::new();
|
||||
lua.scope(|scope| {
|
||||
let mut inner: Option<Table> = None;
|
||||
let f = scope
|
||||
.create_function_mut(move |_, t: Table| {
|
||||
if let Some(old) = inner.take() {
|
||||
// Access old callback `Lua`.
|
||||
}
|
||||
inner = Some(t);
|
||||
Ok(())
|
||||
})?;
|
||||
f.call::<_, ()>(lua.create_table()?)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
|
||||
--> $DIR/scope_callback_capture.rs:8:14
|
||||
|
|
||||
8 | .create_function_mut(move |_, t: Table| {
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 5:15...
|
||||
--> $DIR/scope_callback_capture.rs:5:15
|
||||
|
|
||||
5 | lua.scope(|scope| {
|
||||
| _______________^
|
||||
6 | | let mut inner: Option<Table> = None;
|
||||
7 | | let f = scope
|
||||
8 | | .create_function_mut(move |_, t: Table| {
|
||||
... |
|
||||
16 | | Ok(())
|
||||
17 | | });
|
||||
| |_____^
|
||||
note: ...so that reference does not outlive borrowed content
|
||||
--> $DIR/scope_callback_capture.rs:7:17
|
||||
|
|
||||
7 | let f = scope
|
||||
| ^^^^^
|
||||
note: but, the lifetime must be valid for the method call at 5:5...
|
||||
--> $DIR/scope_callback_capture.rs:5:5
|
||||
|
|
||||
5 | / lua.scope(|scope| {
|
||||
6 | | let mut inner: Option<Table> = None;
|
||||
7 | | let f = scope
|
||||
8 | | .create_function_mut(move |_, t: Table| {
|
||||
... |
|
||||
16 | | Ok(())
|
||||
17 | | });
|
||||
| |______^
|
||||
note: ...so that a type/lifetime parameter is in scope here
|
||||
--> $DIR/scope_callback_capture.rs:5:5
|
||||
|
|
||||
5 | / lua.scope(|scope| {
|
||||
6 | | let mut inner: Option<Table> = None;
|
||||
7 | | let f = scope
|
||||
8 | | .create_function_mut(move |_, t: Table| {
|
||||
... |
|
||||
16 | | Ok(())
|
||||
17 | | });
|
||||
| |______^
|
|
@ -0,0 +1,15 @@
|
|||
use mlua::{Lua, Table};
|
||||
|
||||
fn main() {
|
||||
let lua = Lua::new();
|
||||
lua.scope(|scope| {
|
||||
let mut inner: Option<Table> = None;
|
||||
let f = scope
|
||||
.create_function_mut(|_, t: Table| {
|
||||
inner = Some(t);
|
||||
Ok(())
|
||||
})?;
|
||||
f.call::<_, ()>(lua.create_table()?)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
|
||||
--> $DIR/scope_callback_inner.rs:8:14
|
||||
|
|
||||
8 | .create_function_mut(|_, t: Table| {
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 5:15...
|
||||
--> $DIR/scope_callback_inner.rs:5:15
|
||||
|
|
||||
5 | lua.scope(|scope| {
|
||||
| _______________^
|
||||
6 | | let mut inner: Option<Table> = None;
|
||||
7 | | let f = scope
|
||||
8 | | .create_function_mut(|_, t: Table| {
|
||||
... |
|
||||
13 | | Ok(())
|
||||
14 | | });
|
||||
| |_____^
|
||||
note: ...so that reference does not outlive borrowed content
|
||||
--> $DIR/scope_callback_inner.rs:7:17
|
||||
|
|
||||
7 | let f = scope
|
||||
| ^^^^^
|
||||
note: but, the lifetime must be valid for the method call at 5:5...
|
||||
--> $DIR/scope_callback_inner.rs:5:5
|
||||
|
|
||||
5 | / lua.scope(|scope| {
|
||||
6 | | let mut inner: Option<Table> = None;
|
||||
7 | | let f = scope
|
||||
8 | | .create_function_mut(|_, t: Table| {
|
||||
... |
|
||||
13 | | Ok(())
|
||||
14 | | });
|
||||
| |______^
|
||||
note: ...so that a type/lifetime parameter is in scope here
|
||||
--> $DIR/scope_callback_inner.rs:5:5
|
||||
|
|
||||
5 | / lua.scope(|scope| {
|
||||
6 | | let mut inner: Option<Table> = None;
|
||||
7 | | let f = scope
|
||||
8 | | .create_function_mut(|_, t: Table| {
|
||||
... |
|
||||
13 | | Ok(())
|
||||
14 | | });
|
||||
| |______^
|
|
@ -0,0 +1,15 @@
|
|||
use mlua::{Lua, Table};
|
||||
|
||||
fn main() {
|
||||
let lua = Lua::new();
|
||||
let mut outer: Option<Table> = None;
|
||||
lua.scope(|scope| {
|
||||
let f = scope
|
||||
.create_function_mut(|_, t: Table| {
|
||||
outer = Some(t);
|
||||
Ok(())
|
||||
})?;
|
||||
f.call::<_, ()>(lua.create_table()?)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
error: borrowed data cannot be stored outside of its closure
|
||||
--> $DIR/scope_callback_outer.rs:7:17
|
||||
|
|
||||
5 | let mut outer: Option<Table> = None;
|
||||
| --------- ...so that variable is valid at time of its declaration
|
||||
6 | lua.scope(|scope| {
|
||||
| ------- borrowed data cannot outlive this closure
|
||||
7 | let f = scope
|
||||
| ^^^^^ cannot be stored outside of its closure
|
||||
8 | .create_function_mut(|_, t: Table| {
|
||||
| ------------------- cannot infer an appropriate lifetime...
|
|
@ -0,0 +1,23 @@
|
|||
use mlua::Lua;
|
||||
|
||||
struct Test {
|
||||
field: i32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let lua = Lua::new();
|
||||
lua.scope(|scope| {
|
||||
let f = {
|
||||
let mut test = Test { field: 0 };
|
||||
|
||||
scope
|
||||
.create_function_mut(|_, ()| {
|
||||
test.field = 42;
|
||||
//~^ error: `test` does not live long enough
|
||||
Ok(())
|
||||
})?
|
||||
};
|
||||
|
||||
f.call::<_, ()>(())
|
||||
});
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
error[E0373]: closure may outlive the current function, but it borrows `test`, which is owned by the current function
|
||||
--> $DIR/scope_invariance.rs:14:38
|
||||
|
|
||||
9 | lua.scope(|scope| {
|
||||
| ----- has type `&mlua::scope::Scope<'_, '1>`
|
||||
...
|
||||
14 | .create_function_mut(|_, ()| {
|
||||
| ^^^^^^^ may outlive borrowed value `test`
|
||||
15 | test.field = 42;
|
||||
| ---- `test` is borrowed here
|
||||
|
|
||||
note: function requires argument type to outlive `'1`
|
||||
--> $DIR/scope_invariance.rs:13:13
|
||||
|
|
||||
13 | / scope
|
||||
14 | | .create_function_mut(|_, ()| {
|
||||
15 | | test.field = 42;
|
||||
16 | | //~^ error: `test` does not live long enough
|
||||
17 | | Ok(())
|
||||
18 | | })?
|
||||
| |__________________^
|
||||
help: to force the closure to take ownership of `test` (and any other referenced variables), use the `move` keyword
|
||||
|
|
||||
14 | .create_function_mut(move |_, ()| {
|
||||
| ^^^^^^^^^^^^
|
|
@ -0,0 +1,15 @@
|
|||
use mlua::{Lua, UserData};
|
||||
|
||||
fn main() {
|
||||
struct MyUserData<'a>(&'a mut i32);
|
||||
impl<'a> UserData for MyUserData<'a> {};
|
||||
|
||||
let mut i = 1;
|
||||
|
||||
let lua = Lua::new();
|
||||
lua.scope(|scope| {
|
||||
let _a = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap();
|
||||
let _b = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap();
|
||||
Ok(())
|
||||
});
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
error[E0499]: cannot borrow `i` as mutable more than once at a time
|
||||
--> $DIR/scope_mutable_aliasing.rs:12:61
|
||||
|
|
||||
11 | let _a = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap();
|
||||
| ------ first mutable borrow occurs here
|
||||
12 | let _b = scope.create_nonstatic_userdata(MyUserData(&mut i)).unwrap();
|
||||
| ------------------------- ^^^^^^ second mutable borrow occurs here
|
||||
| |
|
||||
| first borrow later used by call
|
|
@ -0,0 +1,19 @@
|
|||
use mlua::{Lua, UserData};
|
||||
|
||||
fn main() {
|
||||
// Should not allow userdata borrow to outlive lifetime of AnyUserData handle
|
||||
struct MyUserData<'a>(&'a i32);
|
||||
impl<'a> UserData for MyUserData<'a> {};
|
||||
|
||||
let igood = 1;
|
||||
|
||||
let lua = Lua::new();
|
||||
lua.scope(|scope| {
|
||||
let _ugood = scope.create_nonstatic_userdata(MyUserData(&igood)).unwrap();
|
||||
let _ubad = {
|
||||
let ibad = 42;
|
||||
scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap();
|
||||
};
|
||||
Ok(())
|
||||
});
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
error[E0597]: `ibad` does not live long enough
|
||||
--> $DIR/scope_userdata_borrow.rs:15:56
|
||||
|
|
||||
11 | lua.scope(|scope| {
|
||||
| ----- has type `&mlua::scope::Scope<'_, '1>`
|
||||
...
|
||||
15 | scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap();
|
||||
| -------------------------------------------^^^^^--
|
||||
| | |
|
||||
| | borrowed value does not live long enough
|
||||
| argument requires that `ibad` is borrowed for `'1`
|
||||
16 | };
|
||||
| - `ibad` dropped here while still borrowed
|
|
@ -0,0 +1,228 @@
|
|||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use mlua::{Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataMethods};
|
||||
|
||||
#[test]
|
||||
fn scope_func() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let rc = Rc::new(Cell::new(0));
|
||||
lua.scope(|scope| {
|
||||
let r = rc.clone();
|
||||
let f = scope.create_function(move |_, ()| {
|
||||
r.set(42);
|
||||
Ok(())
|
||||
})?;
|
||||
lua.globals().set("bad", f.clone())?;
|
||||
f.call::<_, ()>(())?;
|
||||
assert_eq!(Rc::strong_count(&rc), 2);
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(rc.get(), 42);
|
||||
assert_eq!(Rc::strong_count(&rc), 1);
|
||||
|
||||
match lua.globals().get::<_, Function>("bad")?.call::<_, ()>(()) {
|
||||
Err(Error::CallbackError { .. }) => {}
|
||||
r => panic!("improper return for destructed function: {:?}", r),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_drop() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
struct MyUserdata(Rc<()>);
|
||||
impl UserData for MyUserdata {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("method", |_, _, ()| Ok(()));
|
||||
}
|
||||
}
|
||||
|
||||
let rc = Rc::new(());
|
||||
|
||||
lua.scope(|scope| {
|
||||
lua.globals()
|
||||
.set("test", scope.create_userdata(MyUserdata(rc.clone()))?)?;
|
||||
assert_eq!(Rc::strong_count(&rc), 2);
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(Rc::strong_count(&rc), 1);
|
||||
|
||||
match lua.load("test:method()").exec() {
|
||||
Err(Error::CallbackError { .. }) => {}
|
||||
r => panic!("improper return for destructed userdata: {:?}", r),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_capture() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let mut i = 0;
|
||||
lua.scope(|scope| {
|
||||
scope
|
||||
.create_function_mut(|_, ()| {
|
||||
i = 42;
|
||||
Ok(())
|
||||
})?
|
||||
.call::<_, ()>(())
|
||||
})?;
|
||||
assert_eq!(i, 42);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outer_lua_access() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let table = lua.create_table()?;
|
||||
lua.scope(|scope| {
|
||||
scope
|
||||
.create_function_mut(|_, ()| table.set("a", "b"))?
|
||||
.call::<_, ()>(())
|
||||
})?;
|
||||
assert_eq!(table.get::<_, String>("a")?, "b");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_userdata_methods() -> Result<()> {
|
||||
struct MyUserData<'a>(&'a Cell<i64>);
|
||||
|
||||
impl<'a> UserData for MyUserData<'a> {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("inc", |_, data, ()| {
|
||||
data.0.set(data.0.get() + 1);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
methods.add_method("dec", |_, data, ()| {
|
||||
data.0.set(data.0.get() - 1);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
|
||||
let i = Cell::new(42);
|
||||
let f: Function = lua
|
||||
.load(
|
||||
r#"
|
||||
function(u)
|
||||
u:inc()
|
||||
u:inc()
|
||||
u:inc()
|
||||
u:dec()
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.eval()?;
|
||||
|
||||
lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&i))?))?;
|
||||
|
||||
assert_eq!(i.get(), 44);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_userdata_functions() -> Result<()> {
|
||||
struct MyUserData<'a>(&'a i64);
|
||||
|
||||
impl<'a> UserData for MyUserData<'a> {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_function(MetaMethod::Add, |lua, ()| {
|
||||
let globals = lua.globals();
|
||||
globals.set("i", globals.get::<_, i64>("i")? + 1)?;
|
||||
Ok(())
|
||||
});
|
||||
methods.add_meta_function(MetaMethod::Sub, |lua, ()| {
|
||||
let globals = lua.globals();
|
||||
globals.set("i", globals.get::<_, i64>("i")? + 1)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
|
||||
let dummy = 0;
|
||||
let f = lua
|
||||
.load(
|
||||
r#"
|
||||
i = 0
|
||||
return function(u)
|
||||
_ = u + u
|
||||
_ = u - 1
|
||||
_ = 1 + u
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.eval::<Function>()?;
|
||||
|
||||
lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&dummy))?))?;
|
||||
|
||||
assert_eq!(lua.globals().get::<_, i64>("i")?, 3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_userdata_mismatch() -> Result<()> {
|
||||
struct MyUserData<'a>(&'a Cell<i64>);
|
||||
|
||||
impl<'a> UserData for MyUserData<'a> {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("inc", |_, data, ()| {
|
||||
data.0.set(data.0.get() + 1);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
function okay(a, b)
|
||||
a.inc(a)
|
||||
b.inc(b)
|
||||
end
|
||||
function bad(a, b)
|
||||
a.inc(b)
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
let a = Cell::new(1);
|
||||
let b = Cell::new(1);
|
||||
|
||||
let okay: Function = lua.globals().get("okay")?;
|
||||
let bad: Function = lua.globals().get("bad")?;
|
||||
|
||||
lua.scope(|scope| {
|
||||
let au = scope.create_nonstatic_userdata(MyUserData(&a))?;
|
||||
let bu = scope.create_nonstatic_userdata(MyUserData(&b))?;
|
||||
assert!(okay.call::<_, ()>((au.clone(), bu.clone())).is_ok());
|
||||
match bad.call::<_, ()>((au, bu)) {
|
||||
Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() {
|
||||
Error::UserDataTypeMismatch => {}
|
||||
ref other => panic!("wrong error type {:?}", other),
|
||||
},
|
||||
Err(other) => panic!("wrong error type {:?}", other),
|
||||
Ok(_) => panic!("incorrectly returned Ok"),
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue