从 Rust 中调用 Lua 函数出错:`*mut rlua::ffi::lua_State` 无法在线程之间安全共享

我正在开发一个命令行程序,用于使用 MiniJinja 库(由 mitsuhiko 创建)渲染模板文件。

程序在这里:https://github.com/benwilber/temple

我希望能够通过允许用户加载自定义 Lua 脚本来扩展程序,以便进行自定义过滤器、函数和测试。然而,我遇到了 Rust 生命周期错误,无法解决。

基本上,我希望能够将 Lua 函数注册为自定义过滤器函数。但是在编译时会出现错误。这是代码:

https://github.com/benwilber/temple/compare/0.3.1..lua

错误信息:

https://gist.github.com/c649a0b240cf299d3dbbe018c24cbcdc

如何从 MiniJinja 的 add_filter 函数中调用 Lua 函数?如果可能的话,我更愿意尝试在常规/安全的方式中解决这个问题。但是如果必须的话,我也可以尝试不安全的替代方案。

谢谢!

编辑:我在 Redditusers.rust-lang.org 上发布了相同的问题。

原文链接 https://stackoverflow.com/questions/69519948

点赞
stackoverflow用户221955
stackoverflow用户221955

Lua 使用的状态不安全,不能从多个线程中使用。由此导致 LuaFunction 不是 SyncSend

错误信息的一部分正在强制执行:

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)。

这意味着不能在调用 Filters 之间共享 LuaFunctions(甚至是 LuaContext)。

一种选择是不要将 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 的存根版本。您可能需要更好的错误处理,并可能需要一些额外的代码来处理干净地关闭所有线程。

2021-10-11 03:13:59