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.

Last updated 10 min read Edit on GitHub

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.

AntrilRun
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);
}
Terminal
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: virtual lets derived classes replace a method, override replaces one, and final override stops further overrides. See Methods.
  • A class cannot derive from itself, directly or not (AN1010), nor from a final class (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):

AntrilRun
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());
}
Terminal
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.

Antril
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.

AntrilRun
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);
}
Terminal
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 (a str, a class) has no zero, so its variables need a value.
  • A value cannot have a destructor (AN1077) or reactive members.
  • this inside 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 == and Equals, 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).
AntrilRun
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);
}
Terminal
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:

AntrilRun
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());
}
Terminal
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 how INumber<T> works (see Generics).
  • It can declare operators its implementers must have (AN1045 when one is missing).
  • x is IShape s tests 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:

AntrilRun
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);
	}
}
Terminal
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.

Antril
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.

AntrilRun
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);
}
Terminal
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).

Was this page helpful?

No address is stored; your user name is, if you are signed in.

Esc