Skip to main content

mlua/
table.rs

1//! Lua table handling.
2//!
3//! Tables are Lua's primary data structure, used for arrays, dictionaries, objects, modules,
4//! and more. This module provides types for creating and manipulating Lua tables from Rust.
5//!
6//! # Basic Operations
7//!
8//! Tables support key-value access similar to Rust's `HashMap`:
9//!
10//! ```
11//! # use mlua::{Lua, Result};
12//! # fn main() -> Result<()> {
13//! let lua = Lua::new();
14//! let table = lua.create_table()?;
15//!
16//! // Set and get values
17//! table.set("key", "value")?;
18//! let value: String = table.get("key")?;
19//! assert_eq!(value, "value");
20//!
21//! // Keys and values can be any Lua-compatible type
22//! table.set(1, "first")?;
23//! table.set("nested", lua.create_table()?)?;
24//! # Ok(())
25//! # }
26//! ```
27//!
28//! # Array Operations
29//!
30//! Tables can be used as arrays with 1-based indexing:
31//!
32//! ```
33//! # use mlua::{Lua, Result};
34//! # fn main() -> Result<()> {
35//! let lua = Lua::new();
36//! let array = lua.create_table()?;
37//!
38//! // Push values to the end (like Vec::push)
39//! array.push("first")?;
40//! array.push("second")?;
41//! array.push("third")?;
42//!
43//! // Pop from the end
44//! let last: String = array.pop()?;
45//! assert_eq!(last, "third");
46//!
47//! // Get length
48//! assert_eq!(array.raw_len(), 2);
49//! # Ok(())
50//! # }
51//! ```
52//!
53//! # Iteration
54//!
55//! Iterate over all key-value pairs with [`Table::pairs`]:
56//!
57//! ```
58//! # use mlua::{Lua, Result, Value};
59//! # fn main() -> Result<()> {
60//! let lua = Lua::new();
61//! let table = lua.create_table()?;
62//! table.set("a", 1)?;
63//! table.set("b", 2)?;
64//!
65//! for pair in table.pairs::<String, i32>() {
66//!     let (key, value) = pair?;
67//!     println!("{key} = {value}");
68//! }
69//! # Ok(())
70//! # }
71//! ```
72//!
73//! For array portions, use [`Table::sequence_values`]:
74//!
75//! ```
76//! # use mlua::{Lua, Result};
77//! # fn main() -> Result<()> {
78//! let lua = Lua::new();
79//! let array = lua.create_sequence_from(["a", "b", "c"])?;
80//!
81//! for value in array.sequence_values::<String>() {
82//!     println!("{}", value?);
83//! }
84//! # Ok(())
85//! # }
86//! ```
87//!
88//! # Raw vs Normal Access
89//!
90//! Methods prefixed with `raw_` (like [`Table::raw_get`], [`Table::raw_set`]) bypass
91//! metamethods, directly accessing the table's contents. Normal methods may trigger
92//! `__index`, `__newindex`, and other metamethods:
93//!
94//! ```
95//! # use mlua::{Lua, Result};
96//! # fn main() -> Result<()> {
97//! let lua = Lua::new();
98//!
99//! // raw_set bypasses __newindex metamethod
100//! let t = lua.create_table()?;
101//! t.raw_set("key", "value")?;
102//!
103//! // raw_get bypasses __index metamethod
104//! let v: String = t.raw_get("key")?;
105//! # Ok(())
106//! # }
107//! ```
108//!
109//! # Metatables
110//!
111//! Tables can have metatables that customize their behavior:
112//!
113//! ```
114//! # use mlua::{Lua, Result};
115//! # fn main() -> Result<()> {
116//! let lua = Lua::new();
117//!
118//! let table = lua.create_table()?;
119//! let metatable = lua.create_table()?;
120//!
121//! // Set a default value via __index
122//! metatable.set("__index", lua.create_function(|_, _: ()| Ok("default"))?)?;
123//! table.set_metatable(Some(metatable))?;
124//!
125//! // Accessing missing keys returns "default"
126//! let value: String = table.get("missing")?;
127//! assert_eq!(value, "default");
128//! # Ok(())
129//! # }
130//! ```
131//!
132//! # Global Table
133//!
134//! The Lua global environment is itself a table, accessible via [`Lua::globals`]:
135//!
136//! ```
137//! # use mlua::{Lua, Result};
138//! # fn main() -> Result<()> {
139//! let lua = Lua::new();
140//! let globals = lua.globals();
141//!
142//! // Set a global variable
143//! globals.set("my_var", 42)?;
144//!
145//! // Now accessible from Lua code
146//! let result: i32 = lua.load("my_var + 8").eval()?;
147//! assert_eq!(result, 50);
148//! # Ok(())
149//! # }
150//! ```
151//!
152//! [`Lua::globals`]: crate::Lua::globals
153
154use std::collections::HashSet;
155use std::fmt;
156use std::marker::PhantomData;
157use std::os::raw::c_void;
158
159use crate::error::{Error, Result};
160use crate::function::Function;
161use crate::state::{LuaGuard, RawLua, WeakLua};
162use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
163use crate::types::{Integer, ValueRef};
164use crate::util::{StackGuard, assert_stack, check_stack, get_metatable_ptr};
165use crate::value::{Nil, Value};
166
167#[cfg(feature = "async")]
168use crate::function::AsyncCallFuture;
169
170#[cfg(feature = "serde")]
171use {
172    rustc_hash::FxHashSet,
173    serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
174    std::{cell::RefCell, rc::Rc, result::Result as StdResult},
175};
176
177/// Handle to an internal Lua table.
178#[derive(Clone, PartialEq)]
179pub struct Table(pub(crate) ValueRef);
180
181impl Table {
182    /// Sets a key-value pair in the table.
183    ///
184    /// If the value is `nil`, this will effectively remove the pair.
185    ///
186    /// This might invoke the `__newindex` metamethod. Use the [`raw_set`] method if that is not
187    /// desired.
188    ///
189    /// # Examples
190    ///
191    /// Export a value as a global to make it usable from Lua:
192    ///
193    /// ```
194    /// # use mlua::{Lua, Result};
195    /// # fn main() -> Result<()> {
196    /// # let lua = Lua::new();
197    /// let globals = lua.globals();
198    ///
199    /// globals.set("assertions", cfg!(debug_assertions))?;
200    ///
201    /// lua.load(r#"
202    ///     if assertions == true then
203    ///         -- ...
204    ///     elseif assertions == false then
205    ///         -- ...
206    ///     else
207    ///         error("assertions neither on nor off?")
208    ///     end
209    /// "#).exec()?;
210    /// # Ok(())
211    /// # }
212    /// ```
213    ///
214    /// [`raw_set`]: Table::raw_set
215    pub fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
216        // Fast track (skip protected call)
217        if !self.has_metatable() {
218            return self.raw_set(key, value);
219        }
220
221        self.set_protected(key, value)
222    }
223
224    pub(crate) fn set_protected(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
225        let lua = self.0.lua.lock();
226        let state = lua.state();
227        unsafe {
228            let _sg = StackGuard::new(state);
229            check_stack(state, 5)?;
230
231            lua.push_ref(&self.0);
232            key.push_into_stack(&lua)?;
233            value.push_into_stack(&lua)?;
234            protect_lua!(state, 3, 0, fn(state) ffi::lua_settable(state, -3))
235        }
236    }
237
238    /// Gets the value associated to `key` from the table.
239    ///
240    /// If no value is associated to `key`, returns the `nil` value.
241    ///
242    /// This might invoke the `__index` metamethod. Use the [`raw_get`] method if that is not
243    /// desired.
244    ///
245    /// # Examples
246    ///
247    /// Query the version of the Lua interpreter:
248    ///
249    /// ```
250    /// # use mlua::{Lua, Result};
251    /// # fn main() -> Result<()> {
252    /// # let lua = Lua::new();
253    /// let globals = lua.globals();
254    ///
255    /// let version: String = globals.get("_VERSION")?;
256    /// println!("Lua version: {}", version);
257    /// # Ok(())
258    /// # }
259    /// ```
260    ///
261    /// [`raw_get`]: Table::raw_get
262    pub fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
263        // Fast track (skip protected call)
264        if !self.has_metatable() {
265            return self.raw_get(key);
266        }
267
268        self.get_protected(key)
269    }
270
271    pub(crate) fn get_protected<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
272        let lua = self.0.lua.lock();
273        let state = lua.state();
274        unsafe {
275            let _sg = StackGuard::new(state);
276            check_stack(state, 4)?;
277
278            lua.push_ref(&self.0);
279            key.push_into_stack(&lua)?;
280            protect_lua!(state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
281
282            V::from_stack(-1, &lua)
283        }
284    }
285
286    /// Checks whether the table contains a non-nil value for `key`.
287    ///
288    /// This might invoke the `__index` metamethod.
289    pub fn contains_key(&self, key: impl IntoLua) -> Result<bool> {
290        Ok(self.get::<Value>(key)? != Value::Nil)
291    }
292
293    /// Appends a value to the back of the table.
294    ///
295    /// This might invoke the `__len` and `__newindex` metamethods.
296    pub fn push(&self, value: impl IntoLua) -> Result<()> {
297        // Fast track (skip protected call)
298        if !self.has_metatable() {
299            return self.raw_push(value);
300        }
301
302        let lua = self.0.lua.lock();
303        let state = lua.state();
304        unsafe {
305            let _sg = StackGuard::new(state);
306            check_stack(state, 4)?;
307
308            lua.push_ref(&self.0);
309            value.push_into_stack(&lua)?;
310            protect_lua!(state, 2, 0, fn(state) {
311                let len = ffi::luaL_len(state, -2) as Integer;
312                ffi::lua_seti(state, -2, len + 1);
313            })?
314        }
315        Ok(())
316    }
317
318    /// Removes the last element from the table and returns it.
319    ///
320    /// This might invoke the `__len` and `__newindex` metamethods.
321    pub fn pop<V: FromLua>(&self) -> Result<V> {
322        // Fast track (skip protected call)
323        if !self.has_metatable() {
324            return self.raw_pop();
325        }
326
327        let lua = self.0.lua.lock();
328        let state = lua.state();
329        unsafe {
330            let _sg = StackGuard::new(state);
331            check_stack(state, 4)?;
332
333            lua.push_ref(&self.0);
334            protect_lua!(state, 1, 1, fn(state) {
335                let len = ffi::luaL_len(state, -1) as Integer;
336                ffi::lua_geti(state, -1, len);
337                ffi::lua_pushnil(state);
338                ffi::lua_seti(state, -3, len);
339            })?;
340            V::from_stack(-1, &lua)
341        }
342    }
343
344    /// Compares two tables for equality.
345    ///
346    /// Tables are compared by reference first.
347    /// If they are not primitively equals, then mlua will try to invoke the `__eq` metamethod.
348    /// mlua will check `self` first for the metamethod, then `other` if not found.
349    ///
350    /// # Examples
351    ///
352    /// Compare two tables using `__eq` metamethod:
353    ///
354    /// ```
355    /// # use mlua::{Lua, Result, Table};
356    /// # fn main() -> Result<()> {
357    /// # let lua = Lua::new();
358    /// let table1 = lua.create_table()?;
359    /// table1.set(1, "value")?;
360    ///
361    /// let table2 = lua.create_table()?;
362    /// table2.set(2, "value")?;
363    ///
364    /// let always_equals_mt = lua.create_table()?;
365    /// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?;
366    /// table2.set_metatable(Some(always_equals_mt))?;
367    ///
368    /// assert!(table1.equals(&table1.clone())?);
369    /// assert!(table1.equals(&table2)?);
370    /// # Ok(())
371    /// # }
372    /// ```
373    pub fn equals(&self, other: &Self) -> Result<bool> {
374        if self == other {
375            return Ok(true);
376        }
377
378        // Compare using `__eq` metamethod if exists
379        // First, check the self for the metamethod.
380        // If self does not define it, then check the other table.
381        if let Some(mt) = self.metatable()
382            && let Some(eq_func) = mt.get::<Option<Function>>("__eq")?
383        {
384            return eq_func.call((self, other));
385        }
386        if let Some(mt) = other.metatable()
387            && let Some(eq_func) = mt.get::<Option<Function>>("__eq")?
388        {
389            return eq_func.call((self, other));
390        }
391
392        Ok(false)
393    }
394
395    /// Sets a key-value pair without invoking metamethods.
396    pub fn raw_set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
397        let lua = self.0.lua.lock();
398        let state = lua.state();
399        unsafe {
400            #[cfg(feature = "luau")]
401            self.check_readonly_write(&lua)?;
402
403            let _sg = StackGuard::new(state);
404            check_stack(state, 5)?;
405
406            lua.push_ref(&self.0);
407            key.push_into_stack(&lua)?;
408            value.push_into_stack(&lua)?;
409
410            if lua.unlikely_memory_error() {
411                ffi::lua_rawset(state, -3);
412                ffi::lua_pop(state, 1);
413                Ok(())
414            } else {
415                protect_lua!(state, 3, 0, fn(state) ffi::lua_rawset(state, -3))
416            }
417        }
418    }
419
420    /// Gets the value associated to `key` without invoking metamethods.
421    pub fn raw_get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
422        let lua = self.0.lua.lock();
423        let state = lua.state();
424        unsafe {
425            let _sg = StackGuard::new(state);
426            check_stack(state, 3)?;
427
428            lua.push_ref(&self.0);
429            key.push_into_stack(&lua)?;
430            ffi::lua_rawget(state, -2);
431
432            V::from_stack(-1, &lua)
433        }
434    }
435
436    /// Inserts element value at position `idx` to the table, shifting up the elements from
437    /// `table[idx]`.
438    ///
439    /// The worst case complexity is O(n), where n is the table length.
440    pub fn raw_insert(&self, idx: Integer, value: impl IntoLua) -> Result<()> {
441        let size = self.raw_len() as Integer;
442        if idx < 1 || idx > size + 1 {
443            return Err(Error::runtime("index out of bounds"));
444        }
445
446        let lua = self.0.lua.lock();
447        let state = lua.state();
448        unsafe {
449            let _sg = StackGuard::new(state);
450            check_stack(state, 5)?;
451
452            lua.push_ref(&self.0);
453            value.push_into_stack(&lua)?;
454            protect_lua!(state, 2, 0, |state| {
455                for i in (idx..=size).rev() {
456                    // table[i+1] = table[i]
457                    ffi::lua_rawgeti(state, -2, i);
458                    ffi::lua_rawseti(state, -3, i + 1);
459                }
460                ffi::lua_rawseti(state, -2, idx)
461            })
462        }
463    }
464
465    /// Appends a value to the back of the table without invoking metamethods.
466    pub fn raw_push(&self, value: impl IntoLua) -> Result<()> {
467        let lua = self.0.lua.lock();
468        let state = lua.state();
469        unsafe {
470            #[cfg(feature = "luau")]
471            self.check_readonly_write(&lua)?;
472
473            let _sg = StackGuard::new(state);
474            check_stack(state, 4)?;
475
476            lua.push_ref(&self.0);
477            value.push_into_stack(&lua)?;
478
479            unsafe fn callback(state: *mut ffi::lua_State) {
480                let len = ffi::lua_rawlen(state, -2) as Integer;
481                ffi::lua_rawseti(state, -2, len + 1);
482            }
483
484            if lua.unlikely_memory_error() {
485                callback(state);
486            } else {
487                protect_lua!(state, 2, 0, fn(state) callback(state))?;
488            }
489        }
490        Ok(())
491    }
492
493    /// Removes the last element from the table and returns it, without invoking metamethods.
494    pub fn raw_pop<V: FromLua>(&self) -> Result<V> {
495        let lua = self.0.lua.lock();
496        let state = lua.state();
497        unsafe {
498            #[cfg(feature = "luau")]
499            self.check_readonly_write(&lua)?;
500
501            let _sg = StackGuard::new(state);
502            check_stack(state, 3)?;
503
504            lua.push_ref(&self.0);
505            let len = ffi::lua_rawlen(state, -1) as Integer;
506            ffi::lua_rawgeti(state, -1, len);
507            // Set slot to nil (it must be safe to do)
508            ffi::lua_pushnil(state);
509            ffi::lua_rawseti(state, -3, len);
510
511            V::from_stack(-1, &lua)
512        }
513    }
514
515    /// Removes a key from the table.
516    ///
517    /// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
518    /// and erases element `table[key]`. The complexity is `O(n)` in the worst case,
519    /// where `n` is the table length.
520    ///
521    /// For other key types this is equivalent to setting `table[key] = nil`.
522    pub fn raw_remove(&self, key: impl IntoLua) -> Result<()> {
523        let lua = self.0.lua.lock();
524        let state = lua.state();
525        let key = key.into_lua(lua.lua())?;
526        match key {
527            Value::Integer(idx) => {
528                let size = self.raw_len() as Integer;
529                if idx < 1 || idx > size {
530                    return Err(Error::runtime("index out of bounds"));
531                }
532                unsafe {
533                    let _sg = StackGuard::new(state);
534                    check_stack(state, 4)?;
535
536                    lua.push_ref(&self.0);
537                    protect_lua!(state, 1, 0, |state| {
538                        for i in idx..size {
539                            ffi::lua_rawgeti(state, -1, i + 1);
540                            ffi::lua_rawseti(state, -2, i);
541                        }
542                        ffi::lua_pushnil(state);
543                        ffi::lua_rawseti(state, -2, size);
544                    })
545                }
546            }
547            _ => self.raw_set(key, Nil),
548        }
549    }
550
551    /// Clears the table, removing all keys and values from array and hash parts,
552    /// without invoking metamethods.
553    ///
554    /// This method is useful to clear the table while keeping its capacity.
555    pub fn clear(&self) -> Result<()> {
556        let lua = self.0.lua.lock();
557        unsafe {
558            #[cfg(feature = "luau")]
559            {
560                self.check_readonly_write(&lua)?;
561                ffi::lua_cleartable(lua.ref_thread(), self.0.index);
562            }
563
564            #[cfg(not(feature = "luau"))]
565            {
566                let state = lua.state();
567                check_stack(state, 4)?;
568
569                lua.push_ref(&self.0);
570
571                // This is safe as long as we don't assign new keys
572                ffi::lua_pushnil(state);
573                while ffi::lua_next(state, -2) != 0 {
574                    ffi::lua_pop(state, 1); // pop value
575                    ffi::lua_pushvalue(state, -1); // copy key
576                    ffi::lua_pushnil(state);
577                    ffi::lua_rawset(state, -4);
578                }
579            }
580        }
581
582        Ok(())
583    }
584
585    /// Returns the result of the Lua `#` operator.
586    ///
587    /// This might invoke the `__len` metamethod. Use the [`Table::raw_len`] method if that is not
588    /// desired.
589    pub fn len(&self) -> Result<Integer> {
590        // Fast track (skip protected call)
591        if !self.has_metatable() {
592            return Ok(self.raw_len() as Integer);
593        }
594
595        let lua = self.0.lua.lock();
596        let state = lua.state();
597        unsafe {
598            let _sg = StackGuard::new(state);
599            check_stack(state, 4)?;
600
601            lua.push_ref(&self.0);
602            protect_lua!(state, 1, 0, |state| ffi::luaL_len(state, -1))
603        }
604    }
605
606    /// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod.
607    pub fn raw_len(&self) -> usize {
608        let lua = self.0.lua.lock();
609        unsafe { ffi::lua_rawlen(lua.ref_thread(), self.0.index) }
610    }
611
612    /// Returns `true` if the table is empty, without invoking metamethods.
613    ///
614    /// It checks both the array part and the hash part.
615    pub fn is_empty(&self) -> bool {
616        let lua = self.0.lua.lock();
617        let ref_thread = lua.ref_thread();
618        unsafe {
619            ffi::lua_pushnil(ref_thread);
620            if ffi::lua_next(ref_thread, self.0.index) == 0 {
621                return true;
622            }
623            ffi::lua_pop(ref_thread, 2);
624        }
625        false
626    }
627
628    /// Returns a reference to the metatable of this table, or `None` if no metatable is set.
629    ///
630    /// Unlike the [`getmetatable`] Lua function, this method ignores the `__metatable` field.
631    ///
632    /// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable
633    pub fn metatable(&self) -> Option<Table> {
634        let lua = self.0.lua.lock();
635        let ref_thread = lua.ref_thread();
636        unsafe {
637            if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 {
638                None
639            } else {
640                Some(Table(lua.pop_ref_thread()))
641            }
642        }
643    }
644
645    /// Sets or removes the metatable of this table.
646    ///
647    /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
648    /// nothing).
649    pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> {
650        #[cfg(feature = "luau")]
651        if self.is_readonly() {
652            return Err(Error::runtime("attempt to modify a readonly table"));
653        }
654
655        let lua = self.0.lua.lock();
656        let ref_thread = lua.ref_thread();
657        unsafe {
658            if let Some(metatable) = &metatable {
659                ffi::lua_pushvalue(ref_thread, metatable.0.index);
660            } else {
661                ffi::lua_pushnil(ref_thread);
662            }
663            ffi::lua_setmetatable(ref_thread, self.0.index);
664        }
665        Ok(())
666    }
667
668    /// Returns true if the table has metatable attached.
669    #[doc(hidden)]
670    #[inline]
671    pub fn has_metatable(&self) -> bool {
672        let lua = self.0.lua.lock();
673        unsafe { !get_metatable_ptr(lua.ref_thread(), self.0.index).is_null() }
674    }
675
676    /// Sets `readonly` attribute on the table.
677    #[cfg(any(feature = "luau", doc))]
678    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
679    pub fn set_readonly(&self, enabled: bool) {
680        let lua = self.0.lua.lock();
681        let ref_thread = lua.ref_thread();
682        unsafe {
683            ffi::lua_setreadonly(ref_thread, self.0.index, enabled as _);
684            if !enabled {
685                // Reset "safeenv" flag
686                ffi::lua_setsafeenv(ref_thread, self.0.index, 0);
687            }
688        }
689    }
690
691    /// Returns `readonly` attribute of the table.
692    #[cfg(any(feature = "luau", doc))]
693    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
694    pub fn is_readonly(&self) -> bool {
695        let lua = self.0.lua.lock();
696        let ref_thread = lua.ref_thread();
697        unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
698    }
699
700    /// Controls `safeenv` attribute on the table.
701    ///
702    /// This a special flag that activates some performance optimizations for environment tables.
703    /// In particular, it controls:
704    /// - Optimization of import resolution (cache values of constant keys).
705    /// - Fast-path for built-in iteration with pairs/ipairs.
706    /// - Fast-path for some built-in functions (fastcall).
707    ///
708    /// For `safeenv` environments, monkey patching or modifying values may not work as expected.
709    #[cfg(any(feature = "luau", doc))]
710    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
711    pub fn set_safeenv(&self, enabled: bool) {
712        let lua = self.0.lua.lock();
713        unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) };
714    }
715
716    /// Converts this table to a generic C pointer.
717    ///
718    /// Different tables will give different pointers.
719    /// There is no way to convert the pointer back to its original value.
720    ///
721    /// Typically this function is used only for hashing and debug information.
722    #[inline]
723    pub fn to_pointer(&self) -> *const c_void {
724        self.0.to_pointer()
725    }
726
727    /// Returns an iterator over the pairs of the table.
728    ///
729    /// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod.
730    ///
731    /// The pairs are wrapped in a [`Result`], since they are lazily converted to `K` and `V` types.
732    ///
733    /// # Examples
734    ///
735    /// Iterate over all globals:
736    ///
737    /// ```
738    /// # use mlua::{Lua, Result, Value};
739    /// # fn main() -> Result<()> {
740    /// # let lua = Lua::new();
741    /// let globals = lua.globals();
742    ///
743    /// for pair in globals.pairs::<Value, Value>() {
744    ///     let (key, value) = pair?;
745    /// #   let _ = (key, value);   // used
746    ///     // ...
747    /// }
748    /// # Ok(())
749    /// # }
750    /// ```
751    ///
752    /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
753    pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<'_, K, V> {
754        TablePairs {
755            guard: self.0.lua.lock(),
756            table: self,
757            key: Some(Nil),
758            _phantom: PhantomData,
759        }
760    }
761
762    /// Iterates over the pairs of the table, invoking the given closure on each pair.
763    ///
764    /// This method is similar to [`Table::pairs`], but optimized for performance.
765    /// It does not invoke the `__pairs` metamethod.
766    pub fn for_each<K, V>(&self, mut f: impl FnMut(K, V) -> Result<()>) -> Result<()>
767    where
768        K: FromLua,
769        V: FromLua,
770    {
771        let lua = self.0.lua.lock();
772        let state = lua.state();
773        unsafe {
774            let _sg = StackGuard::new(state);
775            check_stack(state, 5)?;
776
777            lua.push_ref(&self.0);
778            ffi::lua_pushnil(state);
779            while ffi::lua_next(state, -2) != 0 {
780                let k = K::from_stack(-2, &lua)?;
781                let v = lua.pop::<V>()?;
782                f(k, v)?;
783            }
784        }
785        Ok(())
786    }
787
788    /// Returns an iterator over all values in the sequence part of the table.
789    ///
790    /// The iterator will yield all values `t[1]`, `t[2]` and so on, until a `nil` value is
791    /// encountered. This mirrors the behavior of Lua's `ipairs` function but does not invoke
792    /// any metamethods.
793    ///
794    /// # Examples
795    ///
796    /// ```
797    /// # use mlua::{Lua, Result, Table};
798    /// # fn main() -> Result<()> {
799    /// # let lua = Lua::new();
800    /// let my_table: Table = lua.load(r#"
801    ///     {
802    ///         [1] = 4,
803    ///         [2] = 5,
804    ///         [4] = 7,
805    ///         key = 2
806    ///     }
807    /// "#).eval()?;
808    ///
809    /// let expected = [4, 5];
810    /// for (&expected, got) in expected.iter().zip(my_table.sequence_values::<u32>()) {
811    ///     assert_eq!(expected, got?);
812    /// }
813    /// # Ok(())
814    /// # }
815    /// ```
816    pub fn sequence_values<V: FromLua>(&self) -> TableSequence<'_, V> {
817        TableSequence {
818            guard: self.0.lua.lock(),
819            table: self,
820            index: 1,
821            len: None,
822            _phantom: PhantomData,
823        }
824    }
825
826    /// Iterates over the sequence part of the table, invoking the given closure on each value.
827    ///
828    /// This methods is similar to [`Table::sequence_values`], but optimized for performance.
829    #[doc(hidden)]
830    pub fn for_each_value<V: FromLua>(&self, f: impl FnMut(V) -> Result<()>) -> Result<()> {
831        self.for_each_value_by_len(None, f)
832    }
833
834    fn for_each_value_by_len<V: FromLua>(
835        &self,
836        len: impl Into<Option<usize>>,
837        mut f: impl FnMut(V) -> Result<()>,
838    ) -> Result<()> {
839        let len = len.into();
840        let lua = self.0.lua.lock();
841        let state = lua.state();
842        unsafe {
843            let _sg = StackGuard::new(state);
844            check_stack(state, 4)?;
845
846            lua.push_ref(&self.0);
847            for i in 1.. {
848                if len.map(|len| i > len).unwrap_or(false) {
849                    break;
850                }
851                let t = ffi::lua_rawgeti(state, -1, i as _);
852                if len.is_none() && t == ffi::LUA_TNIL {
853                    break;
854                }
855                f(lua.pop::<V>()?)?;
856            }
857        }
858        Ok(())
859    }
860
861    /// Sets element value at position `idx` without invoking metamethods.
862    #[doc(hidden)]
863    pub fn raw_seti(&self, idx: usize, value: impl IntoLua) -> Result<()> {
864        let lua = self.0.lua.lock();
865        let state = lua.state();
866        unsafe {
867            #[cfg(feature = "luau")]
868            self.check_readonly_write(&lua)?;
869
870            let _sg = StackGuard::new(state);
871            check_stack(state, 5)?;
872
873            lua.push_ref(&self.0);
874            value.push_into_stack(&lua)?;
875
876            let idx = idx.try_into().unwrap();
877            if lua.unlikely_memory_error() {
878                ffi::lua_rawseti(state, -2, idx);
879            } else {
880                protect_lua!(state, 2, 0, |state| ffi::lua_rawseti(state, -2, idx))?;
881            }
882        }
883        Ok(())
884    }
885
886    /// Checks if the table has the array metatable attached.
887    #[cfg(feature = "serde")]
888    fn has_array_metatable(&self) -> bool {
889        let lua = self.0.lua.lock();
890        let state = lua.state();
891        unsafe {
892            let _sg = StackGuard::new(state);
893            assert_stack(state, 3);
894
895            lua.push_ref(&self.0);
896            if ffi::lua_getmetatable(state, -1) == 0 {
897                return false;
898            }
899            crate::serde::push_array_metatable(state);
900            ffi::lua_rawequal(state, -1, -2) != 0
901        }
902    }
903
904    /// If the table is an array, returns the number of non-nil elements and max index.
905    ///
906    /// Returns `None` if the table is not an array.
907    ///
908    /// This operation has O(n) complexity.
909    #[cfg(feature = "serde")]
910    fn find_array_len(&self) -> Option<(usize, usize)> {
911        let lua = self.0.lua.lock();
912        let ref_thread = lua.ref_thread();
913        unsafe {
914            let _sg = StackGuard::new(ref_thread);
915
916            let (mut count, mut max_index) = (0, 0);
917            ffi::lua_pushnil(ref_thread);
918            while ffi::lua_next(ref_thread, self.0.index) != 0 {
919                if ffi::lua_type(ref_thread, -2) != ffi::LUA_TNUMBER {
920                    return None;
921                }
922
923                let k = ffi::lua_tonumber(ref_thread, -2);
924                if k.trunc() != k || k < 1.0 {
925                    return None;
926                }
927                max_index = std::cmp::max(max_index, k as usize);
928                count += 1;
929                ffi::lua_pop(ref_thread, 1);
930            }
931            Some((count, max_index))
932        }
933    }
934
935    /// Determines if the table should be encoded as an array or a map.
936    ///
937    /// The algorithm is the following:
938    /// 1. If `detect_mixed_tables` is enabled, iterate over all keys in the table checking is they
939    ///    all are positive integers. If non-array key is found, return `None` (encode as map).
940    ///    Otherwise check the sparsity of the array. Too sparse arrays are encoded as maps.
941    ///
942    /// 2. If `detect_mixed_tables` is disabled, check if the table has a positive length or has the
943    ///    array metatable. If so, encode as array. If the table is empty and
944    ///    `encode_empty_tables_as_array` is enabled, encode as array.
945    ///
946    /// Returns the length of the array if it should be encoded as an array.
947    #[cfg(feature = "serde")]
948    pub(crate) fn encode_as_array(&self, options: crate::serde::de::Options) -> Option<usize> {
949        if options.detect_mixed_tables {
950            if let Some((len, max_idx)) = self.find_array_len() {
951                // If the array is too sparse, serialize it as a map instead
952                if len < 10 || len * 2 >= max_idx {
953                    return Some(max_idx);
954                }
955            }
956        } else {
957            let len = self.raw_len();
958            if len > 0 || self.has_array_metatable() {
959                return Some(len);
960            }
961            if options.encode_empty_tables_as_array && self.is_empty() {
962                return Some(0);
963            }
964        }
965        None
966    }
967
968    #[cfg(feature = "luau")]
969    #[inline(always)]
970    fn check_readonly_write(&self, lua: &RawLua) -> Result<()> {
971        if unsafe { ffi::lua_getreadonly(lua.ref_thread(), self.0.index) != 0 } {
972            return Err(Error::runtime("attempt to modify a readonly table"));
973        }
974        Ok(())
975    }
976
977    pub(crate) fn fmt_pretty(
978        &self,
979        fmt: &mut fmt::Formatter,
980        ident: usize,
981        visited: &mut HashSet<*const c_void>,
982    ) -> fmt::Result {
983        visited.insert(self.to_pointer());
984
985        // Collect key/value pairs into a vector so we can sort them
986        let mut pairs = self.pairs::<Value, Value>().flatten().collect::<Vec<_>>();
987        // Sort keys
988        pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b));
989        let is_sequence = (pairs.iter().enumerate())
990            .all(|(i, (k, _))| matches!(k, Value::Integer(n) if *n == (i + 1) as Integer));
991        if pairs.is_empty() {
992            return write!(fmt, "{{}}");
993        }
994        writeln!(fmt, "{{")?;
995        if is_sequence {
996            // Format as list
997            for (_, value) in pairs {
998                write!(fmt, "{}", " ".repeat(ident + 2))?;
999                value.fmt_pretty(fmt, true, ident + 2, visited)?;
1000                writeln!(fmt, ",")?;
1001            }
1002        } else {
1003            fn is_simple_key(key: &[u8]) -> bool {
1004                key.iter().take(1).all(|c| c.is_ascii_alphabetic() || *c == b'_')
1005                    && key.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'_')
1006            }
1007
1008            for (key, value) in pairs {
1009                match key {
1010                    Value::String(key) if is_simple_key(&key.as_bytes()) => {
1011                        write!(fmt, "{}{}", " ".repeat(ident + 2), key.display())?;
1012                        write!(fmt, " = ")?;
1013                    }
1014                    _ => {
1015                        write!(fmt, "{}[", " ".repeat(ident + 2))?;
1016                        key.fmt_pretty(fmt, false, ident + 2, visited)?;
1017                        write!(fmt, "] = ")?;
1018                    }
1019                }
1020                value.fmt_pretty(fmt, true, ident + 2, visited)?;
1021                writeln!(fmt, ",")?;
1022            }
1023        }
1024        write!(fmt, "{}}}", " ".repeat(ident))
1025    }
1026}
1027
1028impl fmt::Debug for Table {
1029    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1030        if fmt.alternate() {
1031            return self.fmt_pretty(fmt, 0, &mut HashSet::new());
1032        }
1033        fmt.debug_tuple("Table").field(&self.0).finish()
1034    }
1035}
1036
1037impl<T> PartialEq<[T]> for Table
1038where
1039    T: IntoLua + Clone,
1040{
1041    fn eq(&self, other: &[T]) -> bool {
1042        let lua = self.0.lua.lock();
1043        let state = lua.state();
1044        unsafe {
1045            let _sg = StackGuard::new(state);
1046            assert_stack(state, 4);
1047
1048            lua.push_ref(&self.0);
1049
1050            let len = ffi::lua_rawlen(state, -1);
1051            for i in 0..len {
1052                ffi::lua_rawgeti(state, -1, (i + 1) as _);
1053                let val = lua.pop_value();
1054                if val == Nil {
1055                    return i == other.len();
1056                }
1057                match other.get(i).map(|v| v.clone().into_lua(lua.lua())) {
1058                    Some(Ok(other_val)) if val == other_val => continue,
1059                    _ => return false,
1060                }
1061            }
1062        }
1063        true
1064    }
1065}
1066
1067impl<T> PartialEq<&[T]> for Table
1068where
1069    T: IntoLua + Clone,
1070{
1071    #[inline]
1072    fn eq(&self, other: &&[T]) -> bool {
1073        self == *other
1074    }
1075}
1076
1077impl<T, const N: usize> PartialEq<[T; N]> for Table
1078where
1079    T: IntoLua + Clone,
1080{
1081    #[inline]
1082    fn eq(&self, other: &[T; N]) -> bool {
1083        self == &other[..]
1084    }
1085}
1086
1087impl ObjectLike for Table {
1088    #[inline]
1089    fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
1090        self.get(key)
1091    }
1092
1093    #[inline]
1094    fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
1095        self.set(key, value)
1096    }
1097
1098    #[inline]
1099    fn call<R>(&self, args: impl IntoLuaMulti) -> Result<R>
1100    where
1101        R: FromLuaMulti,
1102    {
1103        // Convert table to a function and call via pcall that respects the `__call` metamethod.
1104        Function(self.0.clone()).call(args)
1105    }
1106
1107    #[cfg(feature = "async")]
1108    #[inline]
1109    fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
1110    where
1111        R: FromLuaMulti,
1112    {
1113        Function(self.0.clone()).call_async(args)
1114    }
1115
1116    #[inline]
1117    fn call_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R>
1118    where
1119        R: FromLuaMulti,
1120    {
1121        self.call_function(name, (self, args))
1122    }
1123
1124    #[cfg(feature = "async")]
1125    fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
1126    where
1127        R: FromLuaMulti,
1128    {
1129        self.call_async_function(name, (self, args))
1130    }
1131
1132    #[inline]
1133    fn call_function<R: FromLuaMulti>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R> {
1134        match self.get(name)? {
1135            Value::Function(func) => func.call(args),
1136            val => {
1137                let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
1138                Err(Error::runtime(msg))
1139            }
1140        }
1141    }
1142
1143    #[cfg(feature = "async")]
1144    #[inline]
1145    fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
1146    where
1147        R: FromLuaMulti,
1148    {
1149        match self.get(name) {
1150            Ok(Value::Function(func)) => func.call_async(args),
1151            Ok(val) => {
1152                let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
1153                AsyncCallFuture::error(Error::RuntimeError(msg))
1154            }
1155            Err(err) => AsyncCallFuture::error(err),
1156        }
1157    }
1158
1159    #[inline]
1160    fn to_string(&self) -> Result<String> {
1161        Value::Table(Table(self.0.clone())).to_string()
1162    }
1163
1164    #[inline]
1165    fn to_value(&self) -> Value {
1166        Value::Table(self.clone())
1167    }
1168
1169    #[inline]
1170    fn weak_lua(&self) -> &WeakLua {
1171        &self.0.lua
1172    }
1173}
1174
1175/// A wrapped [`Table`] with customized serialization behavior.
1176#[cfg(feature = "serde")]
1177pub(crate) struct SerializableTable<'a> {
1178    table: &'a Table,
1179    options: crate::serde::de::Options,
1180    visited: Rc<RefCell<FxHashSet<*const c_void>>>,
1181}
1182
1183#[cfg(feature = "serde")]
1184impl Serialize for Table {
1185    #[inline]
1186    fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
1187        SerializableTable::new(self, Default::default(), Default::default()).serialize(serializer)
1188    }
1189}
1190
1191#[cfg(feature = "serde")]
1192impl<'a> SerializableTable<'a> {
1193    #[inline]
1194    pub(crate) fn new(
1195        table: &'a Table,
1196        options: crate::serde::de::Options,
1197        visited: Rc<RefCell<FxHashSet<*const c_void>>>,
1198    ) -> Self {
1199        Self {
1200            table,
1201            options,
1202            visited,
1203        }
1204    }
1205}
1206
1207impl<V> TableSequence<'_, V> {
1208    /// Sets the length (hint) of the sequence.
1209    #[cfg(feature = "serde")]
1210    pub(crate) fn with_len(mut self, len: usize) -> Self {
1211        self.len = Some(len);
1212        self
1213    }
1214}
1215
1216#[cfg(feature = "serde")]
1217impl Serialize for SerializableTable<'_> {
1218    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
1219    where
1220        S: Serializer,
1221    {
1222        use crate::serde::de::{MapPairs, RecursionGuard, check_value_for_skip};
1223        use crate::value::SerializableValue;
1224
1225        let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res {
1226            Ok(v) => Ok(v),
1227            Err(Error::SerializeError(_)) if serialize_err.is_some() => Err(serialize_err.unwrap()),
1228            Err(Error::SerializeError(msg)) => Err(serde::ser::Error::custom(msg)),
1229            Err(err) => Err(serde::ser::Error::custom(err.to_string())),
1230        };
1231
1232        let options = self.options;
1233        let visited = &self.visited;
1234        let _guard = RecursionGuard::new(self.table, visited);
1235
1236        // Array
1237        if let Some(len) = self.table.encode_as_array(self.options) {
1238            let mut seq = serializer.serialize_seq(Some(len))?;
1239            let mut serialize_err = None;
1240            let res = self.table.for_each_value_by_len::<Value>(len, |value| {
1241                let skip = check_value_for_skip(&value, self.options, visited)
1242                    .map_err(|err| Error::SerializeError(err.to_string()))?;
1243                if skip {
1244                    // continue iteration
1245                    return Ok(());
1246                }
1247                seq.serialize_element(&SerializableValue::new(&value, options, Some(visited)))
1248                    .map_err(|err| {
1249                        serialize_err = Some(err);
1250                        Error::SerializeError(String::new())
1251                    })
1252            });
1253            convert_result(res, serialize_err)?;
1254            return seq.end();
1255        }
1256
1257        // HashMap
1258        let mut map = serializer.serialize_map(None)?;
1259        let mut serialize_err = None;
1260        let mut process_pair = |key, value| {
1261            let skip_key = check_value_for_skip(&key, self.options, visited)
1262                .map_err(|err| Error::SerializeError(err.to_string()))?;
1263            let skip_value = check_value_for_skip(&value, self.options, visited)
1264                .map_err(|err| Error::SerializeError(err.to_string()))?;
1265            if skip_key || skip_value {
1266                // continue iteration
1267                return Ok(());
1268            }
1269            map.serialize_entry(
1270                &SerializableValue::new(&key, options, Some(visited)),
1271                &SerializableValue::new(&value, options, Some(visited)),
1272            )
1273            .map_err(|err| {
1274                serialize_err = Some(err);
1275                Error::SerializeError(String::new())
1276            })
1277        };
1278
1279        let res = if !self.options.sort_keys {
1280            // Fast track
1281            self.table.for_each(process_pair)
1282        } else {
1283            MapPairs::new(self.table, self.options.sort_keys)
1284                .map_err(serde::ser::Error::custom)?
1285                .try_for_each(|kv| {
1286                    let (key, value) = kv?;
1287                    process_pair(key, value)
1288                })
1289        };
1290        convert_result(res, serialize_err)?;
1291        map.end()
1292    }
1293}
1294
1295/// An iterator over the pairs of a Lua table.
1296///
1297/// This struct is created by the [`Table::pairs`] method.
1298///
1299/// [`Table::pairs`]: crate::Table::pairs
1300pub struct TablePairs<'a, K, V> {
1301    guard: LuaGuard,
1302    table: &'a Table,
1303    key: Option<Value>,
1304    _phantom: PhantomData<(K, V)>,
1305}
1306
1307impl<K, V> Iterator for TablePairs<'_, K, V>
1308where
1309    K: FromLua,
1310    V: FromLua,
1311{
1312    type Item = Result<(K, V)>;
1313
1314    fn next(&mut self) -> Option<Self::Item> {
1315        if let Some(prev_key) = self.key.take() {
1316            let lua: &RawLua = &self.guard;
1317            let state = lua.state();
1318
1319            let res = (|| unsafe {
1320                let _sg = StackGuard::new(state);
1321                check_stack(state, 5)?;
1322
1323                lua.push_ref(&self.table.0);
1324                lua.push_value(&prev_key)?;
1325
1326                // It must be safe to call `lua_next` unprotected as deleting a key from a table is
1327                // a permitted operation.
1328                // It fails only if the key is not found (never existed) which seems impossible scenario.
1329                if ffi::lua_next(state, -2) != 0 {
1330                    let key = lua.stack_value(-2, None);
1331                    Ok(Some((
1332                        key.clone(),
1333                        K::from_lua(key, lua.lua())?,
1334                        V::from_stack(-1, lua)?,
1335                    )))
1336                } else {
1337                    Ok(None)
1338                }
1339            })();
1340
1341            match res {
1342                Ok(Some((key, ret_key, value))) => {
1343                    self.key = Some(key);
1344                    Some(Ok((ret_key, value)))
1345                }
1346                Ok(None) => None,
1347                Err(e) => Some(Err(e)),
1348            }
1349        } else {
1350            None
1351        }
1352    }
1353}
1354
1355/// An iterator over the sequence part of a Lua table.
1356///
1357/// This struct is created by the [`Table::sequence_values`] method.
1358///
1359/// [`Table::sequence_values`]: crate::Table::sequence_values
1360pub struct TableSequence<'a, V> {
1361    guard: LuaGuard,
1362    table: &'a Table,
1363    index: Integer,
1364    len: Option<usize>,
1365    _phantom: PhantomData<V>,
1366}
1367
1368impl<V: FromLua> Iterator for TableSequence<'_, V> {
1369    type Item = Result<V>;
1370
1371    fn next(&mut self) -> Option<Self::Item> {
1372        let lua: &RawLua = &self.guard;
1373        let state = lua.state();
1374        unsafe {
1375            let _sg = StackGuard::new(state);
1376            if let Err(err) = check_stack(state, 1) {
1377                return Some(Err(err));
1378            }
1379
1380            lua.push_ref(&self.table.0);
1381            match ffi::lua_rawgeti(state, -1, self.index) {
1382                ffi::LUA_TNIL if self.index as usize > self.len.unwrap_or(0) => None,
1383                _ => {
1384                    self.index += 1;
1385                    Some(V::from_stack(-1, lua))
1386                }
1387            }
1388        }
1389    }
1390}
1391
1392#[cfg(test)]
1393mod assertions {
1394    use super::*;
1395
1396    #[cfg(not(feature = "send"))]
1397    static_assertions::assert_not_impl_any!(Table: Send);
1398    #[cfg(feature = "send")]
1399    static_assertions::assert_impl_all!(Table: Send, Sync);
1400}