mlua 0.9.9

High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau with async/await features and support of writing native Lua modules in Rust.
Documentation
use std::collections::HashMap;
use std::os::raw::c_void;
use std::ptr;
use std::string::String as StdString;

use mlua::{Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, Value};

#[test]
fn test_value_eq() -> Result<()> {
    let lua = Lua::new();
    let globals = lua.globals();

    lua.load(
        r#"
        table1 = {1}
        table2 = {1}
        string1 = "hello"
        string2 = "hello"
        num1 = 1
        num2 = 1.0
        num3 = "1"
        func1 = function() end
        func2 = func1
        func3 = function() end
        thread1 = coroutine.create(function() end)
        thread2 = thread1

        setmetatable(table1, {
            __eq = function(a, b) return a[1] == b[1] end
        })
    "#,
    )
    .exec()?;
    globals.set("null", Value::NULL)?;

    let table1: Value = globals.get("table1")?;
    let table2: Value = globals.get("table2")?;
    let string1: Value = globals.get("string1")?;
    let string2: Value = globals.get("string2")?;
    let num1: Value = globals.get("num1")?;
    let num2: Value = globals.get("num2")?;
    let num3: Value = globals.get("num3")?;
    let func1: Value = globals.get("func1")?;
    let func2: Value = globals.get("func2")?;
    let func3: Value = globals.get("func3")?;
    let thread1: Value = globals.get("thread1")?;
    let thread2: Value = globals.get("thread2")?;
    let null: Value = globals.get("null")?;

    assert!(table1 != table2);
    assert!(table1.equals(&table2)?);
    assert!(string1 == string2);
    assert!(string1.equals(&string2)?);
    assert!(num1 == num2);
    assert!(num1.equals(num2)?);
    assert!(num1 != num3);
    assert!(func1 == func2);
    assert!(func1 != func3);
    assert!(!func1.equals(&func3)?);
    assert!(thread1 == thread2);
    assert!(thread1.equals(&thread2)?);
    assert!(null == Value::NULL);

    assert!(!table1.to_pointer().is_null());
    assert!(!ptr::eq(table1.to_pointer(), table2.to_pointer()));
    assert!(ptr::eq(string1.to_pointer(), string2.to_pointer()));
    assert!(ptr::eq(func1.to_pointer(), func2.to_pointer()));
    assert!(num1.to_pointer().is_null());

    Ok(())
}

#[test]
fn test_multi_value() {
    let mut multi_value = MultiValue::new();
    assert_eq!(multi_value.len(), 0);
    assert_eq!(multi_value.get(0), None);

    multi_value.push_front(Value::Number(2.));
    multi_value.push_front(Value::Number(1.));
    assert_eq!(multi_value.get(0), Some(&Value::Number(1.)));
    assert_eq!(multi_value.get(1), Some(&Value::Number(2.)));

    assert_eq!(multi_value.pop_front(), Some(Value::Number(1.)));
    assert_eq!(multi_value[0], Value::Number(2.));

    multi_value.clear();
    assert!(multi_value.is_empty());
}

#[test]
fn test_value_to_string() -> Result<()> {
    let lua = Lua::new();

    assert_eq!(Value::Nil.to_string()?, "nil");
    assert_eq!(Value::Boolean(true).to_string()?, "true");
    assert_eq!(Value::NULL.to_string()?, "null");
    assert_eq!(
        Value::LightUserData(LightUserData(0x1 as *const c_void as *mut _)).to_string()?,
        "lightuserdata: 0x1"
    );
    assert_eq!(Value::Integer(1).to_string()?, "1");
    assert_eq!(Value::Number(34.59).to_string()?, "34.59");
    #[cfg(all(feature = "luau", not(feature = "luau-vector4")))]
    assert_eq!(
        Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2)).to_string()?,
        "vector(10, 11.1, 12.2)"
    );
    #[cfg(feature = "luau-vector4")]
    assert_eq!(
        Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2, 13.3)).to_string()?,
        "vector(10, 11.1, 12.2, 13.3)"
    );
    assert_eq!(
        Value::String(lua.create_string("hello")?).to_string()?,
        "hello"
    );

    let table: Value = lua.load("{}").eval()?;
    assert!(table.to_string()?.starts_with("table:"));
    let table: Value = lua
        .load("setmetatable({}, {__tostring = function() return 'test table' end})")
        .eval()?;
    assert_eq!(table.to_string()?, "test table");

    let func: Value = lua.load("function() end").eval()?;
    assert!(func.to_string()?.starts_with("function:"));

    let thread: Value = lua.load("coroutine.create(function() end)").eval()?;
    assert!(thread.to_string()?.starts_with("thread:"));

    lua.register_userdata_type::<StdString>(|reg| {
        reg.add_meta_method("__tostring", |_, this, ()| Ok(this.clone()));
    })?;
    let ud: Value = Value::UserData(lua.create_any_userdata(String::from("string userdata"))?);
    assert_eq!(ud.to_string()?, "string userdata");

    struct MyUserData;
    impl UserData for MyUserData {}
    let ud: Value = Value::UserData(lua.create_userdata(MyUserData)?);
    assert!(ud.to_string()?.starts_with("MyUserData:"));

    let err = Value::Error(Error::runtime("test error"));
    assert_eq!(err.to_string()?, "runtime error: test error");

    Ok(())
}

