rhaiscript / rhai

Rhai - An embedded scripting language for Rust.
https://crates.io/crates/rhai
Apache License 2.0
3.73k stars 175 forks source link

How to setup some global variable? #543

Closed uuhan closed 2 years ago

uuhan commented 2 years ago

I want to setup some global variable for the further use, like this:

use rhai::{Engine, Module, ImmutableString, Scope};
use rhai::module_resolvers::FileModuleResolver;

pub fn main() -> Result<(), Box<rhai::EvalAltResult>> {
    let mut engine = Engine::new();

    // module resolver
    engine.set_module_resolver(FileModuleResolver::new_with_path("./lib/"));

    let script1 = r#"
        let obj = #{
            a: 100,
        };

        print(obj);

        export obj;
    "#;
    let ast = engine.compile(script)?;
    let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;

    // module
    engine.register_global_module(module.into());

    let script2 = r#"
        print(`global: ${obj}`);
    "#;

    let ast = engine.compile(script)?;

    engine.run_ast(&ast).unwrap();

    Ok(())
}

Seems I can not access the variable obj in the script2.

schungx commented 2 years ago

That is because register_global_module will ignore variables.

https://rhai.rs/book/rust/modules/index.html#usage-patterns

There are two ways to do this:

1) Use engine.register_static_module("lib", module.into());

print(`global: ${lib::obj}`);

2) Put that module into a Scope

let obj = engine.eval_ast<Map>(&ast)?;
let mut scope = Scope::new();
scope.push_constant("obj", obj);
...
engine.run_ast_with_scope(&mut scope, &ast)?;
uuhan commented 2 years ago

I see. Thanks.

schungx commented 2 years ago

Actually your question gave me an idea. In the next version, variables in modules registered under register_global_module can be accessed via this syntax: global::MY_VAR_NAME.

Not as convenient as non-qualified but at least you can access it.

uuhan commented 2 years ago

Actually your question gave me an idea. In the next version, variables in modules registered under register_global_module can be accessed via this syntax: global::MY_VAR_NAME.

Not as convenient as non-qualified but at least you can access it.

I think global-module maybe better to be able to access the function and variables without namespace, (even the global::* style), otherwise, it seems redundant with a static-module which named 'global'.

In my code sample above, I just what to define some global function and variables, which I can use directly in my scripts, just like the globalThis in javascript world.

IMO, global::MY_VAR_NAME maybe a redundant way to introduce the public members in global module.

or we can use the variable like:

import "my-lib" as lib;

// The default global module
use global::*;
// Any other library
use lib::*;

@schungx

schungx commented 2 years ago

It's true, I can potentially look up the constants in global modules inside the global namespace without the global:: modifier... that might make it work better. Let me try it out.

youngzhaozju commented 1 year ago

Hey, I have a further question. Is it possible to define or to add global varialbes from the script? such like:

// In the script, to add global variable. How to let the complier know var1 is a global var? let var1= 1;

fn test() { var1 = var1 + 1; }

// on the Rust side, call the function test in manytimes. engine.call_fn(&mut self.scope, &self.ast, "test", ());

Thank you!

schungx commented 1 year ago

Technically no, it isn't possible from within a function. Functions are pure, so they do not mutate the outside environment except through this.

So normally, in Rhai, you'd write the following:

let var1 = 1;

fn test() {
    this = this + 1;
}

var1.test();

However, call_fn is different. It runs that particular function while providing access to variables in an external scope.

If your var1 is inside self.scope, then the call_fn would work just fine.

As you write, it won't work because, although let var1 = 1; is evaluated, the scope is cleaned out before test is called. Therefore, there will be no var1 left in the scope.

Check out: https://rhai.rs/book/engine/call-fn.html#admonition-default-behavior

There are ways to avoid cleaning the scope via call_fn_with_options.

youngzhaozju commented 1 year ago

Hey schungx, Thank you very much for the nice reply!

I changed my code desin in the following way! fn ini() { let var1 = 1; return ["var1", var]; }

fn test() { var1= var1+ 1; }

in the rust side,

  1. run ini() firstly, and get the result of ["var1", var]
  2. add ["var1", var] to scope;
  3. run test()

Thank you! Regards, Young

schungx commented 1 year ago

You can look into call_fn_with_options and simply omit rolling back the scope.

Then your init can simple be:

fn init() {
    let var1 = 1;
}

If you specify rewind_scope(false), var1 will be left inside the scope after you finished with init.

For an actual implementation, look at this: https://rhai.rs/book/patterns/events-2.html