LEVIATHAN v962456e · 962456eee1

Expressions

Construction

Creating objects by naming the type, choosing a constructor by label, supplying type arguments, and building a class that declares no constructor.

since 0.1.0-alpha.1linuxwindowswasm

Description

An object is created by naming its type and passing arguments. There is no new at the call site: new appears only in a constructor declaration.

Type(args)                 // construction
Type::Label(args)          // the constructor declared with that label
Type::<int>(args)          // explicit type arguments for a generic class
Type::Label::<int>(args)   // explicit type arguments after the complete labeled callee
NS::Type(args)             // a class in a namespace, reached by qualification
NS::Type::Label(args)      // ...combined with a label
Base::Ctor(args)           // inside a constructor: run a base-class constructor on this object

A constructor declaration new Label(...) { ... } gives the constructor a label that callers use to select it. Several constructors may share a label. The overload is chosen by the argument types, and between candidates that fit equally well the one declared first is used.

A bare declaration Type name; constructs the type's default value: "" for string, [] for an array, 0 for int.

Labeled constructors, defaults and namespaces

namespace Shapes {
    class Circle {
        int r;
        new Unit() { r = 1; }
    }
}

class Account {
    string owner;
    int balance;
    new Open(string who, int start = 0) {
        owner = who;
        balance = start;
    }
}

class Shape {
    string name;
    new Shape(string n) { name = n; }
}

class Square : Shape {
    int side;
    new Square(int s) {
        Shape::Shape("square");
        side = s;
    }
}

Shapes::Circle c = Shapes::Circle::Unit();
console.writeln(c.r);
Account a = Account::Open("ann");
console.writeln("${a.owner} ${a.balance}");
Account b = Account::Open("bo", start: 9);
console.writeln("${b.owner} ${b.balance}");
Square q = Square(4);
console.writeln("${q.name} ${q.side}");
1
ann 0
bo 9
square 4

Rules

  • Constructor arguments may be named, and constructor parameters may declare default values, with the same rules as any call (see lang.calls).
  • The type arguments of a generic class are filled in this order: first a complete explicit ::<...> tuple, then the target type of the surrounding declaration or assignment, then the constructor arguments for any slot still open. So Box<int> b = Box(1.9) constructs a Box<int> and converts the argument to int; the argument does not first fix the type to float.
  • A plain assignment's declared target works the same way as a declaration's: given Map<string, int> m, both Map<string, int> m = Map(); and a later m = Map(); infer the key and value types from m.
  • A generic construction whose type arguments have no source at all (no explicit tuple, no target type, no argument that carries the type) is a compile error. Write a target type, pass an argument that carries the type, or use an explicit tuple such as Box::<int>(5).
  • The explicit tuple follows the complete callee: Box::From::<int>(value), not Box::<int>::From(value).
  • Inside a constructor, Base::Ctor(args) runs a base-class constructor on this. The derived class decides when to call it.

A class without a constructor

A class that declares no constructor is still constructible. ClassName(a, b, ...) fills the data fields positionally, in declaration order. Giving fewer arguments than there are fields leaves the remaining fields at their defaults, and giving none is ordinary default construction. Giving more arguments than the class has data fields is a compile error: too many arguments to construct '...'.

The same rule explains why there are no casts. int(x) is not a conversion; it tries to construct an int, which has no fields, and fails with that error. Use x.toInt(), x.toFloat() or x.toString().

Construction without a constructor, and generic inference

class Email {
    string address;
    string label;
}

class Box<T> {
    T value;
}

Email e = Email("a@b.c");
console.writeln("[${e.address}] [${e.label}]");

Box<int> truncated = Box(1.9);
console.writeln(truncated.value);
Box<float> kept = Box(1.9);
console.writeln(kept.value);
Box<int> explicit = Box::<int>(4);
console.writeln(explicit.value);

Map<string, int> m = Map();
m = Map();
m["k"] = 1;
console.writeln(m.length());
[a@b.c] []
1
1.900000
4
1

Examples

class Duo { int a; int b; }
Duo d = Duo(1, 2, 3);
// error: too many arguments to construct 'Duo': it has 2 assignable fields but 3 were given

class Box<T> { T value; }
Box b = Box();
// error: cannot infer type argument 'T' for 'Box'; provide a target type or a type-bearing argument
not run — shows a compile error

A labeled construction through the dot form

class Tagged {
    int v;
    new Of(int n) { v = n; }
}

console.writeln(Tagged::Of(9).v);
console.writeln(Tagged.Of(8).v);
9
8

Notes

  • A local variable that has the same name as a class takes precedence over the class in a dot call: with Tagged Tagged = Tagged::Of(5); in scope, Tagged.twice() calls the method twice on the variable's value. The :: spelling (Tagged::Of(9)) always names the class.

See also

  • Map — An associative collection from keys of type K to values of type V, with value semantics.