Skip to content

Commit 201e30b

Browse files
committed
Add UserDataOwned<T> wrapper to take ownership of userdata T
It implements `FromLua` and takes ownership of a Lua userdata value. The semantics is similar to `AnyUserData::take`, preventing any further use from Lua. Closes #686
1 parent 4e028d8 commit 201e30b

5 files changed

Lines changed: 116 additions & 6 deletions

File tree

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ pub use crate::table::{TablePairs, TableSequence};
142142
pub use crate::thread::ThreadStatus;
143143
#[doc(hidden)]
144144
pub use crate::userdata::{
145-
MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, UserDataRefMut,
146-
UserDataRegistry,
145+
MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataOwned, UserDataRef,
146+
UserDataRefMut, UserDataRegistry,
147147
};
148148

149149
#[cfg(not(feature = "luau"))]

src/prelude.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub use crate::{
99
ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib,
1010
Table as LuaTable, Thread as LuaThread, UserData as LuaUserData, UserDataFields as LuaUserDataFields,
1111
UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods,
12-
UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut,
12+
UserDataOwned as LuaUserDataOwned, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut,
1313
UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic,
1414
VmState as LuaVmState, WeakLua, chunk::AsChunk as AsLuaChunk, chunk::Chunk as LuaChunk,
1515
chunk::ChunkMode as LuaChunkMode, error::ErrorContext as LuaErrorContext,

src/userdata.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use {
3030

3131
// Re-export for convenience
3232
pub(crate) use cell::UserDataStorage;
33-
pub use r#ref::{UserDataRef, UserDataRefMut};
33+
pub use r#ref::{UserDataOwned, UserDataRef, UserDataRefMut};
3434
pub use registry::UserDataRegistry;
3535
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
3636
pub(crate) use util::{

src/userdata/ref.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::error::{Error, Result};
77
use crate::state::{Lua, RawLua};
88
use crate::traits::FromLua;
99
use crate::userdata::AnyUserData;
10-
use crate::util::get_userdata;
10+
use crate::util::{check_stack, get_userdata, take_userdata};
1111
use crate::value::Value;
1212

1313
use super::cell::{UserDataStorage, UserDataVariant};
@@ -440,6 +440,66 @@ impl<T> DerefMut for UserDataRefMutInner<T> {
440440
}
441441
}
442442

443+
/// A wrapper type that takes ownership of a userdata value.
444+
///
445+
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua by taking
446+
/// ownership of it.
447+
/// The original Lua userdata is marked as destructed and cannot be used further.
448+
pub struct UserDataOwned<T>(pub T);
449+
450+
impl<T> Deref for UserDataOwned<T> {
451+
type Target = T;
452+
453+
#[inline]
454+
fn deref(&self) -> &T {
455+
&self.0
456+
}
457+
}
458+
459+
impl<T> DerefMut for UserDataOwned<T> {
460+
#[inline]
461+
fn deref_mut(&mut self) -> &mut T {
462+
&mut self.0
463+
}
464+
}
465+
466+
impl<T: fmt::Debug> fmt::Debug for UserDataOwned<T> {
467+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468+
(**self).fmt(f)
469+
}
470+
}
471+
472+
impl<T: fmt::Display> fmt::Display for UserDataOwned<T> {
473+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474+
(**self).fmt(f)
475+
}
476+
}
477+
478+
impl<T: 'static> FromLua for UserDataOwned<T> {
479+
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
480+
try_value_to_userdata::<T>(value)?.take().map(UserDataOwned)
481+
}
482+
483+
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
484+
let state = lua.state();
485+
let type_id = lua.get_userdata_type_id::<T>(state, idx)?;
486+
match type_id {
487+
Some(type_id) if type_id == TypeId::of::<T>() => {
488+
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
489+
if (*ud).has_exclusive_access() {
490+
check_stack(state, 1)?;
491+
take_userdata::<UserDataStorage<T>>(state, idx)
492+
.into_inner()
493+
.map(UserDataOwned)
494+
} else {
495+
Err(Error::UserDataBorrowMutError)
496+
}
497+
}
498+
_ => Err(Error::UserDataTypeMismatch),
499+
}
500+
}
501+
}
502+
443503
#[inline]
444504
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
445505
match value {
@@ -464,6 +524,10 @@ mod assertions {
464524
static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send);
465525
#[cfg(feature = "send")]
466526
static_assertions::assert_not_impl_all!(UserDataRefMut<std::rc::Rc<()>>: Send, Sync);
527+
#[cfg(feature = "send")]
528+
static_assertions::assert_impl_all!(UserDataOwned<()>: Send, Sync);
529+
#[cfg(feature = "send")]
530+
static_assertions::assert_not_impl_all!(UserDataOwned<std::rc::Rc<()>>: Send, Sync);
467531

468532
#[cfg(not(feature = "send"))]
469533
static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync);

tests/userdata.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicI64, Ordering};
77

88
use mlua::{
99
AnyUserData, Error, ExternalError, Function, Lua, LuaString, MetaMethod, Nil, ObjectLike, Result,
10-
UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value, Variadic,
10+
UserData, UserDataFields, UserDataMethods, UserDataOwned, UserDataRef, UserDataRegistry, Value, Variadic,
1111
};
1212

1313
#[test]
@@ -1459,3 +1459,49 @@ fn test_userdata_get_path() -> Result<()> {
14591459

14601460
Ok(())
14611461
}
1462+
1463+
#[test]
1464+
fn test_userdata_owned() -> Result<()> {
1465+
#[derive(Debug)]
1466+
struct MyUserdata(Arc<i64>);
1467+
1468+
impl UserData for MyUserdata {
1469+
fn register(registry: &mut UserDataRegistry<Self>) {
1470+
registry.add_method("num", |_, this, ()| Ok(*this.0));
1471+
}
1472+
}
1473+
1474+
let lua = Lua::new();
1475+
let rc = Arc::new(42);
1476+
1477+
// It takes ownership and destructs the Lua userdata
1478+
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
1479+
assert_eq!(Arc::strong_count(&rc), 2);
1480+
let owned: UserDataOwned<MyUserdata> = lua.convert(&ud)?;
1481+
assert_eq!(*owned.0.0, 42);
1482+
drop(owned);
1483+
assert_eq!(Arc::strong_count(&rc), 1);
1484+
match ud.borrow::<MyUserdata>() {
1485+
Err(Error::UserDataDestructed) => {}
1486+
r => panic!("expected UserDataDestructed, got {:?}", r),
1487+
}
1488+
1489+
// Cannot take while borrowed
1490+
let rc = Arc::new(7);
1491+
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
1492+
let borrowed = ud.borrow::<MyUserdata>()?;
1493+
match lua.convert::<UserDataOwned<MyUserdata>>(&ud) {
1494+
Err(Error::UserDataBorrowMutError) => {}
1495+
r => panic!("expected UserDataBorrowMutError, got {:?}", r),
1496+
}
1497+
drop(borrowed);
1498+
1499+
// Works as a function parameter
1500+
let f = lua.create_function(|_, owned: UserDataOwned<MyUserdata>| Ok(*owned.0.0))?;
1501+
let rc = Arc::new(55);
1502+
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
1503+
assert_eq!(f.call::<i64>(ud)?, 55);
1504+
assert_eq!(Arc::strong_count(&rc), 1); // dropped after call
1505+
1506+
Ok(())
1507+
}

0 commit comments

Comments
 (0)