Global Variables

By default V does not allow global variables. However, in low-level applications they have their place so their usage can be enabled with the compiler flag -enable-globals. Declarations of global variables must be surrounded with a __global ( ... ) specification – as in the example above.

An initializer for global variables must be explicitly converted to the desired target type. If no initializer is given a default initialization is done. Use const after __global (or inside the __global ( ... ) block) when the symbol must stay a true C-level constant. Non-extern const globals currently require an explicit initializer that can be emitted directly in C global scope. Some objects like semaphores and mutexes require an explicit initialization in place, i.e. not with a value returned from a function call but with a method call by reference. A separate init() function can be used for this purpose – it will be called before main():

import sync __global ( sem sync.Semaphore // needs initialization in `init()` mtx sync.RwMutex // needs initialization in `init()` f1 = f64(34.0625) // explicitly initialized shmap shared map[string]f64 // initialized as empty `shared` map f2 f64 // initialized to `0.0` ) fn init() { sem.init(0) mtx.init() }

Be aware that in multi threaded applications the access to global variables is subject to race conditions. There are several approaches to deal with these:

  • use shared types for the variable declarations and use lock blocks for access. This is most appropriate for larger objects like structs, arrays or maps.
  • handle primitive data types as "atomics" using special C-functions (see above).
  • use explicit synchronization primitives like mutexes to control access. The compiler cannot really help in this case, so you have to know what you are doing.
  • don't care – this approach is possible but makes only sense if the exact values of global variables do not really matter. An example can be found in the rand module where global variables are used to generate (non cryptographic) pseudo random numbers. In this case data races lead to random numbers in different threads becoming somewhat correlated, which is acceptable considering the performance penalty that using synchronization primitives would represent.

Shadowing a global

A local variable may not reuse the name of a global. A global's bare name is visible everywhere, including in modules that never import the one declaring it, so the two names are not as far apart as they look.

Where the global belongs to another module, the local does not merely shadow it, it loses: the declaration is ignored and every use of that name, including the ones that look like reads of the local, means the global. The code then does something other than it reads, with nothing to point at.

V rejects it:

error: variable `devices` shadows a global variable

The fix is to rename the local, giving it a name that says what it holds:

__global ( devices []&Device ) fn mount_dev(root &Node) bool { // Was `devices`, which silently meant the global above. dev_dir := get_node(root, '/dev') or { return false } dev_dir.parent = root return true }

The check covers every source-level local binding: declaration targets, function and lambda parameters, for variables, if guards, select receive declarations, and compile-time \$for variables. Each name is checked, so both targets of value, devices := make_pair() are covered. A name that shadows nothing, and _, are left alone.

Only code the project owns is checked, which for a directory build means the whole project, not just the file named on the command line. An installed dependency is left alone: its author cannot see the globals your program declares, so a local of theirs that happens to collide will not stop your build.

On this page