#[test]
fn test_debug_format() -> Result<()> {
    let lua = Lua::new();

    lua.register_userdata_type::<HashMap<i32, StdString>>(|_| {})?;
    let ud = lua
        .create_any_userdata::<HashMap<i32, StdString>>(HashMap::new())
        .map(Value::UserData)?;
    assert!(format!("{ud:#?}").starts_with("HashMap<i32, String>:"));

    Ok(())
}

#[test]
fn test_value_conversions() -> Result<()> {
    let lua = Lua::new();

    assert!(Value::Nil.is_nil());
    assert!(!Value::NULL.is_nil());
    assert!(Value::NULL.is_null());
    assert!(Value::NULL.is_light_userdata());
    assert!(Value::NULL.as_light_userdata() == Some(LightUserData(ptr::null_mut())));
    assert!(Value::Boolean(true).is_boolean());
    assert_eq!(Value::Boolean(false).as_boolean(), Some(false));
    assert!(Value::Integer(1).is_integer());
    assert_eq!(Value::Integer(1).as_integer(), Some(1));
    assert_eq!(Value::Integer(1).as_i32(), Some(1i32));
    assert_eq!(Value::Integer(1).as_u32(), Some(1u32));
    assert_eq!(Value::Integer(1).as_i64(), Some(1i64));
    assert_eq!(Value::Integer(1).as_u64(), Some(1u64));
    #[cfg(any(feature = "lua54", feature = "lua53"))]
    {
        assert_eq!(Value::Integer(mlua::Integer::MAX).as_i32(), None);
        assert_eq!(Value::Integer(mlua::Integer::MAX).as_u32(), None);
    }
    assert_eq!(Value::Integer(1).as_isize(), Some(1isize));
    assert_eq!(Value::Integer(1).as_usize(), Some(1usize));
    assert!(Value::Number(1.23).is_number());
    assert_eq!(Value::Number(1.23).as_number(), Some(1.23));
    assert_eq!(Value::Number(1.23).as_f32(), Some(1.23f32));
    assert_eq!(Value::Number(1.23).as_f64(), Some(1.23f64));
    assert!(Value::String(lua.create_string("hello")?).is_string());
    assert_eq!(
        Value::String(lua.create_string("hello")?)
            .as_string()
            .unwrap(),
        "hello"
    );
    assert_eq!(
        Value::String(lua.create_string("hello")?).as_str().unwrap(),
        "hello"
    );
    assert_eq!(
        Value::String(lua.create_string("hello")?)
            .as_string_lossy()
            .unwrap(),
        "hello"
    );
    assert!(Value::Table(lua.create_table()?).is_table());
    assert!(Value::Table(lua.create_table()?).as_table().is_some());
    assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()).is_function());
    assert!(
        Value::Function(lua.create_function(|_, ()| Ok(())).unwrap())
            .as_function()
            .is_some()
    );
    assert!(Value::Thread(lua.create_thread(lua.load("function() end").eval()?)?).is_thread());
    assert!(
        Value::Thread(lua.create_thread(lua.load("function() end").eval()?)?)
            .as_thread()
            .is_some()
    );
    assert!(Value::UserData(lua.create_any_userdata("hello")?).is_userdata());
    assert_eq!(
        Value::UserData(lua.create_any_userdata("hello")?)
            .as_userdata()
            .and_then(|ud| ud.borrow::<&str>().ok())
            .as_deref(),
        Some(&"hello")
    );

    Ok(())
}