mlua-rs/rlua

How to read _ENV after script execution?

singalen opened this issue · 2 comments

We have Chunk::set_environment() to set _ENV.
(Initially I expected lua_ctx.globals().set() to work, but apparently it's not the way).

Now, I'd like to know all the global variables that user has set, so I want to read _ENV back, but apparently there is no way? lua_ctx.globals().get("_ENV") returns an empty Table.

What am I missing? Thank you.

Hi,
The way _ENV works in Lua definitely causes confusion - it's not exactly the same as global variables.
As a quick summary, loading a chunk of code like x = 3*y the Chunk is a lot like defining a function where any references to non-locals are rewritten to refer to _ENV:

function()
    _ENV.x = 3*_ENV.y
end

_ENV by default is an upvalue which points to the same table as is returned by Context::globals(), but you can point that to anything else with Chunk::set_environment(). That doesn't affect Context::globals() at all, just what that chunk/function sees when executing.

It is possible in the low level Lua API to read back the upvalues from a chunk, but that isn't currently exposed in rlua. However, you can just keep a reference to it and read it after executing the chunk:

fn main() {
    Lua::new().context(|lua| {
        // Load some Lua code
        let chunk = lua.load(r#"
            print(x)
            x = 3
            y = 7
        "#);
        // Set up a new environment with some predefined data
        let new_env = lua.create_table()?;
        new_env.set("x", 1)?;
        let print_function: Function = lua.globals().get("print")?;
        // The code uses print()
        new_env.set("print", print_function)?;

        // The code will execute with new_env as the _ENV value,
        // which acts as the globals in that chunk.
        let chunk = chunk.set_environment(new_env.clone())?;

        // Actually parse and run it.  This should print "1"
        chunk.exec()?;

        let new_x: i32 = new_env.get("x")?;
        let new_y: i32 = new_env.get("y")?;
        println!("After executing, x={} y={}", new_x, new_y);

        Ok::<(), rlua::Error>(())
    }).unwrap();
}

If you need to keep a reference to the environment for longer than one Lua::context() call then the normal way to do that is to save it in the registry using Context::create_registry_value().

Great, thank you! I expected Table to be a value type, not "reference". But I should have guessed by the fact that it requires a lifetime.