diff --git a/Cargo.toml b/Cargo.toml index 15db628..7d12670 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,12 +16,8 @@ High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) with async/await features and support of writing native lua modules in Rust. """ -[badges] -# github-actions = { repository = "khvzak/mlua", workflow = "CI" } -maintenance = { status = "actively-developed" } - [package.metadata.docs.rs] -features = ["async", "send", "lua53"] +features = ["lua53", "async", "send", "serialize"] [workspace] members = [ @@ -88,3 +84,7 @@ required-features = ["async", "send"] [[example]] name = "async_tcp_server" required-features = ["async"] + +[[example]] +name = "serialize" +required-features = ["serialize"] diff --git a/README.md b/README.md index e9a7a53..7be1778 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,20 @@ Starting from v0.3, `mlua` supports async/await for all Lua versions. This works **Examples**: - [HTTP Client](examples/async_http_client.rs) +- [HTTP Client (json)](examples/async_http_reqwest.rs) - [HTTP Server](examples/async_http_server.rs) - [TCP Server](examples/async_tcp_server.rs) +### Serialization (serde) support + +With `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition `mlua` provides [`serde::Serialize`] trait implementation for it (including user data support). + +[Example](examples/serialize.rs) + +[`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html +[`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html +[`mlua::Value`]: https://docs.rs/mlua/latest/mlua/enum.Value.html + ### Compiling You have to enable one of the features `lua54`, `lua53`, `lua52`, `lua51` or `luajit`, according to the choosen Lua version. diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 7ded275..bf02c7a 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -5,7 +5,8 @@ #[cfg_attr( all(feature = "luajit", target_os = "macos", target_arch = "x86_64"), - link_args = "-pagezero_size 10000 -image_base 100000000" + link_args = "-pagezero_size 10000 -image_base 100000000", + allow(unused_attributes) )] extern "system" {} diff --git a/examples/async_http_client.rs b/examples/async_http_client.rs index 063c8b5..c536e90 100644 --- a/examples/async_http_client.rs +++ b/examples/async_http_client.rs @@ -61,7 +61,7 @@ async fn main() -> Result<()> { let f = lua .load( r#" - local res = fetch_url(...); + local res = fetch_url(...) print(res.status) for key, vals in pairs(res.headers) do for _, val in ipairs(vals) do diff --git a/examples/async_tcp_server.rs b/examples/async_tcp_server.rs index 6fcff6e..acd7b5e 100644 --- a/examples/async_tcp_server.rs +++ b/examples/async_tcp_server.rs @@ -63,7 +63,7 @@ impl UserData for LuaTcpStream { async fn run_server(lua: &'static Lua) -> Result<()> { let spawn = lua.create_function(move |_, func: Function| { - task::spawn_local(async move { func.call_async::<_, ()>(()).await.unwrap() }); + task::spawn_local(async move { func.call_async::<_, ()>(()).await }); Ok(()) })?; diff --git a/examples/guided_tour.rs b/examples/guided_tour.rs index dab9ac0..540d4b2 100644 --- a/examples/guided_tour.rs +++ b/examples/guided_tour.rs @@ -164,6 +164,33 @@ fn main() -> Result<()> { < f32::EPSILON ); + // Normally, Rust types passed to `Lua` must be `'static`, because there is no way to be + // sure of their lifetime inside the Lua state. There is, however, a limited way to lift this + // requirement. You can call `Lua::scope` to create userdata and callbacks types that only live + // for as long as the call to scope, but do not have to be `'static` (and `Send`). + + { + let mut rust_val = 0; + + lua.scope(|scope| { + // We create a 'sketchy' Lua callback that holds a mutable reference to the variable + // `rust_val`. Outside of a `Lua::scope` call, this would not be allowed + // because it could be unsafe. + + lua.globals().set( + "sketchy", + scope.create_function_mut(|_, ()| { + rust_val = 42; + Ok(()) + })?, + )?; + + lua.load("sketchy()").exec() + })?; + + assert_eq!(rust_val, 42); + } + // We were able to run our 'sketchy' function inside the scope just fine. However, if we // try to run our 'sketchy' function outside of the scope, the function we created will have // been invalidated and we will generate an error. If our function wasn't invalidated, we diff --git a/examples/serialize.rs b/examples/serialize.rs new file mode 100644 index 0000000..d1b9e42 --- /dev/null +++ b/examples/serialize.rs @@ -0,0 +1,66 @@ +use mlua::{Error, Lua, LuaSerdeExt, Result, UserData, Value}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +enum Transmission { + Manual, + Automatic, +} + +#[derive(Serialize, Deserialize)] +struct Engine { + v: u32, + kw: u32, +} + +#[derive(Serialize, Deserialize)] +struct Car { + active: bool, + model: String, + transmission: Transmission, + engine: Engine, +} + +impl UserData for Car {} + +fn main() -> Result<()> { + let lua = Lua::new(); + let globals = lua.globals(); + + // Create Car struct from a Lua table + let car: Car = lua.from_value(lua.load(r#" + {active = true, model = "Volkswagen Golf", transmission = "Automatic", engine = {v = 1499, kw = 90}} + "#).eval()?)?; + + // Set it as (serializable) userdata + globals.set("null", lua.null()?)?; + globals.set("array_mt", lua.array_metatable()?)?; + globals.set("car", lua.create_ser_userdata(car)?)?; + + // Create a Lua table with multiple data types + let val: Value = lua + .load(r#"{driver = "Boris", car = car, price = null, points = setmetatable({}, array_mt)}"#) + .eval()?; + + // Serialize the table above to JSON + let json_str = serde_json::to_string(&val).map_err(Error::external)?; + println!("{}", json_str); + + // Create Lua Value from JSON (or any serializable type) + let json = serde_json::json!({ + "key": "value", + "null": null, + "array": [], + }); + globals.set("json_value", lua.to_value(&json)?)?; + lua.load( + r#" + assert(json_value["key"] == "value") + assert(json_value["null"] == null) + assert(#(json_value["array"]) == 0) + "#, + ) + .exec()?; + + Ok(()) +} diff --git a/src/lua.rs b/src/lua.rs index 9946eed..3d3a93b 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -7,9 +7,6 @@ use std::os::raw::{c_char, c_int, c_void}; use std::sync::{Arc, Mutex, Weak}; use std::{mem, ptr, str}; -#[cfg(feature = "serialize")] -use serde::Serialize; - use crate::error::{Error, Result}; use crate::ffi; use crate::function::Function; @@ -25,11 +22,10 @@ use crate::types::{ }; use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods, UserDataWrapped}; use crate::util::{ - assert_stack, callback_error, check_stack, get_destructed_userdata_metatable, get_gc_userdata, - get_main_state, get_meta_gc_userdata, get_wrapped_error, init_error_registry, - init_gc_metatable_for, init_userdata_metatable, pop_error, protect_lua, protect_lua_closure, - push_gc_userdata, push_meta_gc_userdata, push_string, push_userdata, push_wrapped_error, - StackGuard, + assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state, + get_meta_gc_userdata, get_wrapped_error, init_error_registry, init_gc_metatable_for, + init_userdata_metatable, pop_error, protect_lua, protect_lua_closure, push_gc_userdata, + push_meta_gc_userdata, push_string, push_userdata, push_wrapped_error, StackGuard, }; use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value}; @@ -44,6 +40,9 @@ use { futures_util::future::{self, TryFutureExt}, }; +#[cfg(feature = "serialize")] +use {crate::util::get_destructed_userdata_metatable, serde::Serialize}; + /// Top level Lua struct which holds the Lua state itself. pub struct Lua { pub(crate) state: *mut ffi::lua_State, diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 75b2d94..a476d8f 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -65,10 +65,12 @@ pub trait LuaSerdeExt<'lua> { /// ``` fn array_metatable(&'lua self) -> Result>; - /// Convert a `T` into Lua `Value` instance. + /// Converts `T` into a `Value` instance. /// /// Requires `feature = "serialize"` /// + /// [`Value`]: enum.Value.html + /// /// # Example /// /// ``` @@ -96,10 +98,12 @@ pub trait LuaSerdeExt<'lua> { /// ``` fn to_value(&'lua self, t: &T) -> Result>; - /// Deserialize a Lua `Value` into any serde deserializable object. + /// Deserializes a `Value` into any serde deserializable object. /// /// Requires `feature = "serialize"` /// + /// [`Value`]: enum.Value.html + /// /// # Example /// /// ```