Skip to main content

low_luajit/lua/type/
userdata.rs

1use core::ffi::CStr;
2use crate::{AmountReturned, LuaState, ffi};
3
4/// Userdata (full & light) related functions.
5impl LuaState {
6	/// Returns `true` if the value at the given acceptable `index` is userdata (either full or light), `false` otherwise.
7	#[inline]
8	#[must_use]
9	pub fn is_userdata(&self, index: i32) -> bool {
10		// SAFETY: self.0 is always valid.
11		unsafe { ffi::lua_isuserdata(self.0, index) != 0 }
12	}
13
14	/// Returns `true` if the value at the given acceptable `index` is full userdata, `false` otherwise.
15	#[inline]
16	#[must_use]
17	pub fn is_full_userdata(&self, index: i32) -> bool {
18		self.is_userdata(index) && !self.is_light_userdata(index)
19	}
20
21	/// Returns `true` if the value at the given acceptable `index` is light userdata, `false` otherwise.
22	#[inline]
23	#[must_use]
24	pub fn is_light_userdata(&self, index: i32) -> bool {
25		// SAFETY: self.0 is always valid.
26		unsafe { ffi::lua_islightuserdata(self.0, index) != 0 }
27	}
28
29	/// Converts the value at the given acceptable `index` to full userdata and returns a mutable reference reference to its block.
30	///
31	/// # Safety
32	///
33	/// - The type of the element in the stack must be full userdata.
34	/// - `T` must be the same type used when creating this userdata.
35	/// - The userdata's value must be valid for `T`.
36	/// - There must not be another living reference to this userdata.
37	#[inline]
38	#[must_use]
39	#[allow(clippy::mut_from_ref)]
40	pub unsafe fn to_full_userdata<T>(&self, index: i32) -> &mut T {
41		// SAFETY: self.0 is always valid and the caller must uphold the safety conditions.
42		unsafe { &mut *ffi::lua_touserdata(self.0, index).cast() }
43	}
44
45	/// Converts the value at the given acceptable `index` to light userdata and returns it.
46	///
47	/// Only light and full userdata are accepted, anything else will return a null pointer.
48	#[inline]
49	#[must_use]
50	pub fn to_light_userdata<T>(&self, index: i32) -> *mut T {
51		// SAFETY: self.0 is always valid.
52		unsafe { ffi::lua_touserdata(self.0, index).cast() }
53	}
54
55	/// Allocates a new block the size of `T` containing `value`, pushes onto the stack a new full userdata with the block's address,
56	/// and then returns a mutable reference to said block.
57	///
58	/// Like any other Lua value, this userdata is inevitably collected by the garbage collector to be freed;
59	/// but by default this is done by simply deallocating the block instead of properly dropping the value!
60	///
61	/// *(This won't result in undefined behavior,
62	/// but could cause a memory leak for types that allocate dynamically or access some sort of external resource;
63	/// more primitive types should not need to be properly dropped.)*
64	///
65	/// This can be changed by having the `__gc` metamethod (which is triggered before the block is freed) drop the userdata.
66	/// For a simple way to do this, see [`LuaState::set_drop_gc_metamethod`].
67	#[inline]
68	pub fn new_full_userdata<T>(&mut self, value: T) -> &mut T {
69		// SAFETY: self.0 is always valid and lua_newuserdata should always return a pointer that is valid, aligned and not accessed anywhere else.
70		unsafe {
71			let ptr = ffi::lua_newuserdata(self.0, size_of::<T>()).cast::<T>();
72			ptr.write(value);
73			&mut *ptr
74		}
75	}
76
77	/// Behaves exactly as [`LuaState::new_full_userdata`], but uses `T`'s default value as the block's value.
78	#[inline]
79	pub fn new_full_userdata_default<T: Default>(&mut self) -> &mut T {
80		self.new_full_userdata(T::default())
81	}
82
83	/// Pushes the given `pointer` onto the stack as *light* userdata.
84	///
85	/// Unlike normal *full* userdata, light userdata is the raw pointer itself.
86	#[inline]
87	pub fn push_light_userdata<T>(&mut self, pointer: *mut T) {
88		// SAFETY: self.0 is always valid and pointer is simply stored, so it doesn't have to be valid.
89		unsafe { ffi::lua_pushlightuserdata(self.0, pointer.cast()); }
90	}
91
92	/// Checks if there's an userdata (either full or light) of type `userdata_type` at the given index `arg_n` and returns it as full userdata.
93	///
94	/// Userdata is of type `userdata_type` when it uses the metatable stored in the registry with key `userdata_type`
95	/// (see [`LuaState::new_userdata_metatable`]).
96	///
97	/// If the check fails, [`LuaState::type_error`] is called using `arg_n`.
98	///
99	/// # Safety
100	///
101	/// - `T` must be the same type used when creating this userdata.
102	/// - The userdata's value must be valid for `T`.
103	/// - There must not be another living reference to this userdata.
104	///
105	/// # Lua Errors
106	///
107	/// Intentionally errors if the check fails.
108	#[inline]
109	#[must_use]
110	#[allow(clippy::mut_from_ref)]
111	pub unsafe fn check_full_userdata<T>(&self, n_arg: i32, userdata_type: &CStr) -> &mut T {
112		unsafe { &mut *ffi::luaL_checkudata(self.0, n_arg, userdata_type.as_ptr()).cast() }
113	}
114
115	/// Checks if there's an userdata (either full or light) of type `userdata_type` at the given index `arg_n` and returns it as light userdata.
116	///
117	/// Userdata is of type `userdata_type` when it uses the metatable stored in the registry with key `userdata_type`
118	/// (see [`LuaState::new_userdata_metatable`]).
119	///
120	/// If the check fails, [`LuaState::type_error`] is called using `arg_n`.
121	///
122	/// # Lua Errors
123	///
124	/// Intentionally errors if the check fails.
125	#[inline]
126	#[must_use]
127	pub fn check_light_userdata<T>(&self, n_arg: i32, userdata_type: &CStr) -> *mut T {
128		unsafe { ffi::luaL_checkudata(self.0, n_arg, userdata_type.as_ptr()).cast() }
129	}
130
131	/// Finishes the currently called [`CFunction`], returning the given `pointer` as light userdata to the caller.
132	///
133	/// The `LuaState` wrapper is consumed to prevent invalidating the `AmountReturned` (the Lua state itself is left intact).
134	///
135	/// [`CFunction`]: ./type.CFunction.html
136	#[inline]
137	pub fn return_light_userdata<T>(mut self, pointer: *mut T) -> AmountReturned {
138		self.push_light_userdata(pointer);
139
140		// SAFETY: There should always be at least one element in the stack.
141		unsafe { AmountReturned::unchecked(1) }
142	}
143
144	/// Pushes onto the stack the value in the registry with key `userdata_type`, returning `false`.
145	///
146	/// If no such value exists yet, a table will be set as the value (before being pushed) and `true` will be returned instead.
147	///
148	/// This function is used to get the metatable to be set as the metatable of the desired userdata (see [`LuaState::set_metatable`]).
149	///
150	/// # Examples
151	///
152	/// ```
153	/// use low_luajit::{LuaState, AmountReturned};
154	///
155	/// extern "C-unwind" fn index_metamethod(lua: LuaState) -> AmountReturned {
156	///     // ...
157	/// # AmountReturned::NONE
158	/// }
159	///
160	/// extern "C-unwind" fn my_light_userdata_constructor(mut lua: LuaState) -> AmountReturned {
161	/// # let ptr = core::ptr::null_mut::<()>();
162	///     // ...
163	///
164	///     // Construct the light userdata.
165	///     lua.push_light_userdata(ptr);
166	///     if lua.new_userdata_metatable(c"my_light_userdata") {
167	///         // Initialize the metatable.
168	///         lua.push_cfunction(index_metamethod);
169	///         lua.set_field(-2, c"__index");
170	///         // ...
171	///     }
172	///     lua.set_metatable(-2);
173	///
174	///     lua.return_any(1)
175	/// }
176	/// ```
177	#[inline]
178	pub fn new_userdata_metatable(&mut self, userdata_type: &CStr) -> bool {
179		unsafe { ffi::luaL_newmetatable(self.0, userdata_type.as_ptr()) != 0 }
180	}
181
182	/// Sets the `__gc` metamethod of the metatable at the top of the stack to a special destructor metamethod.
183	///
184	/// This metamethod will drop the full userdata (that is using the metatable) before the garbage collector frees the userdata's block.
185	///
186	/// This function is usually only necessary when the userdata's type implements `Drop`,
187	/// see [`LuaState::new_full_userdata`] for more information.
188	///
189	/// # Safety
190	///
191	/// - The type of the value that uses the metatable must be full userdata.
192	/// - `T` must be the same type used when creating this userdata.
193	/// - The userdata's value must be valid for `T`.
194	///
195	/// These conditions should always be true when [`LuaState::new_full_userdata`] and [`LuaState::new_userdata_metatable`] are used properly.
196	///
197	/// # Lua Errors
198	///
199	/// Not every top element can be indexed: for example, if `t` is `nil` this function will error.
200	///
201	/// Indexing the top element may also trigger the `__newindex` metamethod which could error.
202	///
203	/// # Examples
204	///
205	/// ```
206	/// # use low_luajit::{LuaState, AmountReturned};
207	/// # extern "C-unwind" fn example(mut lua: LuaState) -> AmountReturned {
208	/// lua.new_full_userdata(vec![36.2, 123.456, 0.998]);
209	/// if lua.new_userdata_metatable(c"my_full_userdata") {
210	///     // SAFETY: The metatable is being used by a valid full userdata of the same type.
211	///     unsafe { lua.set_drop_gc_metamethod::<Vec<f64>>() }
212	/// }
213	/// lua.set_metatable(-2);
214	/// # AmountReturned::NONE }
215	/// ```
216	///
217	/// *(For a more complete example on how to implement metatables for userdata, see [`LuaState::new_userdata_metatable`].)*
218	#[inline]
219	pub unsafe fn set_drop_gc_metamethod<T>(&mut self) {
220		extern "C-unwind" fn drop_userdata<T>(lua: LuaState) -> AmountReturned {
221			// SAFETY: The caller must uphold the safety conditions.
222			unsafe { lua.to_light_userdata::<T>(-1).drop_in_place() };
223			AmountReturned::NONE
224		}
225
226		self.push_cfunction(drop_userdata::<T>);
227		self.set_field(-2, c"__gc");
228	}
229}