Objects and Nullability
An object is a reference type with named fields. Use objects for application data, parsed messages, configuration, and shared mutable state.
Defining an object
object Point {
float x;
float y;
}
Objects can define methods. Inside a method, this is the receiving object:
object Player {
string name;
int score;
Point position;
infallible fn move(float dx, float dy) -> void {
this.position.x = this.position.x + dx;
this.position.y = this.position.y + dy;
}
}
Call a method with .: player.move(2.0, -1.0);.
Constructing an object
An object literal supplies fields by name:
Point origin = Point {
x: 0.0,
y: 0.0
};
Player player = Player {
name: "Ada",
score: 0,
position: origin
};
Fields may appear in any order, each at most once. A trailing comma is allowed.
Omitted number, char and bool fields start at zero or false, and an
omitted enum field at its member valued 0. Omitted nullable fields start as
null. Every other field, including string and an enum with no member
valued 0, must be supplied.
Reading and writing fields
int old_score = player.score;
player.score = old_score + 10;
Field types are checked statically.
Const views
const T is a read-only view of an object, interface or engine type:
infallible fn inspect(const Player player) -> int {
return player.score;
}
A Player may flow into const Player. The reverse is rejected for
assignments, parameters, returns, branch results, and captured values.
Through a const view, fields are readable but not assignable. Methods you write can't be called through a const view, because they may change the object. Some engine types have methods that can be.
Const is shallow: a reference read from a const view is not itself const.
Private fields
Prefix a field with ~ to make it private to the package (folder) that
declares the object:
object Counter {
~int value;
infallible fn increment() -> void {
this.value = this.value + 1;
}
infallible fn current() -> int {
return this.value;
}
}
infallible fn counter_create() -> Counter {
return Counter { value: 0 };
}
Any file in the same package may read, write, and initialize value. Other
packages can neither access it nor name it in an object literal; they use
counter_create, increment, and current. This keeps representation
private behind constructor functions and methods. Fields without ~ are
public.
The same ~ before object makes the whole type private to its package
(~object Cursor { ... }); see
Private declarations. A
public object may hold one only in a ~ field. ~ before a method is an
error: methods are public whenever the object is usable.
Interfaces
An interface names a set of required methods:
interface Counter {
infallible fn increment(int amount) -> void;
infallible fn current() -> int;
}
An object satisfies an interface when its methods match. There is no
implements declaration:
object Score {
int value;
infallible fn increment(int amount) -> void {
this.value = this.value + amount;
}
infallible fn current() -> int {
return this.value;
}
}
Score score = Score { value: 10 };
Counter counter = score;
counter.increment(5);
Interfaces are reference types. They may be parameters, results, locals, fields, nullable references, collection elements, and task results. Converting an object to an interface aliases the same object; it does not copy or wrap it.
Conformance is checked at compile time. Parameter and result types must match exactly. An infallible method satisfies a fallible requirement, but a fallible method does not satisfy an infallible one.
To find out what an interface value really holds, test it with is, and get
the object back with a downcast:
if (counter is Score) {
Score original = Score(counter)?;
original.value = 0;
}
value is T is false for null. T(value) fails with code 3 when the
value is null or not a T, so it needs ? or !!. T may also be another
interface: Named(counter)? succeeds when the object has Named's methods.
examples/interfaces/interfaces.bt
is a complete program in which two object types share one interface.
References and aliases
Objects are references. Assignment creates another alias to the same object; it does not copy fields.
Player first = player;
Player second = first;
second.score = 50;
// first.score and player.score are now also 50.
To get independent state, create a new object and copy the fields you need. Arrays, maps, channels, tasks, errors, buffers, interfaces, and engine objects alias the same way.
Equality
== on objects compares identity: two references are equal when they refer to
the same object. Fields are not compared. Strings are values, not references,
and == compares their contents.
Nullable references
Reference types are non-null by default. Add ? to permit null:
Player? selected = null;
if (selected == null) {
println("Nothing selected");
}
Scalar types cannot be nullable: numbers, bool, char, enums, string,
and function types.
A T? is not assignable to T, and a null check does not narrow it. Reading
a field through null traps at runtime; the compiler does not reject it.
The same holds for built-in methods on a nullable collection: items.length()
or queue.send(v) on an array<T>? or channel<T>? compiles without a check
and traps if the value is null (a null array's length() traps rather than
returning 0).
Check before access:
if (selected != null) {
println(selected.name + ": " + int_to_string(selected.score));
}
Prefer non-null values in the core of a function.
Engine objects
cTurtle provides object types of its own. You use their fields like any
object's. For example, Weapon has ammo and magazine_size:
include "cturtle/world";
infallible fn reload(Weapon weapon) -> void {
if (weapon.ammo == 0) {
weapon.ammo = weapon.magazine_size;
}
}
The cTurtle API reference lists these types, their fields and functions, and which operations can fail. Each lives in a package your code must include.
Some engine objects stand for data that can disappear, such as an entity's
component. Reading or writing their fields can fail, so each access takes ?:
(velocity.x = 2.0)?. If the entity has been destroyed, the access fails with
error code -410, which you can handle with !! like any other error. The
entities, components and systems guide shows this in use.