13个版本
0.5.3 | 2024年3月5日 |
---|---|
0.5.0 | 2024年1月29日 |
0.4.4 | 2023年11月19日 |
0.3.2 | 2023年6月6日 |
#389 在 无标准库
86 每月下载量
用于 udbg
360KB
9K SLoC
针对Lua5.4的舒适、高效且零成本的Rust绑定
功能
- 支持序列化(serde)
- 支持异步函数绑定
- 对函数和 userdata 方法进行舒适绑定
- 对栈值进行舒适操作,无需关注栈细节
- 高效:无需辅助栈,支持引用类型转换
- 内建绑定到大多数常用的 Rust std 函数和类型
- 支持多线程
- 支持nostd
限制
- 需要Nightly Rust编译器(1.76+)
- 目前仅支持Lua5.4
示例
使用方法
功能标志
async
:启用异步/await支持(可以使用任何执行器,例如[tokio]或[async-std])serde
:使用[serde]框架为ezlua
类型添加序列化和反序列化支持vendored
:在ezlua
编译期间从源代码构建静态Lua库使用[lua-src] cratethread
启用多线程支持std
:启用Rust std函数和类型的内建绑定json
:启用[serde_json] crate的内建绑定regex
:启用[regex] crate的内建绑定tokio
:启用[tokio] crate的内建绑定chrono
:启用[chrono] crate的内建绑定
基础
首先,在Cargo.toml中将ezlua添加到依赖项
[dependencies]
ezlua = { version = '0.5' }
然后,在Rust中使用ezlua,代码框架如下
use ezlua::prelude::*;
fn main() -> LuaResult<()> {
// create a lua VM
let lua = Lua::with_open_libs();
// load your lua script and execute it
lua.do_string(r#"function add(a, b) return a + b end"#, None)?;
// get function named add from lua global table
let add = lua.global().get("add")?;
// call add function and get its result
let result = add.pcall::<_, u32>((111, 222))?;
assert_eq!(result, 333);
// ... for the following code
Ok(())
}
绑定您的函数
当然,您可以通过ezlua绑定将Rust函数提供给Lua,非常简单,如下所示
lua.global().set("add", lua.new_closure(|a: u32, b: u32| a + b)?)?;
lua.do_string("assert(add(111, 222) == 333)", None)?;
并且您可以轻松地绑定现有函数
let string: LuaTable = lua.global().get("string")?.try_into()?;
string.set_closure("trim", str::trim)?;
string.set_closure("trim_start", str::trim_start)?;
string.set_closure("trim_end", str::trim_end)?;
let os: LuaTable = lua.global().get("os")?.try_into()?;
os.set_closure("mkdir", std::fs::create_dir::<&str>)?;
os.set_closure("mkdirs", std::fs::create_dir_all::<&str>)?;
os.set_closure("rmdir", std::fs::remove_dir::<&str>)?;
os.set_closure("chdir", std::env::set_current_dir::<&str>)?;
os.set_closure("getcwd", std::env::current_dir)?;
os.set_closure("getexe", std::env::current_exe)?;
绑定您的类型
为您的类型实现 ToLua
特性,然后您可以将其传递给 Lua
#[derive(Debug, Default)]
struct Config {
name: String,
path: String,
timeout: u64,
// ...
}
impl ToLua for Config {
fn to_lua<'a>(self, lua: &'a LuaState) -> LuaResult<ValRef<'a>> {
let conf = lua.new_table()?;
conf.set("name", self.name)?;
conf.set("path", self.path)?;
conf.set("timeout", self.timeout)?;
conf.to_lua(lua)
}
}
lua.global().set_closure("default_config", Config::default)?;
通过 serde 简单绑定
继续上面的示例,您可以通过 serde 简化绑定代码
use serde::{Deserialize, Serialize};
use ezlua::serde::SerdeValue;
#[derive(Debug, Default, Deserialize, Serialize)]
struct Config {
name: String,
path: String,
timeout: u64,
// ...
}
// You can use impl_tolua_as_serde macro to simply this after version v0.3.1
// ezlua::impl_tolua_as_serde!(Config);
impl ToLua for Config {
fn to_lua<'a>(self, lua: &'a LuaState) -> LuaResult<ValRef<'a>> {
SerdeValue(self).to_lua(lua)
}
}
// You can use impl_fromlua_as_serde macro to simply this after version v0.3.1
// ezlua::impl_fromlua_as_serde!(Config);
impl FromLua<'_> for Config {
fn from_lua(lua: &LuaState, val: ValRef) -> LuaResult<Self> {
SerdeValue::<Self>::from_lua(lua, val).map(|s| s.0)
}
}
lua.global().set("DEFAULT_CONFIG", SerdeValue(Config::default()))?;
lua.global()
.set_closure("set_config", |config: Config| {
// ... set your config
})?;
绑定自定义对象(userdata)
ezlua 的 userdata 绑定机制非常强大,以下代码来自 std 绑定
use std::{fs::Metadata, path::*};
impl UserData for Metadata {
fn getter(fields: UserdataRegistry<Self>) -> Result<()> {
fields.set_closure("size", Self::len)?;
fields.set_closure("modified", Self::modified)?;
fields.set_closure("created", Self::created)?;
fields.set_closure("accessed", Self::accessed)?;
fields.set_closure("readonly", |this: &Self| this.permissions().readonly())?;
Ok(())
}
fn methods(mt: UserdataRegistry<Self>) -> Result<()> {
mt.set_closure("len", Self::len)?;
mt.set_closure("is_dir", Self::is_dir)?;
mt.set_closure("is_file", Self::is_file)?;
mt.set_closure("is_symlink", Self::is_symlink)?;
Ok(())
}
}
实现了 UserData
特性的类型,ezlua 也为它实现了 ToLua
,并为它的引用实现了 FromLua
lua.global().set("path_metadata", Path::metadata)?;
默认情况下,绑定为 userdata 的类型是不可变的,如果您需要 可变引用,您可以指定一个 UserData::Trans
类型,其中有一个内建的实现是 RefCell
,所以可变绑定的实现如下所示
use core::cell::RefCell;
use std::process::{Child, Command, ExitStatus, Stdio};
impl UserData for Child {
type Trans = RefCell<Self>;
fn getter(fields: UserdataRegistry<Self>) -> LuaResult<()> {
fields.add("id", Self::id)?;
Ok(())
}
fn methods(mt: UserdataRegistry<Self>) -> Result<()> {
mt.add_mut("kill", Self::kill)?;
mt.add_mut("wait", Self::wait)?;
mt.add_mut("try_wait", |this: &mut Self| {
this.try_wait().ok().flatten().ok_or(())
})?;
}
}
在实现 UserData 特性时,通常只需要实现 getter
/ setter
/ methods
方法,这允许您通过 userdata 值“读取属性”/“写入属性”/“调用方法”,但 ezlua 还为 UserData 提供了 更强大的功能,例如“uservalue 访问”和“userdata 缓存”。
为了启用 userdata 类型的“uservalue 访问”功能,只需指定 const INDEX_USERVALUE: bool = true
struct Test {
a: i32,
}
impl UserData for Test {
type Trans = RefCell<Self>;
const INDEX_USERVALUE: bool = true;
fn methods(mt: UserdataRegistry<Self>) -> LuaResult<()> {
mt.set_closure("inc", |mut this: RefMut<Self>| this.a += 1)?;
Ok(())
}
}
let uv = lua.new_val(Test { a: 0 })?;
lua.global().set("uv", uv)?;
lua.do_string("uv.abc = 3; assert(uv.abc == 3)", None)?;
lua.do_string("assert(debug.getuservalue(uv).abc == 3)", None)?;
为了启用 userdata 类型的“userdata 缓存”功能,您应该实现 UserData::key_to_cache
方法,该方法返回一个指针,作为 Lua 缓存表中的 lightuserdata 键。
#[derive(derive_more::Deref, Clone)]
struct RcTest(Rc<Test>);
impl UserData for RcTest {
fn key_to_cache(&self) -> *const () {
self.as_ref() as *const _ as _
}
fn getter(fields: UserdataRegistry<Self>) -> LuaResult<()> {
fields.set_closure("a", |this: &Self| this.a)?;
Ok(())
}
fn methods(_: UserdataRegistry<Self>) -> LuaResult<()> {
Ok(())
}
}
let test = RcTest(Test { a: 123 }.into());
lua.global().set("uv", test.clone())?;
// when converting an UserData type to lua value, ezlua will first use the userdata in the cache table if existing,
// otherwise, create a new userdata and insert it to the cache table, so the "uv" and "uv1" will refer to the same userdata object
lua.global().set("uv1", test.clone())?;
lua.do_string("print(uv, uv1)", None)?;
lua.do_string("assert(uv == uv1)", None)?;
注册您自己的模块
要注册一个 Lua 模块,您可以通过 LuaState::register_module
方法提供一个返回 Lua 表的 Rust 函数
lua.register_module("json", ezlua::binding::json::open, false)?;
lua.register_module("path", |lua| {
let t = lua.new_table()?;
t.set_closure("dirname", Path::parent)?;
t.set_closure("exists", Path::exists)?;
t.set_closure("abspath", std::fs::canonicalize::<&str>)?;
t.set_closure("isabs", Path::is_absolute)?;
t.set_closure("isdir", Path::is_dir)?;
t.set_closure("isfile", Path::is_file)?;
t.set_closure("issymlink", Path::is_symlink)?;
return Ok(t);
}, false)?;
然后在 Lua 中使用它们
local json = require 'json'
local path = require 'path'
local dir = path.abspath('.')
assert(json.load(json.dump(dir)) == dir)
多线程使用
要在 Lua 中使用多线程功能,您需要在 Cargo.toml 中指定 thread
功能,并使用 ezlua 的自定义 补丁 lua-src crate
[dependencies]
ezlua = { version = '0.3', features = ['thread'] }
[patch.crates-io]
lua-src = { git = "https://github.com/metaworm/lua-src-rs" }
然后,为 Lua 注册线程模块
lua.register_module("thread", ezlua::binding::std::thread::init, true)?;
然后,在 Lua 中使用它
local thread = require 'thread'
local threads = {}
local tt = { n = 0 }
local count = 64
for i = 1, count do
threads[i] = thread.spawn(function()
tt.n = tt.n + 1
-- print(tt.n)
end)
end
for i, t in ipairs(threads) do
t:join()
print('#' .. i .. ' finished')
end
assert(tt.n == count)
此外,您还可以使用相同的 Lua VM 启动一个新线程
let co = Coroutine::empty(&lua);
std::thread::spawn(move || {
let print = co.global().get("print")?;
print.pcall_void("running lua in another thread")?;
LuaResult::Ok(())
})
.join()
.unwrap();
模块模式
在模块模式下,ezlua
允许创建一个可编译的 Lua 模块,该模块可以使用 require
通过 Lua 代码加载。
首先,禁用默认的 vendored 功能,只保留 std 功能,并在 Cargo.toml
中将您的 crate 配置为 cdylib
[dependencies]
ezlua = {version = '0.3', default-features = false, features = ['std']}
[lib]
crate-type = ['cdylib']
然后,使用 ezlua::lua_module!
宏导出您的 luaopen_
函数,其中第一个参数是 luaopen_<Your module name>
use ezlua::prelude::*;
ezlua::lua_module!(luaopen_ezluamod, |lua| {
let module = lua.new_table()?;
module.set("_VERSION", "0.1.0")?;
// ... else module functions
return Ok(module);
});
内部设计
待办事项
依赖项
~0.5–11MB
~106K SLoC