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}