rust - 从 Rust 调用 Lua 函数时出错:`*mut rlua::ffi::lua_State` 不能在线程之间安全共享
问题描述
我正在开发一个 CLI 程序,用于使用 mitsuhiko 的新MiniJinja库渲染模板文件。
该程序在这里:https ://github.com/benwilber/temple 。
我希望能够通过允许用户为自定义过滤器、函数和测试等内容加载自定义 Lua 脚本来扩展程序。但是,我遇到了我无法解决的 Rust 生命周期错误。
基本上,我希望能够将 Lua 函数注册为自定义过滤器函数。但是编译的时候会报错。这是代码:
https://github.com/benwilber/temple/compare/0.3.1..lua
错误:
https://gist.github.com/c649a0b240cf299d3dbbe018c24cbcdc
如何从 MiniJinja 函数中调用 Luaadd_filter
函数?我宁愿尝试以常规/安全的方式执行此操作。但如果需要,我对不安全的替代品持开放态度。
谢谢!
解决方案
Lua 使用的状态在多个线程中使用是不安全的。这样做的结果LuaFunction
是既不是 也不Sync
是Send
。
这由错误消息的这一部分强制执行:
help: within `LuaFunction<'_>`, the trait `Sync` is not implemented for `*mut rlua::ffi::lua_State`
相比之下,minijinja::Filter
必须实施Send + Sync + 'static
。(见https://docs.rs/minijinja/0.5.0/minijinja/filters/trait.Filter.html)
LuaFunctions
这意味着我们不能LuaContext
在对Filters
.
一种选择是不将您的 lua 状态传递给闭包,而是在每次调用时创建一个新的 lua 状态,就像这样。
env.add_filter(
"concat2",
|_env: &Environment, s1: String, s2: String|
-> anyhow::Result<String, minijinja::Error> {
lua.context(|lua_ctx| {
lua_ctx.load(include_str!("temple.lua")).exec().unwrap();
let globals = lua_ctx.globals();
let temple: rlua::Table = globals.get("temple").unwrap();
let filters: rlua::Table = temple.get("_filters").unwrap();
let concat2: rlua::Function = filters.get("concat2").unwrap();
let res: String = concat2.call::<_, String>((s1, s2)).unwrap();
Ok(res)
}
}
);
这可能具有相对较高的开销。
另一种选择是在一个线程中创建您的 rlua 状态并通过管道与其通信。这看起来更像这样:
pub fn test() {
let mut env = minijinja::Environment::new();
let (to_lua_tx, to_lua_rx) = channel::<(String,String,SyncSender<String>)>();
thread::spawn(move|| {
let lua = rlua::Lua::new();
lua.context(move |lua_ctx| {
lua_ctx.load("some_code").exec().unwrap();
let globals = lua_ctx.globals();
let temple: rlua::Table = globals.get("temple").unwrap();
let filters: rlua::Table = temple.get("_filters").unwrap();
let concat2: rlua::Function = filters.get("concat2").unwrap();
while let Ok((s1,s2, channel)) = to_lua_rx.recv() {
let res: String = concat2.call::<_, String>((s1, s2)).unwrap();
channel.send(res).unwrap()
}
})
});
let to_lua_tx = Mutex::new(to_lua_tx);
env.add_filter(
"concat2",
move |_env: &minijinja::Environment,
s1: String,
s2: String|
-> anyhow::Result<String, minijinja::Error> {
let (tx,rx) = sync_channel::<String>(0);
to_lua_tx.lock().unwrap().send((s1,s2,tx)).unwrap();
let res = rx.recv().unwrap();
Ok(res)
}
);
}
甚至可以以这种方式启动多个 lua 状态,但需要更多的管道。
免责声明:此代码均未经测试 - 但是,它是在 Playground 中使用minijinja和 rlua 的存根版本构建的。您可能想要更好的错误处理,并且可能需要一些额外的代码来处理干净地关闭所有线程。
推荐阅读
- javascript - 当 Html 复选框未选中时,有什么方法可以在 Javascript 中触发事件?
- python - 如何处理通过 HTTP 请求发送的类实例?
- mysql - mySQL 对非常大的表进行分组以消除重复
- javascript - 如何使用来自 json 对象的数据填充“多选”?
- gradle - 当我的应用程序依赖于依赖于不同 micronaut 版本的库时,找不到匹配的 micronaut-bom 变体
- intellij-idea - 有没有办法用 Brew 安装旧版本的 IntelliJ?
- flutter - 在 Android 移动应用中实现多个过滤器的简单方法
- python - ValueError:linprog 的输入无效
- python - 工作目录未配置 Visual Studio 2019
- docker - 在 docker 中设置 zeppelin 而不使用内置图像