首页 > 解决方案 > 从 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函数?我宁愿尝试以常规/安全的方式执行此操作。但如果需要,我对不安全的替代品持开放态度。

谢谢!

编辑:在Redditusers.rust-lang.org上发布相同的内容

标签: rustlua

解决方案


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

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


推荐阅读