diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md
index ba551367..61111f65 100644
--- a/doc/src/language/fn-namespaces.md
+++ b/doc/src/language/fn-namespaces.md
@@ -15,28 +15,51 @@ allow combining all functions in one [`AST`] into another, forming a new, unifie
In general, there are two types of _namespaces_ where functions are looked up:
-| Namespace | Source | Lookup method | Sub-modules? | Variables? |
-| --------- | ------------------------------------------------------------------------------------- | --------------------------------- | :----------: | :--------: |
-| Global | 1) `Engine::register_XXX` API
2) [`AST`] being evaluated
3) [packages] loaded | simple function name | ignored | ignored |
-| Module | [`Module`] | namespace-qualified function name | yes | yes |
+| Namespace | How Many | Source | Lookup method | Sub-modules? | Variables? |
+| --------- | :------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | :----------: | :--------: |
+| Global | One | 1) [`AST`] being evaluated
2) `Engine::register_XXX` API
3) [packages] loaded
4) functions in [modules] loaded via `Engine::register_module` and marked _global_ | simple function name | ignored | ignored |
+| Module | Many | [`Module`] | namespace-qualified function name | yes | yes |
+
+
+Module Namespace
+----------------
+
+There can be multiple module namespaces at any time during a script evaluation, loaded via the
+[`import`] statement.
+
+Functions and variables in module namespaces are isolated and encapsulated within their own environments.
+
+They must be called or accessed in a _namespace-qualified_ manner.
+
+```rust
+import "my_module" as m; // new module namespace 'm' created via 'import'
+
+let x = m::calc_result(); // namespace-qualified function call
+
+let y = m::MY_NUMBER; // namespace-qualified variable (constant) access
+
+let x = calc_result(); // <- error: function 'calc_result' not found
+ // in global namespace!
+```
Global Namespace
----------------
-There is one _global_ namespace for every [`Engine`], which includes:
+There is one _global_ namespace for every [`Engine`], which includes (in the following search order):
-* All the native Rust functions registered via the `Engine::register_XXX` API.
+* All functions defined in the [`AST`] currently being evaluated.
-* All the Rust functions defined in [packages] that are loaded into the [`Engine`].
+* All native Rust functions and iterators registered via the `Engine::register_XXX` API.
-* All the [modules] imported via [`import`] statements.
+* All functions and iterators defined in [packages] that are loaded into the [`Engine`].
-In addition, during evaluation of an [`AST`], all script-defined functions bundled together within
-the [`AST`] are added to the global namespace and override any existing registered functions of
-the same names and number of parameters.
+* Functions defined in [modules] loaded via `Engine::register_module` that are specifically marked
+ for exposure to the global namespace (e.g. via the `#[rhai(global)]` attribute in a [plugin module]).
+
+Anywhere in a Rhai script, when a function call is made, the function is searched within the
+global namespace, in the above search order.
-Anywhere in a Rhai script, when a function call is made, it is searched within the global namespace.
Therefore, function calls in Rhai are _late_ bound - meaning that the function called cannot be
determined or guaranteed and there is no way to _lock down_ the function being called.
This aspect is very similar to JavaScript before ES6 modules.
diff --git a/src/module/mod.rs b/src/module/mod.rs
index 482dea48..1fde1d37 100644
--- a/src/module/mod.rs
+++ b/src/module/mod.rs
@@ -514,6 +514,18 @@ impl Module {
self
}
+ /// Update the namespace of a registered function.
+ ///
+ /// The [`u64`] hash is calculated either by the function [`crate::calc_native_fn_hash`] or
+ /// the function [`crate::calc_script_fn_hash`].
+ pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self {
+ if let Some(f) = self.functions.get_mut(&hash_fn) {
+ f.namespace = namespace;
+ }
+ self.indexed = false;
+ self
+ }
+
/// Set a Rust function into the module, returning a hash key.
///
/// If there is an existing Rust function of the same hash, it is replaced.
diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs
index 44ab4e7e..69e53bf1 100644
--- a/src/packages/string_basic.rs
+++ b/src/packages/string_basic.rs
@@ -172,8 +172,7 @@ mod print_debug_functions {
let len = map.len();
map.iter_mut().enumerate().for_each(|(i, (k, v))| {
- result.push_str(k);
- result.push_str(": ");
+ result.push_str(&format!("{:?}: ", k));
result.push_str(&print_with_func(FUNC_TO_DEBUG, &ctx, v));
if i < len - 1 {
result.push_str(", ");
diff --git a/tests/functions.rs b/tests/functions.rs
index e04f7ae3..2034095f 100644
--- a/tests/functions.rs
+++ b/tests/functions.rs
@@ -1,5 +1,5 @@
#![cfg(not(feature = "no_function"))]
-use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
+use rhai::{Engine, EvalAltResult, FnNamespace, Module, ParseErrorType, RegisterFn, INT};
#[test]
fn test_functions() -> Result<(), Box> {
@@ -51,6 +51,31 @@ fn test_functions() -> Result<(), Box> {
Ok(())
}
+#[cfg(not(feature = "no_function"))]
+#[test]
+fn test_functions_namespaces() -> Result<(), Box> {
+ let mut engine = Engine::new();
+
+ #[cfg(not(feature = "no_module"))]
+ {
+ let mut m = Module::new();
+ let hash = m.set_fn_0("test", || Ok(999 as INT));
+ m.update_fn_namespace(hash, FnNamespace::Global);
+
+ engine.register_module("hello", m);
+
+ assert_eq!(engine.eval::("test()")?, 999);
+ assert_eq!(engine.eval::("fn test() { 123 } test()")?, 123);
+ }
+
+ engine.register_fn("test", || 42 as INT);
+
+ assert_eq!(engine.eval::("test()")?, 42);
+ assert_eq!(engine.eval::("fn test() { 123 } test()")?, 123);
+
+ Ok(())
+}
+
#[test]
fn test_function_pointers() -> Result<(), Box> {
let engine = Engine::new();
diff --git a/tests/print.rs b/tests/print.rs
index 7967670a..3d4c34a7 100644
--- a/tests/print.rs
+++ b/tests/print.rs
@@ -73,6 +73,6 @@ fn test_print_custom_type() -> Result<(), Box> {
x.to_string()
"#
)?
- .contains("e: hello: 42"));
+ .contains(r#""e": hello: 42"#));
Ok(())
}