Types

Classes, values, properties, inheritance, interfaces, enums, records, tuples and nullable types in Antril.

Last updated 13 min read Edit on GitHub

On this page

Antril has these kinds of types:

Kind What it is
class A reference type: variables share one object. One base class, any number of interfaces.
base class A class that cannot be created directly, only derived from.
value A value type: assigning or passing it copies it. Implements interfaces, has no base.
record class, record value Fields fixed by the constructor, equality by content, copies with with.
interface Methods and properties a type promises, with optional default bodies.
enum, bitflag enum A closed set of named values; flags combined with |.
exception, attribute Classes derived from System.Exception and System.Attribute (see Errors).

Classes

AntrilRun
import System;

into namespace Bank
{
	public class Account
	{
		public:
			new(str owner, i32 start)
			{
				this.owner = owner;
				balance = start;
				Opened += 1;
			}

			str Owner { get => owner; }
			i32 Balance { get { return balance; } private set { balance = value; } }
			static i32 Opened { get; set; }

			Deposit(i32 amount)
			{
				Balance = balance + amount;
			}

		private:
			str owner;
			i32 balance;
	}

	Main()
	{
		Account a = new Account("Ana", 10);
		a.Deposit(5);
		Account b = a;
		b.Deposit(1);
		Console.WriteLine(a.Owner + " has " + a.Balance);
		Console.WriteLine(Account.Opened + " account opened");
	}
}
Terminal
larva run
Ana has 16
1 account opened

b = a copies the reference: a and b are the same account.

Access sections

Members are grouped under access labels: public:, private:, internal:, inherited:, friendly: and file:. A label applies until the next one or the end of the block, and can be repeated. A modifier written on a single declaration wins over the label.

By convention the label goes one tab further in than its block, and its members one tab more; the compiler warns otherwise.

Access Visible from
public Everywhere, also other libraries.
private The type itself and its nested types. The default for members.
internal The same library or program. The default for top-level types.
inherited The type and the types derived from it.
friendly The type and its friends, declared with [FriendOf(Other)] (friendship goes both ways).
file The same source file.

Interface members, operators and enum values are always public. A project can change the default for declarations that say nothing with access.default in its .queen file.

Fields and constructors

Fields are declared like variables. Fields of value types start at zero; a readonly field is assigned only in the constructor.

A constructor is new(…). A class without one gets an empty one. Constructors can be overloaded, and the forms below save typing:

AntrilRun
import System;

into namespace Geometry
{
	public value Size
	{
		public:
			i32 Width;
			i32 Height;

			// No body: each parameter is stored in the field of the same name.
			new(i32 Width, i32 Height);

			// Calls the other constructor.
			new(i32 side) : (side, side);

			i32 Area { get => Width * Height; }
	}

	public class Label
	{
		readonly str text;

		public:
			new(str text) { this.text = text; }
			override ToString() : str => "[" + text + "]";
	}

	Main()
	{
		Size square = new Size(4);
		Console.WriteLine(square.Area);
		Console.WriteLine(new Label("ok"));
	}
}
Terminal
larva run
16
[ok]

A class can have one destructor, destroy(). It runs once, when the object stops existing; when that is depends on how the program manages memory (see Memory).

Properties

Form What it is
i32 Count { get; set; } A field read and written as a property.
i32 Count { get; private set; } The same, set only by the type itself.
i32 Area { get => w * h; } Computed each time it is read.
i32 Balance { get { … } private set { … } } Bodies for each accessor; inside set, the new value is value. An accessor can be more restricted than the property.

Static members and static classes

static fields, properties and methods belong to the type: Account.Opened. A static class only has static members (its members are static without saying so) and cannot be used as the type of a variable:

Antril
public static class Settings
{
	public:
		static i32 Retries = 3;
		Describe() : str => "retries: " + Retries;
}

Types can be nested inside other types; from outside they are named Outer.Inner.

Values

