Declarations
Classes & Interfaces
How Leviathan declares reference types: inline and sectional access control, contracts that allocate nothing, multiple inheritance, and the collision rule that decides what happens when two bases name the same member.
Declaring a class
A class optionally takes generic parameters and a
comma-separated base list. The body is a brace-delimited member list;
a class with no members yet may use the empty-body form.
class Name<T, U> : Base1, Base2 {
// ...members...
}
class Name; // empty-body form
class is Leviathan's reference type:
instances have identity and are shared by reference. This is the
opposite of struct, the value type covered on
Structs & Enums — reach for
class when identity matters, struct when it
doesn't.
Constructors, just enough to read the examples
A constructor is marked by the new keyword;
its name is only a label used to disambiguate overloads, and carries
no other meaning. Construction itself has no new
at the call site — calling the class name runs the matching
constructor.
new Counter() {
label = "unnamed";
}
// Overload: same 'new' marker, different parameters.
new Counter(string startLabel) {
label = startLabel;
}
// The label need not match the class name.
new WithValue(string startLabel, int start) {
label = startLabel;
value = start;
}Counter a = Counter(); // matches the nullary constructor
Counter b = Counter::WithValue("hi", 5); // explicit selection by label
When two constructors would otherwise collide on parameter shape,
first-declared wins and the label is the explicit tiebreaker — the
same shape as the field qualification in
Distinct below. Full constructor, method, and
accessor syntax — including get/set and
mutating — lives on
Members & Accessors.
Access modifiers
Access may be marked inline, per member, or
sectionally with a public: /
private: region that applies to every member until the
next section marker.
class Account {
// Inline access modifiers
public string owner;
private int pin;
public:
string describe() => owner;
private:
bool checkPin(int guess) => guess == pin;
}Interfaces
An interface declares required members but is
a contract that allocates nothing. That includes
fields — a deliberate exception that treats a field requirement and a
method requirement as the same kind of requirement. The implementing
class's declaration is the one and only allocating site.
public interface MyInterface {
int myNumber;
string myString;
}
class MyInterfacedClass : MyInterface {
int myNumber; // the declaration that allocates
string myString;
}A requirement left unsatisfied is a compile error.
Because an interface never allocates, two interfaces requiring the same field create no conflict — the implementing class satisfies both with its single declared slot. The field-collision machinery only ever engages between allocating bases (classes), because an interface never contributes a second slot to begin with.
Multiple inheritance
A class base list may name more than one class, giving Leviathan multiple inheritance of both interface and implementation. Two constructs from the same worked example:
class Counter : Named {
public string label;
public distinct int value = 0; // distinct: survives same-typed collision
get value() => value;
set value(int v) value = v;
public:
string describe() => label;
new Counter() { label = "unnamed"; }
new Counter(string startLabel) { label = startLabel; }
}
class Tag {
public distinct int value = 99; // same name+type as Counter.value
public string note = "tag-note";
}
Widget then inherits from both. Its constructor reaches
each base's constructor by qualifying the label with the base's name
— Base::Base() applies to this:
class Widget<T> : Counter, Tag {
public T payload;
new Widget() {
Counter::Counter();
Tag::Tag();
// Same-typed 'value' kept distinct on both sides:
// this.Counter::value == 0
// this.Tag::value == 99
this.Counter::value = 5;
this.Tag::value = 7;
// 'note' comes only from Tag — no collision, bare access is fine
note = "widget";
}
}The rule that decides whether two same-named inherited members share a slot or not:
| Inherited members | Outcome |
|---|---|
| Same name, same type, from two bases | Collision — see Distinct |
| Same name, different type, from two bases | No collision — both coexist, resolved by type at each use |
| Same field required by two interfaces | No collision — interfaces allocate nothing, one slot satisfies both |
Distinct: resolving collisions
A collision is name + type inherited from two bases.
By default a collision collapses to one slot — the
later base wins. Marking the field distinct, on either
source, keeps separate per-source slots instead,
reachable only by qualification:
public distinct int value = 0;
With value declared distinct on both
Counter and Tag, Widget holds
two independent int slots, each reached through
this.Base::member:
this.Counter::value = 5; // explicit qualification, no collision
this.Tag::value = 7;
// console.write(value); // invalid: two int 'value' slots, no type contextOnce a member is distinct-collided, there is no default slot to
fall back to — every read or write must qualify which base's slot
it means, either via this.Base::member or, from
outside the instance, Base::member resolution by
static type.
Fields still auto-construct when bare-declared, the same as any
field: string s; is "",
Array<T> a; is [],
int i; is 0 — a distinct
field with no initializer follows the same rule as any other.
Covariant interface returns
A method satisfies an interface requirement when its name and
parameter canonicals match exactly and its declared
class/interface return type is assignable to the
required return — a subtype return is enough. T also
satisfies a requirement declared as T?. Every other
return shape — unions, function types, primitives, and
void — must still match exactly.
interface Shape {
Shape clone();
}
class Circle : Shape {
// Circle is a subtype of Shape — satisfies the requirement covariantly.
Circle clone() => Circle::Circle();
}interface Repository {
Widget? find(int id);
}
class MemRepository : Repository {
// T satisfies a required T?
Widget find(int id) => ...;
}This relaxation applies only to interface satisfaction — narrowing a return type in a derived class does not override the base class's method the same way. And because a requirement allocates no slot, dispatch still reaches the implementing class's single method; there is nothing extra to resolve at the call site.