Type declarations
The kinds of types an Antril program declares - classes, base classes, final and static classes, values, records, interfaces, exceptions, attributes and nested types - and how inheritance works.
On this page
A type declaration names a new type and lists its members. Its general form is:
[attributes] [modifiers] kind Name[<type parameters>] [: base, interfaces…] [into namespace N]
{
members
}
| Kind | What it declares |
|---|---|
class |
A reference type. One base class, any number of interfaces. |
base class |
A class that cannot be created directly, only derived from. It may leave methods without a body. |
final class |
A class nothing can derive from. |
static class |
A holder of static members, with no values. |
value |
A value type: copied on assignment. Implements interfaces, has no base. |
record class, record value |
Data compared by content, with fields fixed by the constructor and with copies. |
interface |
Members a type promises, with optional default bodies. |
enum, bitflag enum |
A closed set of named values. See Enums. |
exception Name |
The class NameException, derived from System.Exception. |
attribute Name |
The class NameAttribute, derived from System.Attribute. |
The members (fields, constructors, properties, methods…) are described in Members, and the access words in Access. Type parameters are in Generics.
Classes
A class is a reference type: new makes an object on the heap, and every variable that holds it refers to the same object.
import System;
public class Account
{
public:
new(str owner) { this.owner = owner; }
Deposit(i32 amount) { balance = balance + amount; }
i32 Balance { get => balance; }
str Owner { get => owner; }
private:
str owner;
i32 balance;
}
Main()
{
Account a = new Account("Ana");
Account same = a;
same.Deposit(10);
Console.WriteLine(a.Owner + ": " + a.Balance);
}
larva run
Ana: 10
Every class derives, directly or through its bases, from any (System.Any), which gives it ToString(), Equals(other), Hash() and GetType(). Override ToString() to choose how an object is printed; without an override, an object of a class prints as System.Any.
Inheritance
A class names its base class first after :, then the interfaces it implements: class Circle : Shape, IDrawable, IComparable<Circle>. Only one base class is allowed; a second class there is error AN1008.
- A derived class has every member of its base. Inside it,
base.M()calls the base class's version of a method. - A constructor calls a base constructor with
: base(arguments)(see Constructors). - Methods are not virtual unless they say so:
virtuallets derived classes replace a method,overridereplaces one, andfinal overridestops further overrides. See Methods. - A class cannot derive from itself, directly or not (AN1010), nor from a
finalclass (AN1009) or a record class (AN1076).
A derived object converts to its base class and to its interfaces by itself. Going the other way needs a test or a cast: shape is Circle c, shape as Circle, or (Circle)shape.
Base classes
A base class cannot be created with new (AN2061). It can declare instance methods without a body, which every class derived from it must write (AN1034 lists the ones missing):
import System;
public base class Animal
{
public:
Speak() : str;
virtual Kind() : str => "animal";
Intro() : str => Kind() + " says " + Speak();
}
public class Dog : Animal
{
public:
override Speak() : str => "woof";
override Kind() : str => "dog, an " + base.Kind();
}
Main()
{
Animal a = new Dog();
Console.WriteLine(a.Intro());
}
larva run
dog, an animal says woof
Warning
Not yet available: marking a bodiless method with base (base Area() : f64;). Leave the body out, as above: in a base class that makes the method abstract.
Final classes
A final class cannot be derived from (AN1009). virtual makes no sense in it (AN1100). A final class can keep an array inside each object with fixed T[] (see Memory).
Static classes
A static class only holds static members; its members are static without saying so. It has no values, so it cannot be the type of a variable, a parameter or anything else (AN1105): its members are used through its name.
public static class Settings
{
public:
const i32 Retries = 3;
static str Name;
static new() { Name = "server"; }
Describe() : str => Name + " x" + Retries;
}
Console.WriteLine(Settings.Describe());
Values
A value is a value type. Assigning it, passing it or returning it copies it; a method called on a variable works on that variable.
import System;
public value Counter
{
public:
i32 N;
Bump() { N = N + 1; }
}
Main()
{
Counter a;
a.Bump();
Counter b = a;
b.Bump();
Console.WriteLine(a.N + " " + b.N);
}
larva run
1 2
- A value has no base type (AN1008) and nothing derives from it, but it can implement interfaces. Stored in a variable of an interface type, or in
any, it is copied into a box. - A variable of a value type starts with every field at zero, so
Counter a;is ready to use. A value whose fields include objects (astr, a class) has no zero, so its variables need a value. - A value cannot have a destructor (AN1077) or reactive members.
thisinside a value's method is the value itself.
Records
A record class or record value is data. Compared with a plain class or value, a record:
- has fields that only its constructors set (assigning one elsewhere is AN2070);
- compares by content with
==andEquals, and hashes by content, so it works as a dictionary key; - prints its fields:
P { X = 1, Y = 2 }; - copies with changes using
with:p with { Y = 5 }; - matches positional patterns by its fields in order:
p is P(1, var y).
import System;
public record value Point
{
public:
i32 X;
i32 Y;
new(i32 X, i32 Y);
}
Main()
{
Point p = new Point(1, 2);
Point q = p with { Y = 5 };
Console.WriteLine(p + " " + q);
Console.WriteLine(p == new Point(1, 2));
Dictionary<Point, str> names = new Dictionary<Point, str>();
names[p] = "start";
Console.WriteLine(names[new Point(1, 2)]);
if (q is Point(1, var y))
Console.WriteLine(y);
}
larva run
Point { X = 1, Y = 2 } Point { X = 1, Y = 5 }
true
start
5
A record class has no base class and nothing derives from it (AN1076); it can implement interfaces. Each field's type needs ToString() and Hash() for the record's own (AN2097, AN2138).
Interfaces
An interface lists methods and properties that the types implementing it provide. A method without a body must be written by each implementing type; a method with a body is a default, which implementing types inherit and may replace. A property declares its accessors with base get and base set:
import System;
public interface INamed
{
str Name { base get; }
Describe() : str => "I am " + Name;
}
public interface IGreeter : INamed
{
Greet() : str;
}
public class Person : IGreeter
{
readonly str name;
public:
new(str name) { this.name = name; }
str Name { get => name; }
Greet() : str => "hello from " + name;
}
Main()
{
IGreeter g = new Person("Ana");
Console.WriteLine(g.Greet());
Console.WriteLine(g.Describe());
}
larva run
hello from Ana
I am Ana
- An interface can extend other interfaces:
interface IGreeter : INamed. - Its members are always public; an access word on one is error AN1095.
- It can declare static members, and static requirements such as
static TSelf Zero { base get; }, which a generic function reaches through a type parameter (T.Zero). This is howINumber<T>works (see Generics). - It can declare operators its implementers must have (AN1045 when one is missing).
x is IShape stests whether an object implements an interface.
Exceptions
exception Name { … } declares the class NameException, derived from System.Exception. The suffix is added unless the name already ends with it. Its base is another exception, written with or without the suffix:
import System;
public exception Parse
{
public:
i32 Line;
new(str message, i32 line) : base(message) { Line = line; }
}
public exception BadToken : Parse
{
public:
new(i32 line) : base("bad token", line) { }
}
Main()
{
try
{
throw new BadTokenException(3);
}
catch (ParseException e)
{
Console.WriteLine(e.Message + " at line " + e.Line);
}
}
larva run
bad token at line 3
The base must be an exception (AN1096). Throwing and catching are described in Exceptions.
Attributes
attribute Name { … } declares the class NameAttribute, derived from System.Attribute. It is written without the suffix where it is used: [Name(…)]. See Attributes.
public attribute Tag
{
public:
str Text;
new(str text) { Text = text; }
}
[Tag("entry")] Run() { }
Nested types
A type can be declared inside another type. From outside it is named through its outer type, Outer.Inner; inside, by its own name. A nested type sees the private members of the types that contain it.
import System;
public class Shelf
{
public:
public value Slot
{
public:
i32 Row;
i32 Column;
new(i32 Row, i32 Column);
}
static First() : Slot => new Slot(0, 0);
}
Main()
{
Shelf.Slot s = Shelf.First();
Console.WriteLine(s.Row + s.Column);
}
larva run
0
A nested type of a generic type can use the outer type's parameters.
Modifiers on types
| Word | On a type |
|---|---|
public, internal, file, friendly |
Its access; internal when none is written. See Access. |
final |
Nothing derives from it (classes). |
static |
Only static members (classes). |
unsafe |
Its members may use pointers (see Memory). |
Edit this page on GitHub Report an issue