Skip to main content

mlua/
value.rs

1use std::cmp::Ordering;
2use std::collections::HashSet;
3use std::os::raw::c_void;
4use std::{fmt, ptr, str};
5
6use num_traits::FromPrimitive;
7
8use crate::error::{Error, Result};
9use crate::function::Function;
10use crate::string::{BorrowedStr, LuaString};
11use crate::table::Table;
12use crate::thread::Thread;
13use crate::types::{Integer, LightUserData, Number, ValueRef};
14use crate::userdata::AnyUserData;
15use crate::util::{StackGuard, check_stack};
16
17#[cfg(feature = "serde")]
18use {
19    crate::table::SerializableTable,
20    rustc_hash::FxHashSet,
21    serde::ser::{self, Serialize, Serializer},
22    std::{cell::RefCell, rc::Rc, result::Result as StdResult},
23};
24
25/// A dynamically typed Lua value.
26///
27/// The non-primitive variants (eg. string/table/function/thread/userdata) contain handle types
28/// into the internal Lua state. It is a logic error to mix handle types between separate
29/// `Lua` instances, and doing so will result in a panic.
30#[derive(Clone, Default)]
31pub enum Value {
32    /// The Lua value `nil`.
33    #[default]
34    Nil,
35    /// The Lua value `true` or `false`.
36    Boolean(bool),
37    /// A "light userdata" object, equivalent to a raw pointer.
38    LightUserData(LightUserData),
39    /// An integer number.
40    ///
41    /// Any Lua number convertible to a `Integer` will be represented as this variant.
42    Integer(Integer),
43    /// A floating point number.
44    Number(Number),
45    /// A Luau vector.
46    #[cfg(any(feature = "luau", doc))]
47    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
48    Vector(crate::Vector),
49    /// An interned string, managed by Lua.
50    ///
51    /// Unlike Rust strings, Lua strings may not be valid UTF-8.
52    String(LuaString),
53    /// Reference to a Lua table.
54    Table(Table),
55    /// Reference to a Lua function (or closure).
56    Function(Function),
57    /// Reference to a Lua thread (or coroutine).
58    Thread(Thread),
59    /// Reference to a userdata object that holds a custom type which implements `UserData`.
60    ///
61    /// Special builtin userdata types will be represented as other `Value` variants.
62    UserData(AnyUserData),
63    /// A Luau buffer.
64    #[cfg(any(feature = "luau", doc))]
65    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
66    Buffer(crate::Buffer),
67    /// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
68    Error(Box<Error>),
69    /// Any other value not known to mlua (eg. LuaJIT CData).
70    Other(#[doc(hidden)] ValueRef),
71}
72
73pub use self::Value::Nil;
74
75impl Value {
76    /// A special value (lightuserdata) to represent null value.
77    ///
78    /// It can be used in Lua tables without downsides of `nil`.
79    pub const NULL: Value = Value::LightUserData(LightUserData(ptr::null_mut()));
80
81    /// Returns type name of this value.
82    pub fn type_name(&self) -> &'static str {
83        match *self {
84            Value::Nil => "nil",
85            Value::Boolean(_) => "boolean",
86            Value::LightUserData(_) => "lightuserdata",
87            Value::Integer(_) => "integer",
88            Value::Number(_) => "number",
89            #[cfg(feature = "luau")]
90            Value::Vector(_) => "vector",
91            Value::String(_) => "string",
92            Value::Table(_) => "table",
93            Value::Function(_) => "function",
94            Value::Thread(_) => "thread",
95            Value::UserData(_) => "userdata",
96            #[cfg(feature = "luau")]
97            Value::Buffer(_) => "buffer",
98            Value::Error(_) => "error",
99            Value::Other(_) => "other",
100        }
101    }
102
103    /// Compares two values for equality.
104    ///
105    /// Equality comparisons do not convert strings to numbers or vice versa.
106    /// Tables, functions, threads, and userdata are compared by reference:
107    /// two objects are considered equal only if they are the same object.
108    ///
109    /// If table or userdata have `__eq` metamethod then mlua will try to invoke it.
110    /// The first value is checked first. If that value does not define a metamethod
111    /// for `__eq`, then mlua will check the second value.
112    /// Then mlua calls the metamethod with the two values as arguments, if found.
113    pub fn equals(&self, other: &Self) -> Result<bool> {
114        match (self, other) {
115            (Value::Table(a), Value::Table(b)) => a.equals(b),
116            (Value::UserData(a), Value::UserData(b)) => a.equals(b),
117            (a, b) => Ok(a == b),
118        }
119    }
120
121    /// Converts the value to a generic C pointer.
122    ///
123    /// The value can be a userdata, a table, a thread, a string, or a function; otherwise it
124    /// returns NULL. Different objects will give different pointers.
125    /// There is no way to convert the pointer back to its original value.
126    ///
127    /// Typically this function is used only for hashing and debug information.
128    #[inline]
129    pub fn to_pointer(&self) -> *const c_void {
130        match self {
131            Value::String(LuaString(vref)) => {
132                // In Lua < 5.4 (excluding Luau), string pointers are NULL
133                // Use alternative approach
134                let lua = vref.lua.lock();
135                unsafe { ffi::lua_tostring(lua.ref_thread(), vref.index) as *const c_void }
136            }
137            Value::LightUserData(ud) => ud.0,
138            Value::Table(Table(vref))
139            | Value::Function(Function(vref))
140            | Value::Thread(Thread(vref, ..))
141            | Value::UserData(AnyUserData(vref))
142            | Value::Other(vref) => vref.to_pointer(),
143            #[cfg(feature = "luau")]
144            Value::Buffer(crate::Buffer(vref)) => vref.to_pointer(),
145            _ => ptr::null(),
146        }
147    }
148
149    /// Converts the value to a string.
150    ///
151    /// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables,
152    /// functions).
153    pub fn to_string(&self) -> Result<String> {
154        unsafe fn invoke_tostring(vref: &ValueRef) -> Result<String> {
155            let lua = vref.lua.lock();
156            let state = lua.state();
157            let _guard = StackGuard::new(state);
158            check_stack(state, 3)?;
159
160            lua.push_ref(vref);
161            protect_lua!(state, 1, 1, fn(state) {
162                ffi::luaL_tolstring(state, -1, ptr::null_mut());
163            })?;
164            Ok(LuaString(lua.pop_ref()).to_str()?.to_string())
165        }
166
167        match self {
168            Value::Nil => Ok("nil".to_string()),
169            Value::Boolean(b) => Ok(b.to_string()),
170            Value::LightUserData(ud) if ud.0.is_null() => Ok("null".to_string()),
171            Value::LightUserData(ud) => Ok(format!("lightuserdata: {:p}", ud.0)),
172            Value::Integer(i) => Ok(i.to_string()),
173            Value::Number(n) => Ok(n.to_string()),
174            #[cfg(feature = "luau")]
175            Value::Vector(v) => Ok(v.to_string()),
176            Value::String(s) => Ok(s.to_str()?.to_string()),
177            Value::Table(Table(vref))
178            | Value::Function(Function(vref))
179            | Value::Thread(Thread(vref, ..))
180            | Value::UserData(AnyUserData(vref))
181            | Value::Other(vref) => unsafe { invoke_tostring(vref) },
182            #[cfg(feature = "luau")]
183            Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_tostring(vref) },
184            Value::Error(err) => Ok(err.to_string()),
185        }
186    }
187
188    /// Returns `true` if the value is a [`Nil`].
189    #[inline]
190    pub fn is_nil(&self) -> bool {
191        self == &Nil
192    }
193
194    /// Returns `true` if the value is a [`NULL`].
195    ///
196    /// [`NULL`]: Value::NULL
197    #[inline]
198    pub fn is_null(&self) -> bool {
199        self == &Self::NULL
200    }
201
202    /// Returns `true` if the value is a boolean.
203    #[inline]
204    pub fn is_boolean(&self) -> bool {
205        self.as_boolean().is_some()
206    }
207
208    /// Cast the value to boolean.
209    ///
210    /// If the value is a Boolean, returns it or `None` otherwise.
211    #[inline]
212    pub fn as_boolean(&self) -> Option<bool> {
213        match *self {
214            Value::Boolean(b) => Some(b),
215            _ => None,
216        }
217    }
218
219    /// Returns `true` if the value is a [`LightUserData`].
220    #[inline]
221    pub fn is_light_userdata(&self) -> bool {
222        self.as_light_userdata().is_some()
223    }
224
225    /// Cast the value to [`LightUserData`].
226    ///
227    /// If the value is a [`LightUserData`], returns it or `None` otherwise.
228    #[inline]
229    pub fn as_light_userdata(&self) -> Option<LightUserData> {
230        match *self {
231            Value::LightUserData(l) => Some(l),
232            _ => None,
233        }
234    }
235
236    /// Returns `true` if the value is an [`Integer`].
237    #[inline]
238    pub fn is_integer(&self) -> bool {
239        self.as_integer().is_some()
240    }
241
242    /// Cast the value to [`Integer`].
243    ///
244    /// If the value is a Lua [`Integer`], returns it or `None` otherwise.
245    #[inline]
246    pub fn as_integer(&self) -> Option<Integer> {
247        match *self {
248            Value::Integer(i) => Some(i),
249            _ => None,
250        }
251    }
252
253    /// Cast the value to `i32`.
254    ///
255    /// If the value is a Lua [`Integer`], try to convert it to `i32` or return `None` otherwise.
256    #[inline]
257    pub fn as_i32(&self) -> Option<i32> {
258        #[allow(clippy::useless_conversion)]
259        self.as_integer().and_then(|i| i32::try_from(i).ok())
260    }
261
262    /// Cast the value to `u32`.
263    ///
264    /// If the value is a Lua [`Integer`], try to convert it to `u32` or return `None` otherwise.
265    #[inline]
266    pub fn as_u32(&self) -> Option<u32> {
267        self.as_integer().and_then(|i| u32::try_from(i).ok())
268    }
269
270    /// Cast the value to `i64`.
271    ///
272    /// If the value is a Lua [`Integer`], try to convert it to `i64` or return `None` otherwise.
273    #[inline]
274    pub fn as_i64(&self) -> Option<i64> {
275        #[cfg(target_pointer_width = "64")]
276        return self.as_integer();
277        #[cfg(not(target_pointer_width = "64"))]
278        return self.as_integer().map(i64::from);
279    }
280
281    /// Cast the value to `u64`.
282    ///
283    /// If the value is a Lua [`Integer`], try to convert it to `u64` or return `None` otherwise.
284    #[inline]
285    pub fn as_u64(&self) -> Option<u64> {
286        self.as_integer().and_then(|i| u64::try_from(i).ok())
287    }
288
289    /// Cast the value to `isize`.
290    ///
291    /// If the value is a Lua [`Integer`], try to convert it to `isize` or return `None` otherwise.
292    #[inline]
293    pub fn as_isize(&self) -> Option<isize> {
294        self.as_integer().and_then(|i| isize::try_from(i).ok())
295    }
296
297    /// Cast the value to `usize`.
298    ///
299    /// If the value is a Lua [`Integer`], try to convert it to `usize` or return `None` otherwise.
300    #[inline]
301    pub fn as_usize(&self) -> Option<usize> {
302        self.as_integer().and_then(|i| usize::try_from(i).ok())
303    }
304
305    /// Returns `true` if the value is a Lua [`Number`].
306    #[inline]
307    pub fn is_number(&self) -> bool {
308        self.as_number().is_some()
309    }
310
311    /// Cast the value to [`Number`].
312    ///
313    /// If the value is a Lua [`Number`], returns it or `None` otherwise.
314    #[inline]
315    pub fn as_number(&self) -> Option<Number> {
316        match *self {
317            Value::Number(n) => Some(n),
318            _ => None,
319        }
320    }
321
322    /// Cast the value to `f32`.
323    ///
324    /// If the value is a Lua [`Number`], try to convert it to `f32` or return `None` otherwise.
325    #[inline]
326    pub fn as_f32(&self) -> Option<f32> {
327        self.as_number().and_then(f32::from_f64)
328    }
329
330    /// Cast the value to `f64`.
331    ///
332    /// If the value is a Lua [`Number`], try to convert it to `f64` or return `None` otherwise.
333    #[inline]
334    pub fn as_f64(&self) -> Option<f64> {
335        self.as_number()
336    }
337
338    /// Returns `true` if the value is a [`LuaString`].
339    #[inline]
340    pub fn is_string(&self) -> bool {
341        self.as_string().is_some()
342    }
343
344    /// Cast the value to a [`LuaString`].
345    ///
346    /// If the value is a [`LuaString`], returns it or `None` otherwise.
347    #[inline]
348    pub fn as_string(&self) -> Option<&LuaString> {
349        match self {
350            Value::String(s) => Some(s),
351            _ => None,
352        }
353    }
354
355    /// Cast the value to [`BorrowedStr`].
356    ///
357    /// If the value is a [`LuaString`], try to convert it to [`BorrowedStr`] or return `None`
358    /// otherwise.
359    #[deprecated(
360        since = "0.11.0",
361        note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead."
362    )]
363    #[inline]
364    pub fn as_str(&self) -> Option<BorrowedStr> {
365        self.as_string().and_then(|s| s.to_str().ok())
366    }
367
368    /// Cast the value to [`String`].
369    ///
370    /// If the value is a [`LuaString`], converts it to [`String`] or returns `None` otherwise.
371    #[deprecated(
372        since = "0.11.0",
373        note = "This method does not follow Rust naming convention. Use `as_string().map(|s| s.to_string_lossy())` instead."
374    )]
375    #[inline]
376    pub fn as_string_lossy(&self) -> Option<String> {
377        self.as_string().map(|s| s.to_string_lossy())
378    }
379
380    /// Returns `true` if the value is a Lua [`Table`].
381    #[inline]
382    pub fn is_table(&self) -> bool {
383        self.as_table().is_some()
384    }
385
386    /// Cast the value to [`Table`].
387    ///
388    /// If the value is a Lua [`Table`], returns it or `None` otherwise.
389    #[inline]
390    pub fn as_table(&self) -> Option<&Table> {
391        match self {
392            Value::Table(t) => Some(t),
393            _ => None,
394        }
395    }
396
397    /// Returns `true` if the value is a Lua [`Thread`].
398    #[inline]
399    pub fn is_thread(&self) -> bool {
400        self.as_thread().is_some()
401    }
402
403    /// Cast the value to [`Thread`].
404    ///
405    /// If the value is a Lua [`Thread`], returns it or `None` otherwise.
406    #[inline]
407    pub fn as_thread(&self) -> Option<&Thread> {
408        match self {
409            Value::Thread(t) => Some(t),
410            _ => None,
411        }
412    }
413
414    /// Returns `true` if the value is a Lua [`Function`].
415    #[inline]
416    pub fn is_function(&self) -> bool {
417        self.as_function().is_some()
418    }
419
420    /// Cast the value to [`Function`].
421    ///
422    /// If the value is a Lua [`Function`], returns it or `None` otherwise.
423    #[inline]
424    pub fn as_function(&self) -> Option<&Function> {
425        match self {
426            Value::Function(f) => Some(f),
427            _ => None,
428        }
429    }
430
431    /// Returns `true` if the value is an [`AnyUserData`].
432    #[inline]
433    pub fn is_userdata(&self) -> bool {
434        self.as_userdata().is_some()
435    }
436
437    /// Cast the value to [`AnyUserData`].
438    ///
439    /// If the value is an [`AnyUserData`], returns it or `None` otherwise.
440    #[inline]
441    pub fn as_userdata(&self) -> Option<&AnyUserData> {
442        match self {
443            Value::UserData(ud) => Some(ud),
444            _ => None,
445        }
446    }
447
448    /// Cast the value to a [`Buffer`].
449    ///
450    /// If the value is [`Buffer`], returns it or `None` otherwise.
451    ///
452    /// [`Buffer`]: crate::Buffer
453    #[cfg(any(feature = "luau", doc))]
454    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
455    #[inline]
456    pub fn as_buffer(&self) -> Option<&crate::Buffer> {
457        match self {
458            Value::Buffer(b) => Some(b),
459            _ => None,
460        }
461    }
462
463    /// Returns `true` if the value is a [`Buffer`].
464    ///
465    /// [`Buffer`]: crate::Buffer
466    #[cfg(any(feature = "luau", doc))]
467    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
468    #[inline]
469    pub fn is_buffer(&self) -> bool {
470        self.as_buffer().is_some()
471    }
472
473    /// Returns `true` if the value is an [`Error`].
474    #[inline]
475    pub fn is_error(&self) -> bool {
476        self.as_error().is_some()
477    }
478
479    /// Cast the value to [`Error`].
480    ///
481    /// If the value is an [`Error`], returns it or `None` otherwise.
482    pub fn as_error(&self) -> Option<&Error> {
483        match self {
484            Value::Error(e) => Some(e),
485            _ => None,
486        }
487    }
488
489    /// Wrap reference to this Value into [`SerializableValue`].
490    ///
491    /// This allows customizing serialization behavior using serde.
492    #[cfg(feature = "serde")]
493    #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
494    pub fn to_serializable(&self) -> SerializableValue<'_> {
495        SerializableValue::new(self, Default::default(), None)
496    }
497
498    // Compares two values.
499    // Used to sort values for Debug printing.
500    pub(crate) fn sort_cmp(&self, other: &Self) -> Ordering {
501        fn cmp_num(a: Number, b: Number) -> Ordering {
502            match (a, b) {
503                _ if a < b => Ordering::Less,
504                _ if a > b => Ordering::Greater,
505                _ => Ordering::Equal,
506            }
507        }
508
509        match (self, other) {
510            // Nil
511            (Value::Nil, Value::Nil) => Ordering::Equal,
512            (Value::Nil, _) => Ordering::Less,
513            (_, Value::Nil) => Ordering::Greater,
514            // Null (a special case)
515            (Value::LightUserData(ud1), Value::LightUserData(ud2)) if ud1 == ud2 => Ordering::Equal,
516            (Value::LightUserData(ud1), _) if ud1.0.is_null() => Ordering::Less,
517            (_, Value::LightUserData(ud2)) if ud2.0.is_null() => Ordering::Greater,
518            // Boolean
519            (Value::Boolean(a), Value::Boolean(b)) => a.cmp(b),
520            (Value::Boolean(_), _) => Ordering::Less,
521            (_, Value::Boolean(_)) => Ordering::Greater,
522            // Integer && Number
523            (Value::Integer(a), Value::Integer(b)) => a.cmp(b),
524            (Value::Integer(a), Value::Number(b)) => cmp_num(*a as Number, *b),
525            (Value::Number(a), Value::Integer(b)) => cmp_num(*a, *b as Number),
526            (Value::Number(a), Value::Number(b)) => cmp_num(*a, *b),
527            (Value::Integer(_) | Value::Number(_), _) => Ordering::Less,
528            (_, Value::Integer(_) | Value::Number(_)) => Ordering::Greater,
529            // Vector (Luau)
530            #[cfg(feature = "luau")]
531            (Value::Vector(a), Value::Vector(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
532            // String
533            (Value::String(a), Value::String(b)) => a.as_bytes().cmp(&b.as_bytes()),
534            (Value::String(_), _) => Ordering::Less,
535            (_, Value::String(_)) => Ordering::Greater,
536            // Other variants can be ordered by their pointer
537            (a, b) => a.to_pointer().cmp(&b.to_pointer()),
538        }
539    }
540
541    pub(crate) fn fmt_pretty(
542        &self,
543        fmt: &mut fmt::Formatter,
544        recursive: bool,
545        ident: usize,
546        visited: &mut HashSet<*const c_void>,
547    ) -> fmt::Result {
548        match self {
549            Value::Nil => write!(fmt, "nil"),
550            Value::Boolean(b) => write!(fmt, "{b}"),
551            Value::LightUserData(ud) if ud.0.is_null() => write!(fmt, "null"),
552            Value::LightUserData(ud) => write!(fmt, "lightuserdata: {:?}", ud.0),
553            Value::Integer(i) => write!(fmt, "{i}"),
554            Value::Number(n) => write!(fmt, "{n}"),
555            #[cfg(feature = "luau")]
556            Value::Vector(v) => write!(fmt, "{v}"),
557            Value::String(s) => write!(fmt, "{s:?}"),
558            Value::Table(t) if recursive && !visited.contains(&t.to_pointer()) => {
559                t.fmt_pretty(fmt, ident, visited)
560            }
561            t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()),
562            f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()),
563            t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()),
564            Value::UserData(ud) => ud.fmt_pretty(fmt),
565            #[cfg(feature = "luau")]
566            buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()),
567            Value::Error(e) if recursive => write!(fmt, "{e:?}"),
568            Value::Error(_) => write!(fmt, "error"),
569            Value::Other(v) => write!(fmt, "other: {:?}", v.to_pointer()),
570        }
571    }
572}
573
574impl fmt::Debug for Value {
575    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
576        if fmt.alternate() {
577            return self.fmt_pretty(fmt, true, 0, &mut HashSet::new());
578        }
579
580        match self {
581            Value::Nil => write!(fmt, "Nil"),
582            Value::Boolean(b) => write!(fmt, "Boolean({b})"),
583            Value::LightUserData(ud) => write!(fmt, "{ud:?}"),
584            Value::Integer(i) => write!(fmt, "Integer({i})"),
585            Value::Number(n) => write!(fmt, "Number({n})"),
586            #[cfg(feature = "luau")]
587            Value::Vector(v) => write!(fmt, "{v:?}"),
588            Value::String(s) => write!(fmt, "String({s:?})"),
589            Value::Table(t) => write!(fmt, "{t:?}"),
590            Value::Function(f) => write!(fmt, "{f:?}"),
591            Value::Thread(t) => write!(fmt, "{t:?}"),
592            Value::UserData(ud) => write!(fmt, "{ud:?}"),
593            #[cfg(feature = "luau")]
594            Value::Buffer(buf) => write!(fmt, "{buf:?}"),
595            Value::Error(e) => write!(fmt, "Error({e:?})"),
596            Value::Other(v) => write!(fmt, "Other({v:?})"),
597        }
598    }
599}
600
601impl PartialEq for Value {
602    fn eq(&self, other: &Self) -> bool {
603        match (self, other) {
604            (Value::Nil, Value::Nil) => true,
605            (Value::Boolean(a), Value::Boolean(b)) => a == b,
606            (Value::LightUserData(a), Value::LightUserData(b)) => a == b,
607            (Value::Integer(a), Value::Integer(b)) => *a == *b,
608            (Value::Integer(a), Value::Number(b)) => *a as Number == *b,
609            (Value::Number(a), Value::Integer(b)) => *a == *b as Number,
610            (Value::Number(a), Value::Number(b)) => *a == *b,
611            #[cfg(feature = "luau")]
612            (Value::Vector(v1), Value::Vector(v2)) => v1 == v2,
613            (Value::String(a), Value::String(b)) => a == b,
614            (Value::Table(a), Value::Table(b)) => a == b,
615            (Value::Function(a), Value::Function(b)) => a == b,
616            (Value::Thread(a), Value::Thread(b)) => a == b,
617            (Value::UserData(a), Value::UserData(b)) => a == b,
618            #[cfg(feature = "luau")]
619            (Value::Buffer(a), Value::Buffer(b)) => a == b,
620            _ => false,
621        }
622    }
623}
624
625/// A wrapped [`Value`] with customized serialization behavior.
626#[cfg(feature = "serde")]
627#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
628pub struct SerializableValue<'a> {
629    value: &'a Value,
630    options: crate::serde::de::Options,
631    // In many cases we don't need `visited` map, so don't allocate memory by default
632    visited: Option<Rc<RefCell<FxHashSet<*const c_void>>>>,
633}
634
635#[cfg(feature = "serde")]
636impl Serialize for Value {
637    #[inline]
638    fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
639        SerializableValue::new(self, Default::default(), None).serialize(serializer)
640    }
641}
642
643#[cfg(feature = "serde")]
644impl<'a> SerializableValue<'a> {
645    #[inline]
646    pub(crate) fn new(
647        value: &'a Value,
648        options: crate::serde::de::Options,
649        visited: Option<&Rc<RefCell<FxHashSet<*const c_void>>>>,
650    ) -> Self {
651        if let Value::Table(_) = value {
652            return Self {
653                value,
654                options,
655                // We need to always initialize the `visited` map for Tables
656                visited: visited.cloned().or_else(|| Some(Default::default())),
657            };
658        }
659        Self {
660            value,
661            options,
662            visited: None,
663        }
664    }
665
666    /// If true, an attempt to serialize types such as [`Function`], [`Thread`], [`LightUserData`]
667    /// and [`Error`] will cause an error.
668    /// Otherwise these types skipped when iterating or serialized as unit type.
669    ///
670    /// Default: **true**
671    #[must_use]
672    pub fn deny_unsupported_types(mut self, enabled: bool) -> Self {
673        self.options.deny_unsupported_types = enabled;
674        self
675    }
676
677    /// If true, an attempt to serialize a recursive table (table that refers to itself)
678    /// will cause an error.
679    /// Otherwise subsequent attempts to serialize the same table will be ignored.
680    ///
681    /// Default: **true**
682    #[must_use]
683    pub fn deny_recursive_tables(mut self, enabled: bool) -> Self {
684        self.options.deny_recursive_tables = enabled;
685        self
686    }
687
688    /// If true, keys in tables will be iterated (and serialized) in sorted order.
689    ///
690    /// Default: **false**
691    #[must_use]
692    pub fn sort_keys(mut self, enabled: bool) -> Self {
693        self.options.sort_keys = enabled;
694        self
695    }
696
697    /// If true, empty Lua tables will be encoded as array, instead of map.
698    ///
699    /// Default: **false**
700    #[must_use]
701    pub fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
702        self.options.encode_empty_tables_as_array = enabled;
703        self
704    }
705
706    /// If true, enable detection of mixed tables.
707    ///
708    /// A mixed table is a table that has both array-like and map-like entries or several borders.
709    ///
710    /// Default: **false**
711    #[must_use]
712    pub fn detect_mixed_tables(mut self, enabled: bool) -> Self {
713        self.options.detect_mixed_tables = enabled;
714        self
715    }
716}
717
718#[cfg(feature = "serde")]
719impl Serialize for SerializableValue<'_> {
720    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
721    where
722        S: Serializer,
723    {
724        match self.value {
725            Value::Nil => serializer.serialize_unit(),
726            Value::Boolean(b) => serializer.serialize_bool(*b),
727            #[allow(clippy::useless_conversion)]
728            Value::Integer(i) => serializer.serialize_i64((*i).into()),
729            Value::Number(n) => serializer.serialize_f64(*n),
730            #[cfg(feature = "luau")]
731            Value::Vector(v) => v.serialize(serializer),
732            Value::String(s) => s.serialize(serializer),
733            Value::Table(t) => {
734                let visited = self.visited.as_ref().unwrap().clone();
735                SerializableTable::new(t, self.options, visited).serialize(serializer)
736            }
737            Value::LightUserData(ud) if ud.0.is_null() => serializer.serialize_none(),
738            Value::UserData(ud) if ud.is_serializable() || self.options.deny_unsupported_types => {
739                ud.serialize(serializer)
740            }
741            #[cfg(feature = "luau")]
742            Value::Buffer(buf) => buf.serialize(serializer),
743            Value::Function(_)
744            | Value::Thread(_)
745            | Value::UserData(_)
746            | Value::LightUserData(_)
747            | Value::Error(_)
748            | Value::Other(_) => {
749                if self.options.deny_unsupported_types {
750                    let msg = format!("cannot serialize <{}>", self.value.type_name());
751                    Err(ser::Error::custom(msg))
752                } else {
753                    serializer.serialize_unit()
754                }
755            }
756        }
757    }
758}
759
760#[cfg(test)]
761mod assertions {
762    use super::*;
763
764    #[cfg(not(feature = "send"))]
765    static_assertions::assert_not_impl_any!(Value: Send);
766    #[cfg(feature = "send")]
767    static_assertions::assert_impl_all!(Value: Send, Sync);
768}