Namespaces#
Basic Use#
Namespaces are used to separate symbols into separate scopes, allowing the same identifier to be used in multiple contexts without a naming conflict. This is useful, for example, to ensure that a function in one script can co-exist with another script that has a function with the same name and type signature.
When calling a function[1], the compiler searches for a matching function signature starting in the current namespace. If it doesn’t find any, it looks at the next namespace up (either another one you’ve declared, as you can declare namespaces inside namespaces, or the ‘global namespace’, i.e. everything that isn’t inside a namespace).
To specify what namespace to use, you can list the namespaces out before the function name,
divided by the scope-resolution operator ::
. It will still search in the same pattern, starting from the
current namespace and moving up- but instead of looking for the function name, it looks
for the whole path that you specified exactly.
To specify a symbol that is NOT in a namespace, you can simply use an empty name - i.e. begin the name with the scope-resolution operator ::
.
You can reference both VERSION’ variables, by specifying the namespace to access
namespace MyCoolScript
{
DEFINE VERSION = 2; // version of my script!
}
// then in some other file
namespace SomeDatabaseScript
{
DEFINE VERSION = 5; // script version
}
void test()
{
//Trace(VERSION); // error; 'VERSION' is not declared
Trace(MyCoolScript::VERSION); // prints '2'
Trace(SomeDatabaseScript::VERSION); // prints '5'
}
Referencing a function will always start by looking in the current namespace
#include "std.zh" // has a function 'lweapon CreateLWeaponAt(int id, int x, int y)'
namespace MyCustomWeapon
{
CONFIG DAMAGE = 8;
lweapon CreateLWeaponAt(int id, int x, int y)
{
/* We can use an empty namespace name to specifically call
the version that isn't in any namespace */
lweapon weap = ::CreateLWeaponAt(id, x, y);
weap->Damage = DAMAGE;
}
void FireWeaponInPattern(int id, int x, int y, int pattern)
{
/* imagine some code here that does fancy math for weapon patterns
then create the weapons as needed by calling the custom create function */
/* Since we are inside the namespace, just calling the function by name
will find the function *in this namespace* as the best match */
CreateLWeaponAt(id, x, y);
}
}
void someOtherFunction()
{
/* Since we aren't in any namespace, just calling the function by name will
only find the version not inside any namespace, included from 'std.zh' */
CreateLWeaponAt(LW_FIRE, 64, 64);
}
Nesting namespaces inside each other
namespace Example
{
namespace Internal
{
void some_func()
{
// Some function internal to this example
}
}
void foo()
{
Internal::some_func();
}
}
void bar()
{
Example::Internal::some_func();
}
Across Files#
Unlike things like functions, variables, classes, etc., you can declare a namespace with an already-existing name. The scopes of these namespaces will be merged with each other. A key example of this would be a script header file, which might put ALL of its code in a namespace- but might have more than one file. The same namespace can simply be declared in each file, and it all functions as “one namespace”.
Nested Declaration#
When declaring a namespace, the name does not need to be a single identifier,
but can instead contain an entire identifier list including scope-resolution operator ::
.
This can be used to declare multiple namespaces at once, and can be particularly useful
for merging something with a namespace in another file.
// file 1
namespace DrawHelpers
{
void ColorScreen()
{
/* some code */
}
namespace Draw3D
{
void draw_pyramid()
{
/* some code */
}
}
}
// file 2
namespace DrawHelpers::Draw3D
{
void draw_sphere()
{
/* some code */
}
}
Using#
With the using
statement, you can tell the compiler that you want it to check a
particular namespace for all function calls[1], without the need to
type out the name. The using
statement can be used anywhere outside of functions,
and at the very top inside functions. They only apply at the scope they are placed,
and inward from there.
Name Conflicts
In using using
, you can reference things in that namespace directly, but you
lose some of the benefit of namespaces, namely, you are able to have name conflicts
again. You may run into compiler errors such as:
There are too many vars/consts…
There are too many types…
There are too many choices for function…
Running into these errors can indicate that the compiler could not figure out which
function/var/const/type you were intending to use. This is easy to fix when you
run into it though- you can simply tell it which namespace to use directly, the same
way you would without a using
statement, via scope-resolution operator ::
. This will help the
compiler figure out which version of the function/var/const/type you meant to
reference, and thus compile.
using using
to more easily reference a function
namespace DrawHelpers
{
// Colors in the whole screen a single solid color
void ColorScreen(int color, int layer = 7, bool over_subscreen = true)
{
Screen->Rectangle(layer, 0, over_subscreen ? -56 : 0, 255, 175, color);
}
}
generic script someScript
{
/* anywhere inside this script can call functions
declared in the namespace 'DrawHelpers' */
using namespace DrawHelpers;
void run()
{
CONFIG COLOR_BLACK = 0x0F;
loop()
{
// Would compile error, without the 'using'
ColorScreen(COLOR_BLACK);
Waitframe();
}
}
}
How using
can create conflicts, and how to avoid them
CONFIG VALUE = 5;
namespace Example
{
CONFIG VALUE = 8;
}
void test()
{
using namespace Example;
//Trace(VALUE); // error; 'There are too many vars/consts named VALUE'
Trace(Example::VALUE); // prints '8'
Trace(::VALUE); // prints '5'
}
Tip
You can also add the keyword always
before a using statement, which tells the compiler
that you want it to use that using statement in every single scope of every single file
of your entire compile. You likely should not use this unless you have a specific reason to,
as it is highly likely to cause name conflicts.
always using namespace DrawHelpers;