Overview

A namespace is a collection of names associated with values and functions. Namespaces limit the scope of visibility and help to structure the code. MUFL namespaces behave similarly to C++ namespaces.

When you declare a library, an application, or a script, a namespace is declared implicitly using the name of the library, application, or script. You can also use the module keyword to declare additional, nested namespaces.

In cases where names can be used outside of the namespace in which they were declared, the access must be made using name resolution rules, that is, they must either use the using statement to introduce the external names, or refer to them using multipart names with the :: separator.

The using <namespace_name> syntax allows accessing names within the given module without explicitly specifying the name of the module every time. However, referring to an ambiguous name (for example, when the same name is present in multiple namespaces referenced with the using statement) results in a compile-time error.

script a { 
    x = "a--x". 
    module b {
        x = "b--x". 
        y = "b--y". 
        _print x "\n". 
        _print a::x "\n".
        _print ::a::b::x "\n". 
    }

    _print b::x "\n". 
    using b. 
    // _print x "\n". // error -- this name is now ambiguous
    _print y "\n". 
}

Prints:

b--x 
a--x 
b--x 
b--x 
b--y

Visibility and Loading

Modules and libraries expose their functions, variables, user-defined types, and nested modules. They can be accessed by the name of the namespace, which always corresponds to the name of the module or library.

library A
{
    var = "hello ".

    fn printing_function (_)
    {
        _print "world\n".
    }
}

application B loads library A
{
    _print A::var.
    A::printing_function().
}

Prints:

hello world

MUFL precludes implicit visibility between libraries. That is, if library A loads library B, and library B loads library C, then code inside A does not have transitive access to the names inside C. In order to gain visibility, A must load C explicitly.

library A
{
    var = "hello".
}

library B loads library A
{
    var = A::var. // this works as expected
}

script X loads library B
{
    _print B::var. // this works fine
    _print A::var. // this throws an error since `A` is not accessible here
}

Results in this expected compiler output:

---COMPILER OUTPUT---- 
<...>Symbol A::var was not found 

Compilation Error 
ERROR running compiler

Hidden Directive

A hidden code block can be used to prevent outside access to some of the names inside a namespace. The names are still accessible by the code inside the namespace and within its nested namespaces.

library A
{
    hidden
    {
        var = "hello ".
    }

    fn print_hidden (_)
    {
        _print var "\n".
    }
}

library B loads library A
{
    A::print_hidden(). // this one works because the function accesses its own data
    _print A::var. // leads to a compilation error
}

Results in this expected error:

---COMPILER OUTPUT---- 
<...>Symbol var is hidden 
Compilation Error 
ERROR running compiler

Name Resolution Rules

Simple Names

The MUFL compiler resolves simple (local) names through a layer-by-layer examination of namespaces, from innermost to outermost. Specifically:

  • If a simple name maps to multiple locations, a compilation error ensues. For example, when the using keyword references a nested namespace housing an identical simple name.
  • In cases where a namespace does not include a simple name, the search progresses to the parent namespace.

Absolute Path Names

Names identified by their absolute path are resolved by investigating namespaces from the root to innermost layers, section by section. Notably:

  • If a name corresponds to multiple locations within a namespace section, it triggers a compilation error.
  • The absence of a section within a namespace also results in a compilation error.