Skip to content
volt
GitHub

Namespaces, globals and packages

namespace a { ... } groups declarations; a::name reaches them. Namespaces nest (namespace a::b is short for one inside the other), and the same namespace can be opened again in another place or file.

use std::io;
namespace geo {
struct vec2 { x: f64; y: f64; }
fn len2(v: vec2) -> f64 {
return v.x * v.x + v.y * v.y;
}
namespace units {
fn to_cm(m: f64) -> f64 { return m * 100.0; }
}
}
fn main() -> void {
val v: geo::vec2 = { x: 3.0, y: 4.0 };
std::println("{} {}", geo::len2(v), geo::units::to_cm(1.23));
}
// expect: 25 123

Inside a namespace, its own names (and those of the namespaces around it) are reachable without a prefix.

use a::b::c; makes c’s members reachable through the first namespace of the path, a: so after use std::io;, std::io::println can be written std::println. When c is a single item instead of a namespace, the item itself becomes a::c. The full path keeps working.

use std::io;
use app::util;
namespace app::util {
fn twice(x: i32) -> i32 { return x * 2; }
}
fn main() -> void {
std::println("{}", app::twice(21));
std::io::println("full paths still work");
}
// expect: 42
// expect: full paths still work

Two uses that bring the same name into one namespace merge into one overload set. A method the namespace attaches doesn’t hide such a name either, since methods are called as x.name(), never by path: std attaches min to slices, and after use std::math;, std::min(a, b) is math’s function.

use { "header.h" } as c; is different: it imports a C header into namespace c. See C interop.

val and var work at the top level and in namespaces. A global’s value must be known at compile time: arithmetic and bit operations over constants and other val globals work (1 << 13, FLAG | 0x10), and so do calls to comptime functions. The result must fit the global’s type. A literal of constants works too (an array, a struct, an enum variant with its payload, an optional), and so does another global’s address (val at: i32* = &hits;).

use std::io;
val LIMIT: i32 = 3 * 4;
var hits: i32 = 0;
fn record() -> void {
hits += 1;
}
fn main() -> void {
record();
record();
std::println("{} {}", LIMIT, hits);
}
// expect: 12 2

A static local keeps its value between calls: static var calls: i32 = 0; inside a function. @attributes([@thread_local]) on a global var gives each thread its own copy (see Attributes).

A package’s declarations are its own unless it marks them public: code in the package can use any of them, and anywhere else, naming one that isn’t public (a function, method, type or global) is a compile error, 'helper' isn't public in package mylib. So a package’s API is what it says is.

public fn api() -> i32 { return helper() + 1; } // the package's API
fn helper() -> i32 { return 1; } // its own

A program’s own files never need public: everything they declare is visible, to std’s templates too (internal still keeps a declaration to the program). A struct’s fields and an enum’s variants go with their type (internal on a field hides it outside its package: naming it in source there, to read, write or set it in a literal, is the error, while reflection and @derive see every field), an export fn is public, and so are the functions in an attach some_trait -> T { } block, which are the trait’s. Templates are fine: a std template that calls one of std’s own helpers still works when your program instantiates it.

A program’s own files (every file on the command line) share one top level, so each sees what the others declare. A package is a directory of .volt files, and each of its files is wrapped in namespace NAME. That’s all std is: every std file is inside namespace std. You give voltc packages with --pkg NAME=DIR (bolt does it for dependencies), and the program reaches them as NAME::.... See Packages and std.