use std::any::TypeId; use std::collections::HashMap; use std::sync::Arc; #[cfg(any(feature = "lua55", feature = "lua54"))] use std::sync::atomic::{AtomicI64, Ordering}; use mlua::{ AnyUserData, Error, ExternalError, Function, Lua, LuaString, MetaMethod, Nil, ObjectLike, Result, UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value, Variadic, }; #[test] fn test_userdata() -> Result<()> { struct UserData1(i64); struct UserData2(Box); impl UserData for UserData1 {} impl UserData for UserData2 {} let lua = Lua::new(); let userdata1 = lua.create_userdata(UserData1(1))?; let userdata2 = lua.create_userdata(UserData2(Box::new(2)))?; assert!(userdata1.is::()); assert!(userdata1.type_id() == Some(TypeId::of::())); assert!(!userdata1.is::()); assert!(userdata2.is::()); assert!(!userdata2.is::()); assert!(userdata2.type_id() == Some(TypeId::of::())); assert_eq!(userdata1.borrow::()?.0, 1); assert_eq!(*userdata2.borrow::()?.0, 2); Ok(()) } #[test] fn test_methods() -> Result<()> { #[cfg_attr(feature = "serde", derive(serde::Serialize))] struct MyUserData(i64); impl UserData for MyUserData { fn add_methods>(methods: &mut M) { methods.add_method("get_value", |_, data, ()| Ok(data.0)); methods.add_method_mut("set_value", |_, data, args| { data.0 = args; Ok(()) }); } } fn check_methods(lua: &Lua, userdata: AnyUserData) -> Result<()> { let globals = lua.globals(); globals.set("userdata", &userdata)?; lua.load( r#" function get_it() return userdata:get_value() end function set_it(i) return userdata:set_value(i) end "#, ) .exec()?; let get = globals.get::("get_it")?; let set = globals.get::("set_it")?; assert_eq!(get.call::(())?, 42); userdata.borrow_mut::()?.0 = 64; assert_eq!(get.call::(())?, 64); set.call::<()>(100)?; assert_eq!(get.call::(())?, 100); Ok(()) } let lua = Lua::new(); check_methods(&lua, lua.create_userdata(MyUserData(42))?)?; // Additionally check serializable userdata #[cfg(feature = "serde")] check_methods(&lua, lua.create_ser_userdata(MyUserData(42))?)?; Ok(()) } #[test] fn test_method_variadic() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { fn add_methods>(methods: &mut M) { methods.add_method("get", |_, data, ()| Ok(data.0)); methods.add_method_mut("add", |_, data, vals: Variadic| { data.0 += vals.into_iter().sum::(); Ok(()) }); } } let lua = Lua::new(); let globals = lua.globals(); globals.set("userdata", MyUserData(0))?; lua.load("userdata:add(1, 5, -10)").exec()?; let ud: UserDataRef = globals.get("userdata")?; assert_eq!(ud.0, -4); Ok(()) } #[test] fn test_metamethods() -> Result<()> { #[derive(Copy, Clone)] struct MyUserData(i64); impl UserData for MyUserData { fn add_methods>(methods: &mut M) { methods.add_method("get", |_, data, ()| Ok(data.0)); methods.add_meta_function( MetaMethod::Add, |_, (lhs, rhs): (UserDataRef, UserDataRef)| Ok(MyUserData(lhs.0 + rhs.0)), ); methods.add_meta_function( MetaMethod::Sub, |_, (lhs, rhs): (UserDataRef, UserDataRef)| Ok(MyUserData(lhs.0 - rhs.0)), ); methods.add_meta_function( MetaMethod::Eq, |_, (lhs, rhs): (UserDataRef, UserDataRef)| Ok(lhs.0 == rhs.0), ); methods.add_meta_method(MetaMethod::Index, |_, data, index: LuaString| { if index.to_str()? == "inner" { Ok(data.0) } else { Err("no such custom index".into_lua_err()) } }); #[cfg(any( feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52" ))] methods.add_meta_method(MetaMethod::Pairs, |lua, data, ()| { use std::iter::FromIterator; let stateless_iter = lua.create_function(|_, (data, i): (UserDataRef, i64)| { let i = i + 1; if i <= data.0 { return Ok(mlua::Variadic::from_iter(vec![i, i])); } return Ok(mlua::Variadic::new()); })?; Ok((stateless_iter, data.clone(), 0)) }); } } let lua = Lua::new(); let globals = lua.globals(); globals.set("userdata1", MyUserData(7))?; globals.set("userdata2", MyUserData(3))?; globals.set("userdata3", MyUserData(3))?; assert_eq!( lua.load("userdata1 + userdata2") .eval::>()? .0, 10 ); #[cfg(any( feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52" ))] let pairs_it = lua .load( r#" function() local r = 0 for i, v in pairs(userdata1) do r = r + v end return r end "#, ) .eval::()?; assert_eq!( lua.load("userdata1 - userdata2") .eval::>()? .0, 4 ); assert_eq!(lua.load("userdata1:get()").eval::()?, 7); assert_eq!(lua.load("userdata2.inner").eval::()?, 3); assert!(lua.load("userdata2.nonexist_field").eval::<()>().is_err()); #[cfg(any( feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52" ))] assert_eq!(pairs_it.call::(())?, 28); let userdata2: Value = globals.get("userdata2")?; let userdata3: Value = globals.get("userdata3")?; assert!(lua.load("userdata2 == userdata3").eval::()?); assert!(userdata2 != userdata3); // because references are differ assert!(userdata2.equals(&userdata3)?); let userdata1: AnyUserData = globals.get("userdata1")?; assert!(userdata1.metatable()?.contains(MetaMethod::Add)?); assert!(userdata1.metatable()?.contains(MetaMethod::Sub)?); assert!(userdata1.metatable()?.contains(MetaMethod::Index)?); assert!(!userdata1.metatable()?.contains(MetaMethod::Pow)?); Ok(()) } #[cfg(any(feature = "lua55", feature = "lua54"))] #[test] fn test_metamethod_close() -> Result<()> { #[derive(Clone)] struct MyUserData(Arc); impl UserData for MyUserData { fn add_methods>(methods: &mut M) { methods.add_method("get", |_, data, ()| Ok(data.0.load(Ordering::Relaxed))); methods.add_meta_method(MetaMethod::Close, |_, data, _err: Value| { data.0.store(0, Ordering::Relaxed); Ok(()) }); } } let lua = Lua::new(); let globals = lua.globals(); let ud = MyUserData(Arc::new(AtomicI64::new(-1))); let ud2 = ud.clone(); globals.set( "new_userdata", lua.create_function(move |_lua, val: i64| { let ud = ud2.clone(); ud.0.store(val, Ordering::Relaxed); Ok(ud) })?, )?; lua.load( r#" do local ud = new_userdata(7) assert(ud:get() == 7) end "#, ) .exec()?; assert_eq!(ud.0.load(Ordering::Relaxed), 0); Ok(()) } #[test] fn test_gc_userdata() -> Result<()> { struct MyUserdata { id: u8, } impl UserData for MyUserdata { fn add_methods>(methods: &mut M) { methods.add_method("access", |_, this, ()| { assert_eq!(this.id, 123); Ok(()) }); } } let lua = Lua::new(); lua.globals().set("userdata", MyUserdata { id: 123 })?; assert!( lua.load( r#" local tbl = setmetatable({ userdata = userdata }, { __gc = function(self) -- resurrect userdata hatch = self.userdata end }) tbl = nil userdata = nil -- make table and userdata collectable collectgarbage("collect") hatch:access() "# ) .exec() .is_err() ); Ok(()) } #[test] fn test_userdata_take() -> Result<()> { #[derive(Debug)] struct MyUserdata(Arc); impl UserData for MyUserdata { fn add_methods>(methods: &mut M) { methods.add_method("num", |_, this, ()| Ok(*this.0)) } } #[cfg(feature = "serde")] impl serde::Serialize for MyUserdata { fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { serializer.serialize_i64(*self.0) } } fn check_userdata_take(lua: &Lua, userdata: AnyUserData, rc: Arc) -> Result<()> { lua.globals().set("userdata", &userdata)?; assert_eq!(Arc::strong_count(&rc), 3); { let _value = userdata.borrow::()?; // We should not be able to take userdata if it's borrowed match userdata.take::() { Err(Error::UserDataBorrowMutError) => {} r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r), } } let value = userdata.take::()?; assert_eq!(*value.0, 18); drop(value); assert_eq!(Arc::strong_count(&rc), 2); match userdata.borrow::() { Err(Error::UserDataDestructed) => {} r => panic!("expected `UserDataDestructed` error, got {:?}", r), } match lua.load("userdata:num()").exec() { Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { Error::UserDataDestructed => {} err => panic!("expected `UserDataDestructed`, got {:?}", err), }, r => panic!("improper return for destructed userdata: {:?}", r), } assert!(!userdata.is::()); drop(userdata); lua.globals().raw_remove("userdata")?; lua.gc_collect()?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&rc), 1); Ok(()) } let lua = Lua::new(); let rc = Arc::new(18); let userdata = lua.create_userdata(MyUserdata(rc.clone()))?; userdata.set_nth_user_value(2, MyUserdata(rc.clone()))?; check_userdata_take(&lua, userdata, rc)?; // Additionally check serializable userdata #[cfg(feature = "serde")] { let rc = Arc::new(18); let userdata = lua.create_ser_userdata(MyUserdata(rc.clone()))?; userdata.set_nth_user_value(2, MyUserdata(rc.clone()))?; check_userdata_take(&lua, userdata, rc)?; } Ok(()) } #[test] fn test_userdata_destroy() -> Result<()> { struct MyUserdata(#[allow(unused)] Arc<()>); impl UserData for MyUserdata { fn add_methods>(methods: &mut M) { methods.add_method("try_destroy", |lua, _this, ()| { let ud = lua.globals().get::("ud")?; match ud.destroy() { Err(Error::UserDataBorrowMutError) => {} r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r), } Ok(()) }); } } let rc = Arc::new(()); let lua = Lua::new(); let ud = lua.create_userdata(MyUserdata(rc.clone()))?; ud.set_user_value(MyUserdata(rc.clone()))?; lua.globals().set("userdata", ud)?; assert_eq!(Arc::strong_count(&rc), 3); // Should destroy all objects lua.globals().raw_remove("userdata")?; lua.gc_collect()?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&rc), 1); let ud = lua.create_userdata(MyUserdata(rc.clone()))?; assert_eq!(Arc::strong_count(&rc), 2); let ud_ref = ud.borrow::()?; // With active `UserDataRef` this methods only marks userdata as destructed // without running destructor ud.destroy().unwrap(); assert_eq!(Arc::strong_count(&rc), 2); drop(ud_ref); assert_eq!(Arc::strong_count(&rc), 1); // We cannot destroy (internally) borrowed userdata let ud = lua.create_userdata(MyUserdata(rc.clone()))?; lua.globals().set("ud", &ud)?; lua.load("ud:try_destroy()").exec().unwrap(); ud.destroy().unwrap(); assert_eq!(Arc::strong_count(&rc), 1); Ok(()) } #[test] fn test_userdata_method_once() -> Result<()> { struct MyUserdata(Arc); impl UserData for MyUserdata { fn add_methods>(methods: &mut M) { methods.add_method_once("take_value", |_, this, ()| Ok(*this.0)); } } let lua = Lua::new(); let rc = Arc::new(42); let userdata = lua.create_userdata(MyUserdata(rc.clone()))?; lua.globals().set("userdata", &userdata)?; // Control userdata let userdata2 = lua.create_userdata(MyUserdata(rc.clone()))?; lua.globals().set("userdata2", userdata2)?; assert_eq!(lua.load("userdata:take_value()").eval::()?, 42); match lua.load("userdata2.take_value(userdata)").eval::() { Err(Error::CallbackError { cause, .. }) => { let err = cause.to_string(); assert!(err.contains("bad argument `self` to `MyUserdata.take_value`")); assert!(err.contains("userdata has been destructed")); } r => panic!("expected Err(CallbackError), got {r:?}"), } assert_eq!(Arc::strong_count(&rc), 2); Ok(()) } #[test] fn test_user_values() -> Result<()> { struct MyUserData; impl UserData for MyUserData {} let lua = Lua::new(); let ud = lua.create_userdata(MyUserData)?; ud.set_nth_user_value(1, "hello")?; ud.set_nth_user_value(2, "world")?; ud.set_nth_user_value(65535, 321)?; assert_eq!(ud.nth_user_value::(1)?, "hello"); assert_eq!(ud.nth_user_value::(2)?, "world"); assert_eq!(ud.nth_user_value::(3)?, Value::Nil); assert_eq!(ud.nth_user_value::(65535)?, 321); assert!(ud.nth_user_value::(0).is_err()); assert!(ud.nth_user_value::(65536).is_err()); // Named user values let ud = lua.create_userdata(MyUserData)?; ud.set_named_user_value("name", "alex")?; ud.set_named_user_value("age", 10)?; assert_eq!(ud.named_user_value::("name")?, "alex"); assert_eq!(ud.named_user_value::("age")?, 10); assert_eq!(ud.named_user_value::("nonexist")?, Value::Nil); Ok(()) } #[test] fn test_functions() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { fn add_methods>(methods: &mut M) { methods.add_function("get_value", |_, ud: AnyUserData| Ok(ud.borrow::()?.0)); methods.add_function_mut("set_value", |_, (ud, value): (AnyUserData, i64)| { ud.borrow_mut::()?.0 = value; Ok(()) }); methods.add_function("get_constant", |_, ()| Ok(7)); } } let lua = Lua::new(); let globals = lua.globals(); let userdata = lua.create_userdata(MyUserData(42))?; globals.set("userdata", &userdata)?; lua.load( r#" function get_it() return userdata:get_value() end function set_it(i) return userdata:set_value(i) end function get_constant() return userdata.get_constant() end "#, ) .exec()?; let get = globals.get::("get_it")?; let set = globals.get::("set_it")?; let get_constant = globals.get::("get_constant")?; assert_eq!(get.call::(())?, 42); userdata.borrow_mut::()?.0 = 64; assert_eq!(get.call::(())?, 64); set.call::<()>(100)?; assert_eq!(get.call::(())?, 100); assert_eq!(get_constant.call::(())?, 7); Ok(()) } #[test] fn test_fields() -> Result<()> { let lua = Lua::new(); let globals = lua.globals(); #[derive(Copy, Clone)] struct MyUserData(i64); impl UserData for MyUserData { fn add_fields>(fields: &mut F) { fields.add_field("static", "constant"); fields.add_field_method_get("val", |_, data| Ok(data.0)); fields.add_field_method_set("val", |_, data, val| { data.0 = val; Ok(()) }); // Field that emulates method fields.add_field_function_get("val_fget", |lua, ud| { lua.create_function(move |_, ()| Ok(ud.borrow::()?.0)) }); // Use userdata "uservalue" storage fields.add_field_function_get("uval", |_, ud| ud.user_value::>()); fields.add_field_function_set("uval", |_, ud, s: Option| ud.set_user_value(s)); fields.add_meta_field(MetaMethod::Index, HashMap::from([("f", 321)])); fields.add_meta_field_with(MetaMethod::NewIndex, |lua| { lua.create_function(|lua, (_, field, val): (AnyUserData, String, Value)| { lua.globals().set(field, val)?; Ok(()) }) }) } fn add_methods>(methods: &mut M) { methods.add_method("dummy", |_, _, ()| Ok(())); } } globals.set("ud", MyUserData(7))?; lua.load( r#" assert(ud.static == "constant") assert(ud.val == 7) ud.val = 10 assert(ud.val == 10) assert(ud:val_fget() == 10) assert(ud.uval == nil) ud.uval = "hello" assert(ud.uval == "hello") assert(ud.f == 321) ud.unknown = 789 assert(unknown == 789) "#, ) .exec()?; // Case: fields + __index metamethod (function) struct MyUserData2(i64); impl UserData for MyUserData2 { fn add_fields>(fields: &mut F) { fields.add_field("z", 0); fields.add_field_method_get("x", |_, data| Ok(data.0)); } fn add_methods>(methods: &mut M) { methods.add_meta_method(MetaMethod::Index, |_, _, name: LuaString| { match name.to_str()?.as_ref() { "y" => Ok(Some(-1)), _ => Ok(None), } }); } } globals.set("ud", MyUserData2(1))?; lua.load( r#" assert(ud.x == 1) assert(ud.y == -1) assert(ud.z == 0) "#, ) .exec()?; Ok(()) } #[test] fn test_metatable() -> Result<()> { #[derive(Copy, Clone)] struct MyUserData; impl UserData for MyUserData { fn add_methods>(methods: &mut M) { methods.add_function("my_type_name", |_, data: AnyUserData| { let metatable = data.metatable()?; metatable.get::(MetaMethod::Type) }); } } let lua = Lua::new(); let globals = lua.globals(); globals.set("ud", MyUserData)?; lua.load(r#"assert(ud:my_type_name() == "MyUserData")"#).exec()?; #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))] lua.load(r#"assert(tostring(ud):sub(1, 11) == "MyUserData:")"#) .exec()?; #[cfg(feature = "luau")] lua.load(r#"assert(typeof(ud) == "MyUserData")"#).exec()?; let ud: AnyUserData = globals.get("ud")?; let metatable = ud.metatable()?; match metatable.get::("__gc") { Ok(_) => panic!("expected MetaMethodRestricted, got no error"), Err(Error::MetaMethodRestricted(_)) => {} Err(e) => panic!("expected MetaMethodRestricted, got {:?}", e), } match metatable.set(MetaMethod::Index, Nil) { Ok(_) => panic!("expected MetaMethodRestricted, got no error"), Err(Error::MetaMethodRestricted(_)) => {} Err(e) => panic!("expected MetaMethodRestricted, got {:?}", e), } let mut methods = metatable .pairs() .map(|kv: Result<(_, Value)>| Ok(kv?.0)) .collect::>>()?; methods.sort(); assert_eq!(methods, vec!["__index", MetaMethod::Type.name()]); #[derive(Copy, Clone)] struct MyUserData2; impl UserData for MyUserData2 { fn add_fields>(fields: &mut F) { fields.add_meta_field_with("__index", |_| Ok(1)); } } match lua.create_userdata(MyUserData2) { Ok(_) => panic!("expected MetaMethodTypeError, got no error"), Err(Error::MetaMethodTypeError { .. }) => {} Err(e) => panic!("expected MetaMethodTypeError, got {:?}", e), } #[derive(Copy, Clone)] struct MyUserData3; impl UserData for MyUserData3 { fn add_fields>(fields: &mut F) { fields.add_meta_field_with(MetaMethod::Type, |_| Ok("CustomName")); } } let ud = lua.create_userdata(MyUserData3)?; let metatable = ud.metatable()?; assert_eq!( metatable.get::(MetaMethod::Type)?.to_str()?, "CustomName" ); Ok(()) } #[test] fn test_userdata_proxy() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { fn add_fields>(fields: &mut F) { fields.add_field("static_field", 123); fields.add_field_method_get("n", |_, this| Ok(this.0)); } fn add_methods>(methods: &mut M) { methods.add_function("new", |_, n| Ok(Self(n))); methods.add_method("plus", |_, this, n: i64| Ok(this.0 + n)); } } let lua = Lua::new(); let globals = lua.globals(); globals.set("MyUserData", lua.create_proxy::()?)?; assert!(!globals.get::("MyUserData")?.is_proxy::<()>()); assert!(globals.get::("MyUserData")?.is_proxy::()); lua.load( r#" assert(MyUserData.static_field == 123) local data = MyUserData.new(321) assert(data.static_field == 123) assert(data.n == 321) assert(data:plus(1) == 322) -- Error when accessing the proxy object fields and methods that require instance local ok = pcall(function() return MyUserData.n end) assert(not ok) ok = pcall(function() return MyUserData:plus(1) end) assert(not ok) "#, ) .exec() } #[test] fn test_any_userdata() -> Result<()> { let lua = Lua::new(); lua.register_userdata_type::(|reg| { reg.add_method("get", |_, this, ()| Ok(this.clone())); reg.add_method_mut("concat", |_, this, s: LuaString| { this.push_str(&s.to_string_lossy()); Ok(()) }); })?; let ud = lua.create_any_userdata("hello".to_string())?; assert_eq!(&*ud.borrow::()?, "hello"); lua.globals().set("ud", ud)?; lua.load( r#" assert(ud:get() == "hello") ud:concat(", world") assert(ud:get() == "hello, world") "#, ) .exec() .unwrap(); Ok(()) } #[test] fn test_any_userdata_wrap() -> Result<()> { let lua = Lua::new(); lua.register_userdata_type::(|reg| { reg.add_method("get", |_, this, ()| Ok(this.clone())); })?; lua.globals().set("s", AnyUserData::wrap("hello".to_string()))?; lua.load( r#" assert(s:get() == "hello") "#, ) .exec() .unwrap(); Ok(()) } #[test] fn test_userdata_object_like() -> Result<()> { let lua = Lua::new(); #[derive(Clone, Copy)] struct MyUserData(u32); impl UserData for MyUserData { fn add_fields>(fields: &mut F) { fields.add_field_method_get("n", |_, this| Ok(this.0)); fields.add_field_method_set("n", |_, this, val| { this.0 = val; Ok(()) }); } fn add_methods>(methods: &mut M) { methods.add_meta_method(MetaMethod::Call, |_, _this, ()| Ok("called")); methods.add_method_mut("add", |_, this, x: u32| { this.0 += x; Ok(()) }); } } let ud = lua.create_userdata(MyUserData(123))?; assert_eq!(ud.get::("n")?, 123); ud.set("n", 321)?; assert_eq!(ud.get::("n")?, 321); assert_eq!(ud.get::>("non-existent")?, None); match ud.set("non-existent", 123) { Err(Error::RuntimeError(_)) => {} r => panic!("expected RuntimeError, got {r:?}"), } assert_eq!(ud.call::(())?, "called"); ud.call_method::<()>("add", 2)?; assert_eq!(ud.get::("n")?, 323); match ud.call_method::<()>("non_existent", ()) { Err(Error::RuntimeError(err)) => { assert!(err.contains("attempt to call a nil value (function 'non_existent')")) } r => panic!("expected RuntimeError, got {r:?}"), } assert!(ud.to_string()?.starts_with("MyUserData")); Ok(()) } #[test] fn test_userdata_method_errors() -> Result<()> { struct MyUserData(i64); impl UserData for MyUserData { fn add_methods>(methods: &mut M) { methods.add_method("get_value", |_, data, ()| Ok(data.0)); } } let lua = Lua::new(); let ud = lua.create_userdata(MyUserData(123))?; let res = ud.call_function::<()>("get_value", "not a userdata"); match res { Err(Error::CallbackError { cause, .. }) => match cause.as_ref() { Error::BadArgument { to, name, cause: cause2, .. } => { assert_eq!(to.as_deref(), Some("MyUserData.get_value")); assert_eq!(name.as_deref(), Some("self")); assert_eq!( cause2.to_string(), "error converting Lua string to userdata (expected userdata of type 'MyUserData')" ); } err => panic!("expected BadArgument, got {err:?}"), }, r => panic!("expected CallbackError, got {r:?}"), } Ok(()) } #[test] fn test_userdata_pointer() -> Result<()> { let lua = Lua::new(); let ud1 = lua.create_any_userdata("hello")?; let ud2 = lua.create_any_userdata("hello")?; assert_eq!(ud1.to_pointer(), ud1.clone().to_pointer()); // Different userdata objects with the same value should have different pointers assert_ne!(ud1.to_pointer(), ud2.to_pointer()); Ok(()) } #[cfg(feature = "macros")] #[test] fn test_userdata_derive() -> Result<()> { let lua = Lua::new(); // Simple struct #[derive(Clone, Copy, mlua::FromLua)] struct MyUserData(i32); lua.register_userdata_type::(|reg| { reg.add_function("val", |_, this: MyUserData| Ok(this.0)); })?; lua.globals().set("ud", AnyUserData::wrap(MyUserData(123)))?; lua.load("assert(ud:val() == 123)").exec()?; // More complex struct where generics and where clause #[derive(Clone, Copy, mlua::FromLua)] struct MyUserData2<'a, T: ?Sized>(&'a T) where T: Copy; lua.register_userdata_type::>(|reg| { reg.add_function("val", |_, this: MyUserData2<'static, i32>| Ok(*this.0)); })?; lua.globals().set("ud", AnyUserData::wrap(MyUserData2(&321)))?; lua.load("assert(ud:val() == 321)").exec()?; Ok(()) } #[test] fn test_nested_userdata_gc() -> Result<()> { let lua = Lua::new(); let counter = Arc::new(()); let arr = vec![lua.create_any_userdata(counter.clone())?]; let arr_ud = lua.create_any_userdata(arr)?; assert_eq!(Arc::strong_count(&counter), 2); drop(arr_ud); // On first iteration Lua will destroy the array, on second - userdata lua.gc_collect()?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&counter), 1); Ok(()) } #[cfg(feature = "userdata-wrappers")] #[test] fn test_userdata_wrappers() -> Result<()> { #[derive(Debug)] struct MyUserData(i64); impl UserData for MyUserData { fn add_fields>(fields: &mut F) { fields.add_field("static", "constant"); fields.add_field_method_get("data", |_, this| Ok(this.0)); fields.add_field_method_set("data", |_, this, val| { this.0 = val; Ok(()) }) } fn add_methods>(methods: &mut M) { methods.add_method("dbg", |_, this, ()| Ok(format!("{this:?}"))); } } let lua = Lua::new(); let globals = lua.globals(); // Rc #[cfg(not(feature = "send"))] { use std::rc::Rc; let ud = Rc::new(MyUserData(1)); globals.set("ud", ud.clone())?; lua.load( r#" assert(ud.static == "constant") local ok, err = pcall(function() ud.data = 2 end) assert( tostring(err):find("error mutably borrowing userdata") ~= nil, "expected 'error mutably borrowing userdata', got '" .. tostring(err) .. "'" ) assert(ud.data == 1) assert(ud:dbg(), "MyUserData(1)") "#, ) .exec() .unwrap(); // Test borrowing original userdata { let ud = globals.get::("ud")?; assert!(ud.is::>()); assert!(!ud.is::()); assert_eq!(ud.borrow::()?.0, 1); assert!(matches!( ud.borrow_mut::(), Err(Error::UserDataBorrowMutError) )); assert!(ud.borrow_mut::>().is_ok()); assert_eq!(ud.borrow_scoped::(|x| x.0)?, 1); assert!(matches!( ud.borrow_mut_scoped::(|_| ()), Err(Error::UserDataBorrowMutError) )); } // Collect userdata globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Rc::strong_count(&ud), 1); // We must be able to mutate userdata when having one reference only globals.set("ud", ud)?; lua.load( r#" ud.data = 2 assert(ud.data == 2) "#, ) .exec() .unwrap(); } // Rc> #[cfg(not(feature = "send"))] { use std::cell::RefCell; use std::rc::Rc; let ud = Rc::new(RefCell::new(MyUserData(2))); globals.set("ud", ud.clone())?; lua.load( r#" assert(ud.static == "constant") assert(ud.data == 2) ud.data = 10 assert(ud.data == 10) assert(ud:dbg() == "MyUserData(10)") "#, ) .exec() .unwrap(); // Test borrowing original userdata { let ud = globals.get::("ud")?; assert!(ud.is::>>()); assert!(!ud.is::()); assert_eq!(ud.borrow::()?.0, 10); assert_eq!(ud.borrow_mut::()?.0, 10); ud.borrow_mut::()?.0 = 20; assert_eq!(ud.borrow::()?.0, 20); assert_eq!(ud.borrow_scoped::(|x| x.0)?, 20); ud.borrow_mut_scoped::(|x| x.0 = 30)?; assert_eq!(ud.borrow::()?.0, 30); // Double (read) borrow is okay let _borrow = ud.borrow::()?; assert_eq!(ud.borrow::()?.0, 30); assert!(matches!( ud.borrow_mut::(), Err(Error::UserDataBorrowMutError) )); } // Collect userdata globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Rc::strong_count(&ud), 1); // Check destroying wrapped UserDataRef without references in Lua let ud = lua.convert::>(ud)?; lua.gc_collect()?; assert_eq!(ud.0, 30); drop(ud); } // Arc { let ud = Arc::new(MyUserData(3)); globals.set("ud", ud.clone())?; lua.load( r#" assert(ud.static == "constant") local ok, err = pcall(function() ud.data = 4 end) assert( tostring(err):find("error mutably borrowing userdata") ~= nil, "expected 'error mutably borrowing userdata', got '" .. tostring(err) .. "'" ) assert(ud.data == 3) assert(ud:dbg() == "MyUserData(3)") "#, ) .exec() .unwrap(); // Test borrowing original userdata { let ud = globals.get::("ud")?; assert!(ud.is::>()); assert!(!ud.is::()); assert_eq!(ud.borrow::()?.0, 3); assert!(matches!( ud.borrow_mut::(), Err(Error::UserDataBorrowMutError) )); assert!(ud.borrow_mut::>().is_ok()); assert_eq!(ud.borrow_scoped::(|x| x.0)?, 3); assert!(matches!( ud.borrow_mut_scoped::(|_| ()), Err(Error::UserDataBorrowMutError) )); } // Collect userdata globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud), 1); // We must be able to mutate userdata when having one reference only globals.set("ud", ud)?; lua.load( r#" ud.data = 4 assert(ud.data == 4) "#, ) .exec() .unwrap(); } // Arc> { use std::sync::Mutex; let ud = Arc::new(Mutex::new(MyUserData(5))); globals.set("ud", ud.clone())?; lua.load( r#" assert(ud.static == "constant") assert(ud.data == 5) ud.data = 6 assert(ud.data == 6) assert(ud:dbg() == "MyUserData(6)") "#, ) .exec() .unwrap(); // Test borrowing original userdata { let ud = globals.get::("ud")?; assert!(ud.is::>>()); assert!(!ud.is::()); #[rustfmt::skip] assert!(matches!(ud.borrow::(), Err(Error::UserDataTypeMismatch))); #[rustfmt::skip] assert!(matches!(ud.borrow_mut::(), Err(Error::UserDataTypeMismatch))); assert_eq!(ud.borrow_scoped::(|x| x.0)?, 6); ud.borrow_mut_scoped::(|x| x.0 = 8)?; assert_eq!(ud.borrow_scoped::(|x| x.0)?, 8); } // Collect userdata globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud), 1); } // Arc> { use std::sync::RwLock; let ud = Arc::new(RwLock::new(MyUserData(9))); globals.set("ud", ud.clone())?; lua.load( r#" assert(ud.static == "constant") assert(ud.data == 9) ud.data = 10 assert(ud.data == 10) assert(ud:dbg() == "MyUserData(10)") "#, ) .exec() .unwrap(); // Test borrowing original userdata { let ud = globals.get::("ud")?; assert!(ud.is::>>()); assert!(!ud.is::()); #[rustfmt::skip] assert!(matches!(ud.borrow::(), Err(Error::UserDataTypeMismatch))); #[rustfmt::skip] assert!(matches!(ud.borrow_mut::(), Err(Error::UserDataTypeMismatch))); assert_eq!(ud.borrow_scoped::(|x| x.0)?, 10); ud.borrow_mut_scoped::(|x| x.0 = 12)?; assert_eq!(ud.borrow_scoped::(|x| x.0)?, 12); } // Collect userdata globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud), 1); } // Arc> { use parking_lot::Mutex; let ud = Arc::new(Mutex::new(MyUserData(13))); globals.set("ud", ud.clone())?; lua.load( r#" assert(ud.static == "constant") assert(ud.data == 13) ud.data = 14 assert(ud.data == 14) assert(ud:dbg() == "MyUserData(14)") "#, ) .exec() .unwrap(); // Test borrowing original userdata { let ud = globals.get::("ud")?; assert!(ud.is::>>()); assert!(!ud.is::()); assert_eq!(ud.borrow::()?.0, 14); assert_eq!(ud.borrow_mut::()?.0, 14); ud.borrow_mut::()?.0 = 15; assert_eq!(ud.borrow::()?.0, 15); assert_eq!(ud.borrow_scoped::(|x| x.0)?, 15); ud.borrow_mut_scoped::(|x| x.0 = 16)?; assert_eq!(ud.borrow::()?.0, 16); // Double borrow is not allowed let _borrow = ud.borrow::()?; assert!(matches!( ud.borrow::(), Err(Error::UserDataBorrowError) )); } // Collect userdata globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud), 1); // Check destroying wrapped UserDataRef without references in Lua let ud = lua.convert::>(ud)?; lua.gc_collect()?; assert_eq!(ud.0, 16); drop(ud); } // Arc> { use parking_lot::RwLock; let ud = Arc::new(RwLock::new(MyUserData(17))); globals.set("ud", ud.clone())?; lua.load( r#" assert(ud.static == "constant") assert(ud.data == 17) ud.data = 18 assert(ud.data == 18) assert(ud:dbg() == "MyUserData(18)") "#, ) .exec() .unwrap(); // Test borrowing original userdata { let ud = globals.get::("ud")?; assert!(ud.is::>>()); assert!(!ud.is::()); assert_eq!(ud.borrow::()?.0, 18); assert_eq!(ud.borrow_mut::()?.0, 18); ud.borrow_mut::()?.0 = 19; assert_eq!(ud.borrow::()?.0, 19); assert_eq!(ud.borrow_scoped::(|x| x.0)?, 19); ud.borrow_mut_scoped::(|x| x.0 = 20)?; assert_eq!(ud.borrow::()?.0, 20); // Multiple read borrows are allowed with parking_lot::RwLock let _borrow1 = ud.borrow::().unwrap(); // FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634 // let _borrow2 = ud.borrow::().unwrap(); assert!(matches!( ud.borrow_mut::(), Err(Error::UserDataBorrowMutError) )); } // Collect userdata globals.set("ud", Nil)?; lua.gc_collect()?; assert_eq!(Arc::strong_count(&ud), 1); // Check destroying wrapped UserDataRef without references in Lua let ud = lua.convert::>(ud)?; lua.gc_collect()?; assert_eq!(ud.0, 20); drop(ud); } Ok(()) } #[cfg(feature = "luau")] #[test] fn test_userdata_namecall() -> Result<()> { let lua = Lua::new(); struct MyUserData; impl UserData for MyUserData { fn register(registry: &mut mlua::UserDataRegistry) { registry.add_method("method", |_, _, ()| Ok("method called")); registry.add_field_method_get("field", |_, _| Ok("field value")); registry.add_meta_method(MetaMethod::Index, |_, _, key: LuaString| Ok(key)); registry.enable_namecall(); } } let ud = lua.create_userdata(MyUserData)?; lua.globals().set("ud", &ud)?; lua.load( r#" assert(ud:method() == "method called") assert(ud.field == "field value") assert(ud.dynamic_field == "dynamic_field") local ok, err = pcall(function() return ud:dynamic_field() end) assert(tostring(err):find("attempt to call an unknown method 'dynamic_field'") ~= nil) "#, ) .exec()?; ud.destroy()?; let err = lua.load("ud:method()").exec().unwrap_err(); assert!(err.to_string().contains("userdata has been destructed")); Ok(()) } #[test] fn test_userdata_get_path() -> Result<()> { let lua = Lua::new(); struct MyUd; impl UserData for MyUd { fn register(registry: &mut UserDataRegistry) { registry.add_field("value", "userdata_value"); } } let ud = lua.create_userdata(MyUd)?; assert_eq!(ud.get_path::(".value")?, "userdata_value"); Ok(()) }