A value is copied when it is assigned or passed. Its methods work on the original when called on a variable.

AntrilRun
import System;

public value Point
{
	public:
		i32 X;
		i32 Y;
		new(i32 X, i32 Y);
		Length2() : i32 => X * X + Y * Y;
		Move(i32 dx) { X = X + dx; }
}

Main()
{
	Point p = new Point(3, 4);
	Point q = p;
	q.X = 0;
	p.Move(1);
	Console.WriteLine(p.Length2() + " " + q.Length2());
}
Terminal
larva run
32 16

Inheritance and interfaces

A class derives from one base class and implements any number of interfaces: class Circle : Shape, IDrawable.

  • Methods are not virtual unless they say so. virtual lets derived classes replace a method, and override replaces it with the same signature.
  • In a base class, a method without a body must be written by every derived class.
  • : base(args) after a constructor's parameters calls the base class's constructor.
  • final class cannot be derived from; final override stops further overrides of one method.
  • An interface lists methods and properties. It can give a default body, which implementing types inherit.
AntrilRun
import System;

into namespace Shapes
{
	public interface IShape
	{
		Area() : f64;
		Describe() : str => "a shape of area " + Area();
	}

	public base class Shape : IShape
	{
		public:
			Area() : f64;
			virtual Name() : str => "shape";
	}

	public class Circle : Shape
	{
		f64 radius;

		public:
			new(f64 radius) { this.radius = radius; }
			override Area() : f64 => 3.14 * radius * radius;
			override Name() : str => "circle";
	}

	public final class Square : Shape
	{
		f64 side;

		public:
			new(f64 side) { this.side = side; }
			override Area() : f64 => side * side;
	}

	Main()
	{
		List<Shape> shapes = new List<Shape>();
		shapes.Add(new Circle(1));
		shapes.Add(new Square(2));
		loop (Shape s in shapes)
			Console.WriteLine(s.Name() + ": " + s.Area());

		IShape i = new Square(3);
		Console.WriteLine(i.Describe());

		Shape first = shapes[0];
		if (first is Circle c)
			Console.WriteLine("a circle of area " + c.Area());
		Square? square = first as Square;
		Console.WriteLine(square == null);
	}
}
Terminal
larva run
circle: 3.14
shape: 4
a shape of area 9
a circle of area 3.14
true

x is T tests a type, and x is T name also gives the value that type. x as T gives a T?, null when x is not a T.

A value can implement interfaces too. Stored in a variable of the interface type, it is copied into a box.

Every class derives from any (System.Any), which has ToString(), Equals and Hash(). Override ToString() to control how an object is printed.

Enums

An enum is a closed set of named values over a base type: an integer (i32 by default), str or a value.

AntrilRun
import System;

into namespace Palette
{
	public enum Size { Small, Medium = 5, Large }

	public enum Word : str { Hello = "hola", World }

	public bitflag enum Access : u8 { Read, Write, Execute, All = declbits }

	public record value Rgb
	{
		public:
			u8 R;
			u8 G;
			u8 B;
			new(u8 R, u8 G, u8 B);
	}

	public enum Color : Rgb
	{
		Red = new Rgb(255, 0, 0),
		Green = new Rgb(0, 255, 0),
	}

	Main()
	{
		Size s = Size.Large;
		Console.WriteLine(s + " is " + (i32)s);
		Console.WriteLine((str)Word.Hello + " " + (str)Word.World);

		Access a = Access.Read | Access.Write;
		Console.WriteLine((a & Access.Write) == Access.Write);
		Console.WriteLine(a.HasFlag(Access.Execute));
		Console.WriteLine((u8)Access.All);

		Rgb green = (Rgb)Color.Green;
		Console.WriteLine(green);
	}
}
Terminal
larva run
Large is 6
hola World
true
false
7
Rgb { R = 0, G = 255, B = 0 }
  • Integer members without a value take the previous one plus 1, from 0. A str member without a value is its own name.
  • An enum converts to and from its base only with a cast: (i32)s, (Size)6.
  • ToString() (and printing) gives the member's name.
  • In a bitflag enum (base u32 by default) members without a value take the next free bit: 1, 2, 4… |, &, ^ and ~ keep the enum's type. declbits is every declared bit, allbits every bit of the base. HasFlag, HasAny, With, Without and Toggle test and change bits.
  • Members can be computed from other members and constants: ReadWrite = Read | Write.
  • With a value base, each member is a value of that type. Converting a value back to the enum ((Color)rgb) needs equality by content: a record value or an op ==.

