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_int, 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        self.set_impl(key, value, false)
217    }
218
219    pub(crate) fn set_impl(&self, key: impl IntoLua, value: impl IntoLua, protect: bool) -> Result<()> {
220        let lua = self.0.lua.lock();
221        let state = lua.state();
222        unsafe {
223            let _sg = StackGuard::new(state);
224            check_stack(state, 5)?;
225
226            lua.push_ref(&self.0);
227            key.push_into_stack(&lua)?;
228            value.push_into_stack(&lua)?;
229            if protect || self.has_metatable() {
230                protect_lua!(state, 3, 0, fn(state) ffi::lua_settable(state, -3))
231            } else {
232                #[cfg(feature = "luau")]
233                self.check_readonly_write(&lua)?;
234
235                protect_lua_mem!(lua, or !Self::is_valid_key(state, -2), 3, 0, fn(state) {
236                    ffi::lua_rawset(state, -3)
237                })
238            }
239        }
240    }
241
242    /// Gets the value associated to `key` from the table.
243    ///
244    /// If no value is associated to `key`, returns the `nil` value.
245    ///
246    /// This might invoke the `__index` metamethod. Use the [`raw_get`] method if that is not
247    /// desired.
248    ///
249    /// # Examples
250    ///
251    /// Query the version of the Lua interpreter:
252    ///
253    /// ```
254    /// # use mlua::{Lua, Result};
255    /// # fn main() -> Result<()> {
256    /// # let lua = Lua::new();
257    /// let globals = lua.globals();
258    ///
259    /// let version: String = globals.get("_VERSION")?;
260    /// println!("Lua version: {}", version);
261    /// # Ok(())
262    /// # }
263    /// ```
264    ///
265    /// [`raw_get`]: Table::raw_get
266    pub fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
267        self.get_impl(key, false)
268    }
269
270    pub(crate) fn get_impl<V: FromLua>(&self, key: impl IntoLua, protect: bool) -> Result<V> {
271        let lua = self.0.lua.lock();
272        let state = lua.state();
273        unsafe {
274            let _sg = StackGuard::new(state);
275            check_stack(state, 4)?;
276
277            lua.push_ref(&self.0);
278            key.push_into_stack(&lua)?;
279            if protect || self.has_metatable() {
280                protect_lua!(state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
281            } else {
282                ffi::lua_rawget(state, -2);
283            }
284
285            V::from_stack(-1, &lua)
286        }
287    }
288
289    /// Checks whether the table contains a non-nil value for `key`.
290    ///
291    /// This might invoke the `__index` metamethod.
292    pub fn contains_key(&self, key: impl IntoLua) -> Result<bool> {
293        Ok(self.get::<Value>(key)? != Value::Nil)
294    }
295
296    /// Appends a value to the back of the table.
297    ///
298    /// This might invoke the `__len` and `__newindex` metamethods.
299    pub fn push(&self, value: impl IntoLua) -> Result<()> {
300        let lua = self.0.lua.lock();
301        let state = lua.state();
302        unsafe {
303            let _sg = StackGuard::new(state);
304            check_stack(state, 4)?;
305
306            lua.push_ref(&self.0);
307            value.push_into_stack(&lua)?;
308            if !self.has_metatable() {
309                #[cfg(feature = "luau")]
310                self.check_readonly_write(&lua)?;
311
312                return protect_lua_mem!(lua, 2, 0, fn(state) {
313                    let len = ffi::lua_rawlen(state, -2) as Integer;
314                    ffi::lua_rawseti(state, -2, len + 1);
315                });
316            }
317            protect_lua!(state, 2, 0, fn(state) {
318                let len = ffi::luaL_len(state, -2) as Integer;
319                if len == Integer::MAX {
320                    ffi::luaL_error(state, cstr!("table length overflow"));
321                }
322                ffi::lua_seti(state, -2, len + 1);
323            })?
324        }
325        Ok(())
326    }
327
328    /// Removes the last element from the table and returns it.
329    ///
330    /// This might invoke the `__len` and `__newindex` metamethods.
331    pub fn pop<V: FromLua>(&self) -> Result<V> {
332        // Fast track (skip protected call)
333        if !self.has_metatable() {
334            return self.raw_pop();
335        }
336
337        let lua = self.0.lua.lock();
338        let state = lua.state();
339        unsafe {
340            let _sg = StackGuard::new(state);
341            check_stack(state, 4)?;
342
343            lua.push_ref(&self.0);
344            protect_lua!(state, 1, 1, fn(state) {
345                let len = ffi::luaL_len(state, -1) as Integer;
346                if len == 0 {
347                    ffi::lua_pushnil(state);
348                } else {
349                    ffi::lua_geti(state, -1, len);
350                    ffi::lua_pushnil(state);
351                    ffi::lua_seti(state, -3, len);
352                }
353            })?;
354            V::from_stack(-1, &lua)
355        }
356    }
357
358    /// Removes a key from the table.
359    ///
360    /// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
361    /// and erases element `table[key]`. The complexity is `O(n)` in the worst case,
362    /// where `n` is the table length.
363    ///
364    /// For other key types this is equivalent to setting `table[key] = nil`.
365    ///
366    /// This might invoke the `__len`, `__index` and `__newindex` metamethods.
367    /// Use the [`raw_remove`] method if that is not desired.
368    ///
369    /// [`raw_remove`]: Table::raw_remove
370    pub fn remove(&self, key: impl IntoLua) -> Result<()> {
371        let lua = self.0.lua.lock();
372        let key = key.into_lua(lua.lua())?;
373
374        // Fast track (skip protected call)
375        if !self.has_metatable() {
376            return self.raw_remove(key);
377        }
378
379        match key {
380            Value::Integer(idx) => {
381                let size = self.len()?;
382                if idx < 1 || idx > size {
383                    return Err(Error::runtime("index out of bounds"));
384                }
385
386                let state = lua.state();
387                unsafe {
388                    let _sg = StackGuard::new(state);
389                    check_stack(state, 4)?;
390
391                    lua.push_ref(&self.0);
392                    protect_lua!(state, 1, 0, |state| {
393                        for i in idx..size {
394                            // table[i] = table[i+1]
395                            ffi::lua_geti(state, -1, i + 1);
396                            ffi::lua_seti(state, -2, i);
397                        }
398                        ffi::lua_pushnil(state);
399                        ffi::lua_seti(state, -2, size);
400                    })
401                }
402            }
403            _ => self.set(key, Nil),
404        }
405    }
406
407    /// Compares two tables for equality.
408    ///
409    /// Tables are compared by reference first.
410    /// If they are not primitively equals, then mlua will try to invoke the `__eq` metamethod.
411    /// mlua will check `self` first for the metamethod, then `other` if not found.
412    ///
413    /// # Examples
414    ///
415    /// Compare two tables using `__eq` metamethod:
416    ///
417    /// ```
418    /// # use mlua::{Lua, Result, Table};
419    /// # fn main() -> Result<()> {
420    /// # let lua = Lua::new();
421    /// let table1 = lua.create_table()?;
422    /// table1.set(1, "value")?;
423    ///
424    /// let table2 = lua.create_table()?;
425    /// table2.set(2, "value")?;
426    ///
427    /// let always_equals_mt = lua.create_table()?;
428    /// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?;
429    /// table2.set_metatable(Some(always_equals_mt))?;
430    ///
431    /// assert!(table1.equals(&table1.clone())?);
432    /// assert!(table1.equals(&table2)?);
433    /// # Ok(())
434    /// # }
435    /// ```
436    pub fn equals(&self, other: &Self) -> Result<bool> {
437        if self == other {
438            return Ok(true);
439        }
440
441        // Compare using `__eq` metamethod if exists
442        // First, check the self for the metamethod.
443        // If self does not define it, then check the other table.
444        if let Some(mt) = self.try_metatable()?
445            && let Some(eq_func) = mt.get::<Option<Function>>("__eq")?
446        {
447            return eq_func.call((self, other));
448        }
449        if let Some(mt) = other.try_metatable()?
450            && let Some(eq_func) = mt.get::<Option<Function>>("__eq")?
451        {
452            return eq_func.call((self, other));
453        }
454
455        Ok(false)
456    }
457
458    /// Sets a key-value pair without invoking metamethods.
459    pub fn raw_set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
460        let lua = self.0.lua.lock();
461        let state = lua.state();
462        unsafe {
463            let _sg = StackGuard::new(state);
464            check_stack(state, 5)?;
465
466            lua.push_ref(&self.0);
467            key.push_into_stack(&lua)?;
468            value.push_into_stack(&lua)?;
469
470            #[cfg(feature = "luau")]
471            self.check_readonly_write(&lua)?;
472
473            protect_lua_mem!(lua, or !Self::is_valid_key(state, -2), 3, 1, fn(state) {
474                ffi::lua_rawset(state, -3)
475            })?;
476            ffi::lua_pop(state, 1);
477            Ok(())
478        }
479    }
480
481    /// Gets the value associated to `key` without invoking metamethods.
482    pub fn raw_get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
483        let lua = self.0.lua.lock();
484        let state = lua.state();
485        unsafe {
486            let _sg = StackGuard::new(state);
487            check_stack(state, 3)?;
488
489            lua.push_ref(&self.0);
490            key.push_into_stack(&lua)?;
491            ffi::lua_rawget(state, -2);
492
493            V::from_stack(-1, &lua)
494        }
495    }
496
497    /// Inserts element value at position `idx` to the table, shifting up the elements from
498    /// `table[idx]`.
499    ///
500    /// The worst case complexity is O(n), where n is the table length.
501    pub fn raw_insert(&self, idx: Integer, value: impl IntoLua) -> Result<()> {
502        let size = self.raw_len() as Integer;
503        if idx < 1 || idx > size + 1 {
504            return Err(Error::runtime("index out of bounds"));
505        }
506
507        let lua = self.0.lua.lock();
508        let state = lua.state();
509        unsafe {
510            let _sg = StackGuard::new(state);
511            check_stack(state, 5)?;
512
513            lua.push_ref(&self.0);
514            value.push_into_stack(&lua)?;
515            protect_lua!(state, 2, 0, |state| {
516                for i in (idx..=size).rev() {
517                    // table[i+1] = table[i]
518                    ffi::lua_rawgeti(state, -2, i);
519                    ffi::lua_rawseti(state, -3, i + 1);
520                }
521                ffi::lua_rawseti(state, -2, idx)
522            })
523        }
524    }
525
526    /// Appends a value to the back of the table without invoking metamethods.
527    pub fn raw_push(&self, value: impl IntoLua) -> Result<()> {
528        let lua = self.0.lua.lock();
529        let state = lua.state();
530        unsafe {
531            let _sg = StackGuard::new(state);
532            check_stack(state, 4)?;
533
534            lua.push_ref(&self.0);
535            value.push_into_stack(&lua)?;
536
537            #[cfg(feature = "luau")]
538            self.check_readonly_write(&lua)?;
539
540            protect_lua_mem!(lua, 2, 0, fn(state) {
541                let len = ffi::lua_rawlen(state, -2) as Integer;
542                ffi::lua_rawseti(state, -2, len + 1);
543            })
544        }
545    }
546
547    /// Removes the last element from the table and returns it, without invoking metamethods.
548    pub fn raw_pop<V: FromLua>(&self) -> Result<V> {
549        let lua = self.0.lua.lock();
550        let state = lua.state();
551        unsafe {
552            #[cfg(feature = "luau")]
553            self.check_readonly_write(&lua)?;
554
555            let _sg = StackGuard::new(state);
556            check_stack(state, 3)?;
557
558            lua.push_ref(&self.0);
559            let len = ffi::lua_rawlen(state, -1) as Integer;
560            if len == 0 {
561                ffi::lua_pushnil(state);
562            } else {
563                ffi::lua_rawgeti(state, -1, len);
564                // Clear an existing slot without allocating.
565                ffi::lua_pushnil(state);
566                ffi::lua_rawseti(state, -3, len);
567            }
568
569            V::from_stack(-1, &lua)
570        }
571    }
572
573    /// Removes a key from the table.
574    ///
575    /// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
576    /// and erases element `table[key]`. The complexity is `O(n)` in the worst case,
577    /// where `n` is the table length.
578    ///
579    /// For other key types this is equivalent to setting `table[key] = nil`.
580    pub fn raw_remove(&self, key: impl IntoLua) -> Result<()> {
581        let lua = self.0.lua.lock();
582        let state = lua.state();
583        let key = key.into_lua(lua.lua())?;
584        match key {
585            Value::Integer(idx) => {
586                let size = self.raw_len() as Integer;
587                if idx < 1 || idx > size {
588                    return Err(Error::runtime("index out of bounds"));
589                }
590                unsafe {
591                    let _sg = StackGuard::new(state);
592                    check_stack(state, 4)?;
593
594                    lua.push_ref(&self.0);
595                    protect_lua!(state, 1, 0, |state| {
596                        for i in idx..size {
597                            ffi::lua_rawgeti(state, -1, i + 1);
598                            ffi::lua_rawseti(state, -2, i);
599                        }
600                        ffi::lua_pushnil(state);
601                        ffi::lua_rawseti(state, -2, size);
602                    })
603                }
604            }
605            _ => self.raw_set(key, Nil),
606        }
607    }
608
609    /// Clears the table, removing all keys and values from array and hash parts,
610    /// without invoking metamethods.
611    ///
612    /// This method is useful to clear the table while keeping its capacity.
613    pub fn clear(&self) -> Result<()> {
614        let lua = self.0.lua.lock();
615        unsafe {
616            #[cfg(feature = "luau")]
617            {
618                self.check_readonly_write(&lua)?;
619                ffi::lua_cleartable(lua.ref_thread(), self.0.index);
620            }
621
622            #[cfg(not(feature = "luau"))]
623            {
624                let state = lua.state();
625                let _sg = StackGuard::new(state);
626                check_stack(state, 4)?;
627
628                lua.push_ref(&self.0);
629
630                // This is safe as long as we don't assign new keys
631                ffi::lua_pushnil(state);
632                while ffi::lua_next(state, -2) != 0 {
633                    ffi::lua_pop(state, 1); // pop value
634                    ffi::lua_pushvalue(state, -1); // copy key
635                    ffi::lua_pushnil(state);
636                    ffi::lua_rawset(state, -4);
637                }
638            }
639        }
640
641        Ok(())
642    }
643
644    /// Returns the result of the Lua `#` operator.
645    ///
646    /// This might invoke the `__len` metamethod. Use the [`Table::raw_len`] method if that is not
647    /// desired.
648    pub fn len(&self) -> Result<Integer> {
649        // Fast track (skip protected call)
650        if !self.has_metatable() {
651            return Ok(self.raw_len() as Integer);
652        }
653
654        let lua = self.0.lua.lock();
655        let state = lua.state();
656        unsafe {
657            let _sg = StackGuard::new(state);
658            check_stack(state, 4)?;
659
660            lua.push_ref(&self.0);
661            protect_lua!(state, 1, 0, |state| ffi::luaL_len(state, -1))
662        }
663    }
664
665    /// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod.
666    pub fn raw_len(&self) -> usize {
667        let lua = self.0.lua.lock();
668        unsafe { ffi::lua_rawlen(lua.ref_thread(), self.0.index) }
669    }
670
671    /// Returns `true` if the table is empty, without invoking metamethods.
672    ///
673    /// It checks both the array part and the hash part.
674    pub fn is_empty(&self) -> bool {
675        let lua = self.0.lua.lock();
676        let ref_thread = lua.ref_thread();
677        unsafe {
678            ffi::lua_pushnil(ref_thread);
679            if ffi::lua_next(ref_thread, self.0.index) == 0 {
680                return true;
681            }
682            ffi::lua_pop(ref_thread, 2);
683        }
684        false
685    }
686
687    // Like `metatable`, but returns an error if the auxiliary stack cannot grow.
688    fn try_metatable(&self) -> Result<Option<Table>> {
689        let lua = self.0.lua.lock();
690        let ref_thread = lua.ref_thread();
691        unsafe {
692            Ok(if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 {
693                None
694            } else {
695                Some(Table(lua.try_pop_ref_thread()?))
696            })
697        }
698    }
699
700    /// Returns a reference to the metatable of this table, or `None` if no metatable is set.
701    ///
702    /// Unlike the [`getmetatable`] Lua function, this method ignores the `__metatable` field.
703    ///
704    /// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable
705    pub fn metatable(&self) -> Option<Table> {
706        let lua = self.0.lua.lock();
707        let ref_thread = lua.ref_thread();
708        unsafe {
709            if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 {
710                None
711            } else {
712                Some(Table(lua.pop_ref_thread()))
713            }
714        }
715    }
716
717    /// Sets or removes the metatable of this table.
718    ///
719    /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
720    /// nothing).
721    pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> {
722        #[cfg(feature = "luau")]
723        if self.is_readonly() {
724            return Err(Error::runtime("attempt to modify a readonly table"));
725        }
726
727        let lua = self.0.lua.lock();
728        let ref_thread = lua.ref_thread();
729        unsafe {
730            if let Some(metatable) = &metatable {
731                assert!(
732                    lua.weak() == &metatable.0.lua,
733                    "Lua instance passed Value created from a different main Lua state"
734                );
735                ffi::lua_pushvalue(ref_thread, metatable.0.index);
736            } else {
737                ffi::lua_pushnil(ref_thread);
738            }
739            ffi::lua_setmetatable(ref_thread, self.0.index);
740        }
741        Ok(())
742    }
743
744    /// Returns true if the table has metatable attached.
745    #[doc(hidden)]
746    #[inline]
747    pub fn has_metatable(&self) -> bool {
748        let lua = self.0.lua.lock();
749        unsafe { !get_metatable_ptr(lua.ref_thread(), self.0.index).is_null() }
750    }
751
752    /// Sets `readonly` attribute on the table.
753    #[cfg(any(feature = "luau", doc))]
754    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
755    pub fn set_readonly(&self, enabled: bool) {
756        let lua = self.0.lua.lock();
757        let ref_thread = lua.ref_thread();
758        unsafe {
759            ffi::lua_setreadonly(ref_thread, self.0.index, enabled as _);
760            if !enabled {
761                // Reset "safeenv" flag
762                ffi::lua_setsafeenv(ref_thread, self.0.index, 0);
763            }
764        }
765    }
766
767    /// Returns `readonly` attribute of the table.
768    #[cfg(any(feature = "luau", doc))]
769    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
770    pub fn is_readonly(&self) -> bool {
771        let lua = self.0.lua.lock();
772        let ref_thread = lua.ref_thread();
773        unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
774    }
775
776    /// Controls `safeenv` attribute on the table.
777    ///
778    /// This a special flag that activates some performance optimizations for environment tables.
779    /// In particular, it controls:
780    /// - Optimization of import resolution (cache values of constant keys).
781    /// - Fast-path for built-in iteration with pairs/ipairs.
782    /// - Fast-path for some built-in functions (fastcall).
783    ///
784    /// For `safeenv` environments, monkey patching or modifying values may not work as expected.
785    #[cfg(any(feature = "luau", doc))]
786    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
787    pub fn set_safeenv(&self, enabled: bool) {
788        let lua = self.0.lua.lock();
789        unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) };
790    }
791
792    /// Converts this table to a generic C pointer.
793    ///
794    /// Different tables will give different pointers.
795    /// There is no way to convert the pointer back to its original value.
796    ///
797    /// Typically this function is used only for hashing and debug information.
798    #[inline]
799    pub fn to_pointer(&self) -> *const c_void {
800        self.0.to_pointer()
801    }
802
803    /// Returns an iterator over the pairs of the table.
804    ///
805    /// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod.
806    ///
807    /// The pairs are wrapped in a [`Result`], since they are lazily converted to `K` and `V` types.
808    ///
809    /// # Examples
810    ///
811    /// Iterate over all globals:
812    ///
813    /// ```
814    /// # use mlua::{Lua, Result, Value};
815    /// # fn main() -> Result<()> {
816    /// # let lua = Lua::new();
817    /// let globals = lua.globals();
818    ///
819    /// for pair in globals.pairs::<Value, Value>() {
820    ///     let (key, value) = pair?;
821    /// #   let _ = (key, value);   // used
822    ///     // ...
823    /// }
824    /// # Ok(())
825    /// # }
826    /// ```
827    ///
828    /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
829    pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<'_, K, V> {
830        TablePairs {
831            guard: self.0.lua.lock(),
832            table: self,
833            key: Some(Nil),
834            #[cfg(feature = "luau")]
835            index: 0,
836            _phantom: PhantomData,
837        }
838    }
839
840    /// Iterates over the pairs of the table, invoking the given closure on each pair.
841    ///
842    /// This method is similar to [`Table::pairs`], but optimized for performance.
843    /// It does not invoke the `__pairs` metamethod.
844    pub fn for_each<K, V>(&self, mut f: impl FnMut(K, V) -> Result<()>) -> Result<()>
845    where
846        K: FromLua,
847        V: FromLua,
848    {
849        let lua = self.0.lua.lock();
850        let state = lua.state();
851        unsafe {
852            let _sg = StackGuard::new(state);
853            check_stack(state, 5)?;
854
855            lua.push_ref(&self.0);
856            let mut callback = || {
857                let k = K::from_stack(-2, &lua)?;
858                let v = V::from_stack(-1, &lua)?;
859                ffi::lua_pop(state, if cfg!(feature = "luau") { 2 } else { 1 });
860                f(k, v)
861            };
862
863            #[cfg(feature = "luau")]
864            {
865                let mut index = ffi::lua_rawiter(state, -1, 0);
866                while index >= 0 {
867                    callback()?;
868                    index = ffi::lua_rawiter(state, -1, index);
869                }
870            }
871
872            #[cfg(not(feature = "luau"))]
873            {
874                use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
875
876                let mut result = Ok(Ok(()));
877                let lua_result = protect_lua!(state, 1, 0, |state| {
878                    ffi::lua_pushnil(state);
879                    while matches!(result, Ok(Ok(()))) && ffi::lua_next(state, -2) != 0 {
880                        // Keep Rust errors/panics outside the longjmp boundary
881                        match catch_unwind(AssertUnwindSafe(&mut callback)) {
882                            Ok(Ok(())) => {}
883                            err => result = err,
884                        }
885                    }
886                });
887                result.unwrap_or_else(|panic| resume_unwind(panic))?;
888                lua_result?;
889            }
890        }
891        Ok(())
892    }
893
894    /// Returns an iterator over all values in the sequence part of the table.
895    ///
896    /// The iterator will yield all values `t[1]`, `t[2]` and so on, until a `nil` value is
897    /// encountered. This mirrors the behavior of Lua's `ipairs` function but does not invoke
898    /// any metamethods.
899    ///
900    /// # Examples
901    ///
902    /// ```
903    /// # use mlua::{Lua, Result, Table};
904    /// # fn main() -> Result<()> {
905    /// # let lua = Lua::new();
906    /// let my_table: Table = lua.load(r#"
907    ///     {
908    ///         [1] = 4,
909    ///         [2] = 5,
910    ///         [4] = 7,
911    ///         key = 2
912    ///     }
913    /// "#).eval()?;
914    ///
915    /// let expected = [4, 5];
916    /// for (&expected, got) in expected.iter().zip(my_table.sequence_values::<u32>()) {
917    ///     assert_eq!(expected, got?);
918    /// }
919    /// # Ok(())
920    /// # }
921    /// ```
922    pub fn sequence_values<V: FromLua>(&self) -> TableSequence<'_, V> {
923        TableSequence {
924            guard: self.0.lua.lock(),
925            table: self,
926            index: 1,
927            len: None,
928            _phantom: PhantomData,
929        }
930    }
931
932    /// Iterates over the sequence part of the table, invoking the given closure on each value.
933    ///
934    /// This methods is similar to [`Table::sequence_values`], but optimized for performance.
935    #[doc(hidden)]
936    pub fn for_each_value<V: FromLua>(&self, f: impl FnMut(V) -> Result<()>) -> Result<()> {
937        self.for_each_value_by_len(None, f)
938    }
939
940    fn for_each_value_by_len<V: FromLua>(
941        &self,
942        len: impl Into<Option<usize>>,
943        mut f: impl FnMut(V) -> Result<()>,
944    ) -> Result<()> {
945        let len = len.into();
946        let lua = self.0.lua.lock();
947        let state = lua.state();
948        unsafe {
949            let _sg = StackGuard::new(state);
950            check_stack(state, 4)?;
951
952            lua.push_ref(&self.0);
953            for i in 1.. {
954                if len.map(|len| i > len).unwrap_or(false) {
955                    break;
956                }
957                let t = ffi::lua_rawgeti(state, -1, i as _);
958                if len.is_none() && t == ffi::LUA_TNIL {
959                    break;
960                }
961                f(lua.pop::<V>()?)?;
962            }
963        }
964        Ok(())
965    }
966
967    /// Sets element value at position `idx` without invoking metamethods.
968    #[doc(hidden)]
969    pub fn raw_seti(&self, idx: usize, value: impl IntoLua) -> Result<()> {
970        let lua = self.0.lua.lock();
971        let state = lua.state();
972        unsafe {
973            let _sg = StackGuard::new(state);
974            check_stack(state, 5)?;
975
976            lua.push_ref(&self.0);
977            value.push_into_stack(&lua)?;
978
979            #[cfg(feature = "luau")]
980            self.check_readonly_write(&lua)?;
981
982            let idx = idx.try_into().unwrap();
983            protect_lua_mem!(lua, 2, 0, |state| {
984                ffi::lua_rawseti(state, -2, idx);
985            })
986        }
987    }
988
989    /// Checks if the table has the array metatable attached.
990    #[cfg(feature = "serde")]
991    fn has_array_metatable(&self) -> bool {
992        let lua = self.0.lua.lock();
993        let ref_thread = lua.ref_thread();
994        unsafe {
995            let _sg = StackGuard::new(ref_thread);
996
997            if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 {
998                return false;
999            }
1000            crate::serde::push_array_metatable(ref_thread);
1001            ffi::lua_rawequal(ref_thread, -1, -2) != 0
1002        }
1003    }
1004
1005    /// If the table is an array, returns the number of non-nil elements and max index.
1006    ///
1007    /// Returns `None` if the table is not an array.
1008    ///
1009    /// This operation has O(n) complexity.
1010    #[cfg(feature = "serde")]
1011    fn find_array_len(&self) -> Option<(usize, usize)> {
1012        let lua = self.0.lua.lock();
1013        let ref_thread = lua.ref_thread();
1014        unsafe {
1015            let _sg = StackGuard::new(ref_thread);
1016
1017            let (mut count, mut max_index) = (0, 0);
1018            ffi::lua_pushnil(ref_thread);
1019            while ffi::lua_next(ref_thread, self.0.index) != 0 {
1020                if ffi::lua_type(ref_thread, -2) != ffi::LUA_TNUMBER {
1021                    return None;
1022                }
1023
1024                let k = ffi::lua_tonumber(ref_thread, -2);
1025                if k.trunc() != k || k < 1.0 {
1026                    return None;
1027                }
1028                max_index = std::cmp::max(max_index, k as usize);
1029                count += 1;
1030                ffi::lua_pop(ref_thread, 1);
1031            }
1032            Some((count, max_index))
1033        }
1034    }
1035
1036    /// Determines if the table should be encoded as an array or a map.
1037    ///
1038    /// The algorithm is the following:
1039    /// 1. If the table has the array metatable attached, always encode it as an array.
1040    ///
1041    /// 2. If `detect_mixed_tables` is enabled, iterate over all keys in the table checking is they
1042    ///    all are positive integers. If non-array key is found, return `None` (encode as map).
1043    ///    Otherwise check the sparsity of the array. Too sparse arrays are encoded as maps.
1044    ///
1045    /// 3. If `detect_mixed_tables` is disabled, check if the table has a positive length. If so,
1046    ///    encode as array. If the table is empty and `encode_empty_tables_as_array` is enabled,
1047    ///    encode as array.
1048    ///
1049    /// Returns the length of the array if it should be encoded as an array.
1050    #[cfg(feature = "serde")]
1051    pub(crate) fn encode_as_array(&self, options: crate::serde::de::Options) -> Option<usize> {
1052        if self.has_array_metatable() {
1053            return Some(self.raw_len());
1054        }
1055        if options.detect_mixed_tables {
1056            if let Some((len, max_idx)) = self.find_array_len() {
1057                // If the array is too sparse, serialize it as a map instead
1058                if max_idx < 10 || len * 2 >= max_idx {
1059                    return Some(max_idx);
1060                }
1061            }
1062        } else {
1063            let len = self.raw_len();
1064            if len > 0 {
1065                return Some(len);
1066            }
1067            if options.encode_empty_tables_as_array && self.is_empty() {
1068                return Some(0);
1069            }
1070        }
1071        None
1072    }
1073
1074    #[cfg(feature = "serde")]
1075    pub(crate) fn collect_pairs(&self) -> Result<Vec<(Value, Value)>> {
1076        let mut pairs = Vec::new();
1077
1078        #[cfg(not(feature = "luau"))]
1079        unsafe {
1080            const LIMIT: c_int = 8;
1081            let lua = self.0.lua.lock();
1082            let state = lua.state();
1083            let _sg = StackGuard::new(state);
1084            check_stack(state, 2 * LIMIT + 2)?;
1085
1086            lua.push_ref(&self.0);
1087            let table_index = ffi::lua_gettop(state);
1088            ffi::lua_pushnil(state);
1089            // Finish small traversals before conversions or allocations can reenter Lua
1090            for count in 0..LIMIT {
1091                if ffi::lua_next(state, table_index) == 0 {
1092                    pairs.reserve_exact(count as usize);
1093                    for i in 0..count {
1094                        let index = table_index + 1 + 2 * i;
1095                        let key = lua.try_stack_value(index, None)?;
1096                        pairs.push((key, lua.try_stack_value(index + 1, None)?));
1097                    }
1098                    return Ok(pairs);
1099                }
1100                ffi::lua_pushvalue(state, -2);
1101            }
1102        }
1103
1104        self.for_each(|key, value| {
1105            pairs.push((key, value));
1106            Ok(())
1107        })?;
1108        Ok(pairs)
1109    }
1110
1111    #[cfg(not(feature = "luau"))]
1112    #[inline]
1113    unsafe fn next(state: *mut ffi::lua_State) -> Result<bool> {
1114        let protect = ffi::lua_isnil(state, -1) == 0 && {
1115            ffi::lua_pushvalue(state, -1);
1116            let missing = ffi::lua_rawget(state, -3) == ffi::LUA_TNIL;
1117            ffi::lua_pop(state, 1);
1118            missing
1119        };
1120        // A deleted key may still be valid for next, but a rehash can invalidate it.
1121        protect_lua_mem!(state, if protect, 2, ffi::LUA_MULTRET, |state| ffi::lua_next(state, -2) != 0)
1122    }
1123
1124    #[inline]
1125    pub(crate) unsafe fn is_valid_key(state: *mut ffi::lua_State, idx: c_int) -> bool {
1126        match ffi::lua_type(state, idx) {
1127            ffi::LUA_TNIL => false,
1128            ffi::LUA_TNUMBER => !ffi::lua_tonumber(state, idx).is_nan(),
1129            // Luau vectors containing NaN are not equal to themselves.
1130            #[cfg(feature = "luau")]
1131            ffi::LUA_TVECTOR => ffi::lua_rawequal(state, idx, idx) != 0,
1132            _ => true,
1133        }
1134    }
1135
1136    #[cfg(feature = "luau")]
1137    #[inline(always)]
1138    fn check_readonly_write(&self, lua: &RawLua) -> Result<()> {
1139        if unsafe { ffi::lua_getreadonly(lua.ref_thread(), self.0.index) != 0 } {
1140            return Err(Error::runtime("attempt to modify a readonly table"));
1141        }
1142        Ok(())
1143    }
1144
1145    pub(crate) fn fmt_pretty(
1146        &self,
1147        fmt: &mut fmt::Formatter,
1148        ident: usize,
1149        visited: &mut HashSet<*const c_void>,
1150    ) -> fmt::Result {
1151        visited.insert(self.to_pointer());
1152
1153        // Collect key/value pairs into a vector so we can sort them
1154        let mut pairs = self.pairs::<Value, Value>().flatten().collect::<Vec<_>>();
1155        // Sort keys
1156        pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b));
1157        let is_sequence = (pairs.iter().enumerate())
1158            .all(|(i, (k, _))| matches!(k, Value::Integer(n) if *n == (i + 1) as Integer));
1159        if pairs.is_empty() {
1160            return write!(fmt, "{{}}");
1161        }
1162        writeln!(fmt, "{{")?;
1163        if is_sequence {
1164            // Format as list
1165            for (_, value) in pairs {
1166                write!(fmt, "{}", " ".repeat(ident + 2))?;
1167                value.fmt_pretty(fmt, true, ident + 2, visited)?;
1168                writeln!(fmt, ",")?;
1169            }
1170        } else {
1171            fn is_simple_key(key: &[u8]) -> bool {
1172                key.iter().take(1).all(|c| c.is_ascii_alphabetic() || *c == b'_')
1173                    && key.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'_')
1174            }
1175
1176            for (key, value) in pairs {
1177                match key {
1178                    Value::String(key) if is_simple_key(&key.as_bytes()) => {
1179                        write!(fmt, "{}{}", " ".repeat(ident + 2), key.display())?;
1180                        write!(fmt, " = ")?;
1181                    }
1182                    _ => {
1183                        write!(fmt, "{}[", " ".repeat(ident + 2))?;
1184                        key.fmt_pretty(fmt, false, ident + 2, visited)?;
1185                        write!(fmt, "] = ")?;
1186                    }
1187                }
1188                value.fmt_pretty(fmt, true, ident + 2, visited)?;
1189                writeln!(fmt, ",")?;
1190            }
1191        }
1192        write!(fmt, "{}}}", " ".repeat(ident))
1193    }
1194}
1195
1196impl fmt::Debug for Table {
1197    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1198        if fmt.alternate() {
1199            return self.fmt_pretty(fmt, 0, &mut HashSet::new());
1200        }
1201        fmt.debug_tuple("Table").field(&self.0).finish()
1202    }
1203}
1204
1205impl<T> PartialEq<[T]> for Table
1206where
1207    T: IntoLua + Clone,
1208{
1209    fn eq(&self, other: &[T]) -> bool {
1210        let lua = self.0.lua.lock();
1211        let state = lua.state();
1212        unsafe {
1213            let _sg = StackGuard::new(state);
1214            assert_stack(state, 4);
1215
1216            lua.push_ref(&self.0);
1217
1218            let len = ffi::lua_rawlen(state, -1);
1219            for i in 0..len {
1220                ffi::lua_rawgeti(state, -1, (i + 1) as _);
1221                let val = lua.pop_value();
1222                if val == Nil {
1223                    return i == other.len();
1224                }
1225                match other.get(i).map(|v| v.clone().into_lua(lua.lua())) {
1226                    Some(Ok(other_val)) if val == other_val => continue,
1227                    _ => return false,
1228                }
1229            }
1230            len == other.len()
1231        }
1232    }
1233}
1234
1235impl<T> PartialEq<&[T]> for Table
1236where
1237    T: IntoLua + Clone,
1238{
1239    #[inline]
1240    fn eq(&self, other: &&[T]) -> bool {
1241        self == *other
1242    }
1243}
1244
1245impl<T, const N: usize> PartialEq<[T; N]> for Table
1246where
1247    T: IntoLua + Clone,
1248{
1249    #[inline]
1250    fn eq(&self, other: &[T; N]) -> bool {
1251        self == &other[..]
1252    }
1253}
1254
1255impl ObjectLike for Table {
1256    #[inline]
1257    fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
1258        self.get(key)
1259    }
1260
1261    #[inline]
1262    fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
1263        self.set(key, value)
1264    }
1265
1266    #[inline]
1267    fn call<R>(&self, args: impl IntoLuaMulti) -> Result<R>
1268    where
1269        R: FromLuaMulti,
1270    {
1271        // Convert table to a function and call via pcall that respects the `__call` metamethod.
1272        Function(self.0.clone()).call(args)
1273    }
1274
1275    #[cfg(feature = "async")]
1276    #[inline]
1277    fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
1278    where
1279        R: FromLuaMulti,
1280    {
1281        Function(self.0.clone()).call_async(args)
1282    }
1283
1284    #[inline]
1285    fn call_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R>
1286    where
1287        R: FromLuaMulti,
1288    {
1289        self.call_function(name, (self, args))
1290    }
1291
1292    #[cfg(feature = "async")]
1293    fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
1294    where
1295        R: FromLuaMulti,
1296    {
1297        self.call_async_function(name, (self, args))
1298    }
1299
1300    #[inline]
1301    fn call_function<R: FromLuaMulti>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R> {
1302        match self.get(name)? {
1303            Value::Function(func) => func.call(args),
1304            val => {
1305                let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
1306                Err(Error::runtime(msg))
1307            }
1308        }
1309    }
1310
1311    #[cfg(feature = "async")]
1312    #[inline]
1313    fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
1314    where
1315        R: FromLuaMulti,
1316    {
1317        match self.get(name) {
1318            Ok(Value::Function(func)) => func.call_async(args),
1319            Ok(val) => {
1320                let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
1321                AsyncCallFuture::error(Error::RuntimeError(msg))
1322            }
1323            Err(err) => AsyncCallFuture::error(err),
1324        }
1325    }
1326
1327    #[inline]
1328    fn to_string(&self) -> Result<String> {
1329        Value::Table(Table(self.0.clone())).to_string()
1330    }
1331
1332    #[inline]
1333    fn to_value(&self) -> Value {
1334        Value::Table(self.clone())
1335    }
1336
1337    #[inline]
1338    fn weak_lua(&self) -> &WeakLua {
1339        &self.0.lua
1340    }
1341}
1342
1343/// A wrapped [`Table`] with customized serialization behavior.
1344#[cfg(feature = "serde")]
1345pub(crate) struct SerializableTable<'a> {
1346    table: &'a Table,
1347    options: crate::serde::de::Options,
1348    visited: Rc<RefCell<FxHashSet<*const c_void>>>,
1349}
1350
1351#[cfg(feature = "serde")]
1352impl Serialize for Table {
1353    #[inline]
1354    fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
1355        SerializableTable::new(self, Default::default(), Default::default()).serialize(serializer)
1356    }
1357}
1358
1359#[cfg(feature = "serde")]
1360impl<'a> SerializableTable<'a> {
1361    #[inline]
1362    pub(crate) fn new(
1363        table: &'a Table,
1364        options: crate::serde::de::Options,
1365        visited: Rc<RefCell<FxHashSet<*const c_void>>>,
1366    ) -> Self {
1367        Self {
1368            table,
1369            options,
1370            visited,
1371        }
1372    }
1373}
1374
1375impl<V> TableSequence<'_, V> {
1376    /// Sets the length (hint) of the sequence.
1377    #[cfg(feature = "serde")]
1378    pub(crate) fn with_len(mut self, len: usize) -> Self {
1379        self.len = Some(len);
1380        self
1381    }
1382}
1383
1384#[cfg(feature = "serde")]
1385impl Serialize for SerializableTable<'_> {
1386    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
1387    where
1388        S: Serializer,
1389    {
1390        use crate::serde::de::{MapPairs, RecursionGuard, check_key_for_skip, check_value_for_skip};
1391        use crate::value::SerializableValue;
1392
1393        let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res {
1394            Ok(v) => Ok(v),
1395            Err(Error::SerializeError(_)) if serialize_err.is_some() => Err(serialize_err.unwrap()),
1396            Err(Error::SerializeError(msg)) => Err(serde::ser::Error::custom(msg)),
1397            Err(err) => Err(serde::ser::Error::custom(err.to_string())),
1398        };
1399
1400        let options = self.options;
1401        let visited = &self.visited;
1402        let _guard = RecursionGuard::new(self.table, visited, options.recursion_limit)
1403            .map_err(serde::ser::Error::custom)?;
1404
1405        // Array
1406        if let Some(len) = self.table.encode_as_array(self.options) {
1407            let mut seq = serializer.serialize_seq(Some(len))?;
1408            let mut serialize_err = None;
1409            let res = self.table.for_each_value_by_len::<Value>(len, |value| {
1410                let skip = check_value_for_skip(&value, self.options, visited)
1411                    .map_err(|err| Error::SerializeError(err.to_string()))?;
1412                if skip {
1413                    // continue iteration
1414                    return Ok(());
1415                }
1416                seq.serialize_element(&SerializableValue::new(&value, options, Some(visited)))
1417                    .map_err(|err| {
1418                        serialize_err = Some(err);
1419                        Error::SerializeError(String::new())
1420                    })
1421            });
1422            convert_result(res, serialize_err)?;
1423            return seq.end();
1424        }
1425
1426        // HashMap
1427        let mut map = serializer.serialize_map(None)?;
1428        let mut serialize_err = None;
1429        let mut process_pair = |key, value| {
1430            let skip_key = check_key_for_skip(&key, self.options, visited)
1431                .map_err(|err| Error::SerializeError(err.to_string()))?;
1432            let skip_value = check_value_for_skip(&value, self.options, visited)
1433                .map_err(|err| Error::SerializeError(err.to_string()))?;
1434            if skip_key || skip_value {
1435                // continue iteration
1436                return Ok(());
1437            }
1438            map.serialize_entry(
1439                &SerializableValue::new(&key, options, Some(visited)),
1440                &SerializableValue::new(&value, options, Some(visited)),
1441            )
1442            .map_err(|err| {
1443                serialize_err = Some(err);
1444                Error::SerializeError(String::new())
1445            })
1446        };
1447
1448        let res = if !self.options.sort_keys {
1449            // Fast track
1450            self.table.for_each(process_pair)
1451        } else {
1452            MapPairs::new(self.table, self.options.sort_keys)
1453                .map_err(serde::ser::Error::custom)?
1454                .try_for_each(|kv| {
1455                    let (key, value) = kv?;
1456                    process_pair(key, value)
1457                })
1458        };
1459        convert_result(res, serialize_err)?;
1460        map.end()
1461    }
1462}
1463
1464/// An iterator over the pairs of a Lua table.
1465///
1466/// This struct is created by the [`Table::pairs`] method.
1467///
1468/// [`Table::pairs`]: crate::Table::pairs
1469pub struct TablePairs<'a, K, V> {
1470    guard: LuaGuard,
1471    table: &'a Table,
1472    key: Option<Value>,
1473    #[cfg(feature = "luau")]
1474    index: c_int,
1475    _phantom: PhantomData<(K, V)>,
1476}
1477
1478impl<K, V> Iterator for TablePairs<'_, K, V>
1479where
1480    K: FromLua,
1481    V: FromLua,
1482{
1483    type Item = Result<(K, V)>;
1484
1485    fn next(&mut self) -> Option<Self::Item> {
1486        if let Some(_prev_key) = self.key.take() {
1487            let lua: &RawLua = &self.guard;
1488            let state = lua.state();
1489
1490            let res = (|| unsafe {
1491                let _sg = StackGuard::new(state);
1492                check_stack(state, 5)?;
1493
1494                lua.push_ref(&self.table.0);
1495                #[cfg(feature = "luau")]
1496                let more = {
1497                    self.index = ffi::lua_rawiter(state, -1, self.index);
1498                    self.index >= 0
1499                };
1500                #[cfg(not(feature = "luau"))]
1501                let more = {
1502                    lua.push_value(&_prev_key)?;
1503                    Table::next(state)?
1504                };
1505
1506                if more {
1507                    let key = lua.try_stack_value(-2, None)?;
1508                    Ok(Some((
1509                        key.clone(),
1510                        K::from_lua(key, lua.lua())?,
1511                        V::from_stack(-1, lua)?,
1512                    )))
1513                } else {
1514                    Ok(None)
1515                }
1516            })();
1517
1518            match res {
1519                Ok(Some((key, ret_key, value))) => {
1520                    self.key = Some(key);
1521                    Some(Ok((ret_key, value)))
1522                }
1523                Ok(None) => None,
1524                Err(e) => Some(Err(e)),
1525            }
1526        } else {
1527            None
1528        }
1529    }
1530}
1531
1532/// An iterator over the sequence part of a Lua table.
1533///
1534/// This struct is created by the [`Table::sequence_values`] method.
1535///
1536/// [`Table::sequence_values`]: crate::Table::sequence_values
1537pub struct TableSequence<'a, V> {
1538    guard: LuaGuard,
1539    table: &'a Table,
1540    index: Integer,
1541    len: Option<usize>,
1542    _phantom: PhantomData<V>,
1543}
1544
1545impl<V: FromLua> Iterator for TableSequence<'_, V> {
1546    type Item = Result<V>;
1547
1548    fn next(&mut self) -> Option<Self::Item> {
1549        let lua: &RawLua = &self.guard;
1550        let state = lua.state();
1551        unsafe {
1552            let _sg = StackGuard::new(state);
1553            if let Err(err) = check_stack(state, 1) {
1554                return Some(Err(err));
1555            }
1556
1557            lua.push_ref(&self.table.0);
1558            match ffi::lua_rawgeti(state, -1, self.index) {
1559                ffi::LUA_TNIL if self.index as usize > self.len.unwrap_or(0) => None,
1560                _ => {
1561                    self.index += 1;
1562                    Some(V::from_stack(-1, lua))
1563                }
1564            }
1565        }
1566    }
1567}
1568
1569#[cfg(test)]
1570mod assertions {
1571    use super::*;
1572
1573    #[cfg(not(feature = "send"))]
1574    static_assertions::assert_not_impl_any!(Table: Send);
1575    #[cfg(feature = "send")]
1576    static_assertions::assert_impl_all!(Table: Send, Sync);
1577}