Declarations
const declarations
Fix a local, field, global, parameter or loop binding after its initializer, with the exact rules for each kind of slot.
since 0.1.0-alpha.1linuxwindowswasm
Description
const makes a slot read-only after its initialization. It applies to local variables, fields,
namespace-level variables, parameters and loop bindings:
const int maxRedirs = 50; // local: fixed at declaration
const var limit = 100; // composes with inference
const Array<string> args = std::sysArgs(); // namespace global: fixed at startup
class Session {
public const string id = "s-1"; // field: a named compile-time constant
const int SSL = 0x0800;
}
void handle(const Options o) { ... } // parameter
for (const string a in args) { ... } // per-iteration binding
const is not part of a type. It never appears where a type is expected and does not change
assignability, overload selection or generics. It is also not transitive: const MyClass m = MyClass(); fixes the variable m, but the object it refers to can still change, so m.field = 5;
is allowed. For deeply immutable data use a struct, a pure array or map, or a get-only accessor.
const bindings, fields, parameters and loops
const int maxRedirs = 50;
const var limit = 100;
const Array<string> names = ["a", "b"];
class Session {
public const string id = "s-1";
const int SSL = 0x0800;
}
class Opts { int level = 1; }
void handle(const Opts o) {
o.level = 5;
console.writeln(o.level);
}
console.writeln(maxRedirs);
console.writeln(limit);
console.writeln(Session().id);
console.writeln(Session().SSL);
const Opts shared = Opts();
shared.level = 3;
console.writeln(shared.level);
handle(shared);
for (const string n in names) {
console.writeln(n);
}
int base = 7;
const int computed = base * 2;
console.writeln(computed);
50
100
s-1
2048
3
5
a
b
14
The objects behind shared and o are still mutable; only the variables are fixed.
Rules
Each slot has a window in which it may be written; after the window closes it is read-only.
- Locals. The window is the declaration's initializer.
const int x;without an initializer is a compile error, because the default value would be frozen forever. The initializer can be any expression. - Fields. A
constfield is a named compile-time constant. It must have an initializer, and the initializer must be a compile-time constant: a literal,None, an array of constants, anotherconstvalue, or an arithmetic or bitwise operator over constants. No constructor may assign aconstfield. A field whose value is known only at construction time isreadonly(lang.readonly). - Namespace and top-level variables. The window is the initializer, which can be any expression.
- Parameters. The value bound by the call; assigning to a
constparameter in the body is an error. for (const T x in ...). Each iteration has its own fresh binding, which cannot be assigned.- Compound assignment (
x += 1) is a write, like=.a[i] = von a pure array is a write to the array variable and is rejected for aconstarray. - Calling a
mutatingmethod on aconststruct variable is a compile error. - A
setaccessor cannot be declared over aconstfield, and a field cannot be bothconstandreadonly. - If two bases declare a field with the same name and type, one
constand one not, the class that inherits both is a compile error; resolve it withdistinctor by restating the field in the derived class. - A
const:label inside a class body declares a run of fieldsconst, in the waypublic:andprivate:set access. The two are independent:public:afterconst:does not end theconstrun, and a member writtenvaropts out of it.
Initialization order of globals
Every namespace-level and top-level variable is first created with its type's default value, before any initializer or statement runs, so an initializer always sees a valid variable. Then:
- Namespace-level initializers run, before the program body starts.
- Top-level statements run in source order, and a top-level variable's explicit initializer runs at its own position among them.
A namespace initializer therefore sees a top-level variable at its default value, not at the value its own initializer will assign later.
Top-level statements
The top level of a file is the program body and accepts the usual statements: expressions,
declarations, if, while, for, do-while, labeled loops, match, try/catch and throw.
It does not accept break, continue or return, since there is no enclosing loop or function, nor
using, which declares a resource binding for a block.
Examples
Namespace initializers run first and see top-level variables at their defaults:
Initialization order
int seedValue = 5;
namespace Reg {
int fromTop = seedValue;
int fixed = 3;
int fromNs = fixed * 2;
}
console.writeln("start");
console.writeln(Reg::fromTop);
console.writeln(Reg::fromNs);
int later = seedValue + 1;
console.writeln(later);
start
0
6
6
Reg::fromTop is 0, not 5: the namespace initializer ran before the top-level statement that
sets seedValue.
A const: section:
A const: section with a var opt-out
class Config {
const:
string host = "localhost";
int port = 8080;
public:
string name = "svc";
var int hits = 0;
void hit() { hits = hits + 1; }
}
Config c = Config();
c.hit();
c.hit();
console.writeln(c.host);
console.writeln(c.port);
console.writeln(c.name);
console.writeln(c.hits);
localhost
8080
svc
2
name is const too, because public: does not end the const: run; only hits, marked var,
can change.
Writes that are rejected at compile time:
Writing a const
const int x = 1;
x = 2; // error: cannot assign to const 'x'
const int y; // error: needs an initializer
class C {
const int n = 1;
new C() { n = 2; } // error: cannot assign to const field 'n'
const readonly int m = 1; // error: a field cannot be both const and readonly
}
int f() => 3;
class D { const int k = f(); } // error: needs a compile-time-constant initializer
A const field may be computed from other const fields:
A const field computed from other constants
class Flags {
const int A = 1;
const int B = 2;
const int BOTH = A | B;
}
console.writeln(Flags().BOTH); // 3
See also
- readonly fields — Fix an instance field once, either in its initializer or in every constructor, and keep it read-only afterwards.
- Fields — Declare the data a class or struct holds, with optional initializers, and how a field without an initializer gets its default value.
- Value structs — Declare value types that are copied on every assignment, pass and return, with derived equality and explicit mutating methods.
- Namespaces — Group declarations under a name, reopen a namespace to add to it, nest namespaces, and reach members with
::.