Records

A record class or record value is data: its fields are set by the constructor and do not change afterwards. Records compare by content, print their fields, hash for use as dictionary keys, and copy with changes using with.

AntrilRun
import System;

into namespace People
{
	public record class Person
	{
		public:
			str Name;
			i32 Age;
			new(str Name, i32 Age);
	}

	Main()
	{
		Person ana = new Person("Ana", 31);
		Person older = ana with { Age = 32 };
		Console.WriteLine(ana);
		Console.WriteLine(ana == new Person("Ana", 31));
		Console.WriteLine(older.Age);
		if (ana is Person(var name, > 30))
			Console.WriteLine(name + " is over 30");
		Console.WriteLine(older is { Name: "Ana", Age: 32 });
	}
}
Terminal
larva run
Person { Name = Ana, Age = 31 }
true
32
Ana is over 30
true

A record class does not derive from another class and cannot be derived from; it can implement interfaces.

Tuples

(T1, T2, …) is a tuple type and (a, b, …) makes one. Elements are Item1, Item2… Tuples are records: they compare by content, print as (1, one) and work as dictionary keys.

AntrilRun
import System;

MinMax(i32[] values) : (i32, i32)
{
	i32 low = values[0], high = values[0];
	loop (i32 v in values)
	{
		if (v < low)
			low = v;
		if (v > high)
			high = v;
	}
	return (low, high);
}

Main()
{
	(i32, i32) range = MinMax([4, 9, 1, 7]);
	Console.WriteLine(range);
	Console.WriteLine(range.Item2 - range.Item1);

	(i32, str) pair = (1, "one");
	if (pair is (1, var word))
		Console.WriteLine(word);

	Dictionary<(i32, i32), str> grid = new Dictionary<(i32, i32), str>();
	grid[(0, 1)] = "a";
	Console.WriteLine(grid[(0, 1)]);
}
Terminal
larva run
(1, 9)
8
one
a

(…) is a System.ValueTuple, whose elements can be changed. ToTuple() gives a System.Tuple, a class whose elements are fixed.

A tuple that holds objects, such as (i32, str), cannot be the type argument of a generic type (List<(i32, str)>). Tuples of numbers and plain values can: Dictionary<(i32, i32), str>.

Nullable types

A type without ? never holds null. T? can:

AntrilRun
import System;

public class Person
{
	public:
		str Name;
		new(str Name);
}

Find(str name) : Person?
{
	if (name == "ana")
		return new Person("Ana");
	return null;
}

Main()
{
	Person? found = Find("ana");
	Person? missing = Find("bob");
	Console.WriteLine(found?.Name ?? "nobody");
	Console.WriteLine(missing?.Name ?? "nobody");

	i32? count = null;
	Console.WriteLine(count ?? -1);
	count = 7;
	Console.WriteLine(count.HasValue);
	Console.WriteLine(count.Value);

	if (found is Person p)
		Console.WriteLine("found " + p.Name);
}
Terminal
larva run
Ana
nobody
-1
true
7
found Ana
  • ?. reads a member, or gives null when the left side is null.
  • ?? gives its left side unless it is null.
  • x is T name tests for a value and names it.
  • For value types, T? has HasValue and Value; reading Value when there is none stops the program.

Next: Functions and generics.

Was this page helpful?

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

Esc