LEVIATHAN v962456e · 962456eee1

Declarations

Constructors

Declare constructors with new, select among them by label and argument types, call base constructors, and construct classes that declare none.

since 0.1.0-alpha.1linuxwindowswasm

Description

A constructor is a member introduced by new. There is no new at a call site: you construct an object by calling the type, Point(3, 4).

new ClassName() { ... }                 // 'new' marks a constructor
new AnyLabel(string s) { ... }          // the name is only a selection label
new Configured(int port = 80) { ... }   // constant default parameter value

The name after new is a label. A constructor whose label is the class name is called as ClassName(args). A constructor with any other label is called as ClassName::label(args), which is how a class offers several named ways to build the same type. The call picks among the constructors that share the label by the types of the arguments. Constructor arguments may be named, and parameters may have constant defaults. A constructor body is one statement, like any other body.

Inside a constructor, Base::Base(args) runs a base class's constructor on this. The derived class decides when it runs and with which arguments.

Labels, defaults, generics and base constructors

class Point {
    int x;
    int y;
    new Point() { x = 0; y = 0; }
    new Point(int px, int py) { x = px; y = py; }
    new polar(int r) { x = r; y = 0; }
    new origin() { x = 0; y = 0; }
}
console.writeln(Point().x);
console.writeln(Point(3, 4).y);
console.writeln(Point::polar(9).x);
Point o = Point::origin();
console.writeln(o.y);
class Configured {
    int port;
    string host;
    new Configured(int port = 80, string host = "localhost") {
        this.port = port;
        this.host = host;
    }
}
Configured c = Configured(host: "example.org");
console.writeln(c.host + ":" + c.port.toString());
class Box<T> {
    T value;
    new From(T v) { value = v; }
}
Box<int> b = Box::From::<int>(1);
console.writeln(b.value);
class Animal {
    string name;
    new Animal(string n) { name = n; }
}
class Dog : Animal {
    int tricks;
    new Dog(string n, int t) {
        Animal::Animal(n);
        tricks = t;
    }
}
Dog d = Dog("Rex", 3);
console.writeln(d.name + " " + d.tricks.toString());
0
4
9
0
example.org:80
1
Rex 3

Rules

  • A constructor is declared with new Label(params) body. The body is one statement.
  • Calls select the label by name and then the overload by argument types (most specific wins; among equals the first declared wins; see lang.argument-binding).
  • ClassName(args) selects constructors labeled with the class name. ClassName::label(args) selects those with that label. A qualified class name works the same way: NS::Class::label(args).
  • For a generic class the type arguments come, in order, from an explicit tuple, from an enclosing target type, and from the constructor arguments. An explicit tuple follows the complete label: Box::From::<int>(1).
  • Constructor parameters may be named at the call and may have constant defaults.
  • Base::Base(args) inside a constructor runs a base constructor. The derived constructor controls the order.
  • A class that declares no constructor can still be constructed. ClassName(a, b, ...) fills the data fields in declaration order; fields of a base class come first. Fewer arguments than fields leave the remaining fields at their defaults, and no arguments is ordinary default construction.
  • Passing more arguments than the class has data fields is a compile error ("too many arguments to construct"). A primitive has no data fields, so int(x), string(x) and bool(x) are errors; use conversion methods such as x.toInt() instead.
  • A constructor may assign a readonly field of its own class (see lang.readonly), but not a const field.
  • There is no static keyword. A function that builds an instance is written as a labeled constructor.

Examples

A class with no constructor is built from its fields in order:

Memberwise construction

class Base { string a; }
class Derived : Base { int b; }
Derived d = Derived("x", 2);
console.writeln(d.a + d.b.toString());
class FieldError { string field; string message; int code; }
FieldError e = FieldError("email", "required");
console.writeln(e.field + ":" + e.message + ":" + e.code.toString());
FieldError z = FieldError();
console.writeln("[" + z.field + "]");
x2
email:required:0
[]

Constructors are chosen by argument type:

Overloaded constructors

class P {
    int x;
    new P(int a) { x = a; }
    new P(string s) { x = s.length(); }
    new P(float f) { x = 100; }
}
console.writeln(P(5).x);
console.writeln(P("abc").x);
console.writeln(P(1.5).x);
5
3
100

Too many arguments are rejected:

Too many arguments

class FieldError { string field; string message; }
FieldError e = FieldError("a", "b", "c");
// error: too many arguments to construct 'FieldError': it has 2 assignable fields but 3 were given
int x = int(3);
// error: too many arguments to construct 'int': it has 0 assignable fields but 1 were given
not run — a compile error on purpose

See also

  • Classes — Declare reference types with fields, methods, constructors and accessors, with generics and inheritance from one or more base classes.
  • readonly fields — Fix an instance field once, either in its initializer or in every constructor, and keep it read-only afterwards.
  • Argument binding — How a call's arguments are matched to parameters, filled by defaults or injection, and how overloads are ranked.
  • Fields — Declare the data a class or struct holds, with optional initializers, and how a field without an initializer gets its default value.