Classes
In Toit, everything is an object, including things that may be non-object
"primitive types" in other languages. For example, an integer
is an object, and has methods like abs:
// Method that takes an `int` and returns an `int`. foo x/int -> int: return x.abs // The absolute value of x.
To create custom objects, we must first define a class:
Here we have given our new class two fields, with integer
types, and they are
by default initialized to 42 and 103. We create new instance objects of the
class by naming the class, no new keyword is needed:
There are various ways to declare fields in objects:
class Foo:
// u is untyped and initialized to zero.
u := 0
// v is an integer.
v /int := 0
// w is a nullable integer (either null or an integer).
w /int? := null
// x is an integer, and it must be set by the constructor.
x /int := ?
// y is an integer, the field is immutable, the
// constructor must set it.
y /int
// z has the same properties as y, but the short form is
// better style.
z /int ::= ?
// LTUAE is a constant. When possible, prefer static
// constants.
LTUAE /int ::= 42
// PASSWORD is a static constant.
static PASSWORD /string ::= "hunter2"
constructor:
x = y = z = 0 // Set the fields that must be set.Getters and Setters
In Toit the external syntax for getters and setters is the same as for public fields:
class Bar: x /int := ? constructor .x: my-function bar/Bar -> none: // Accesses public field x, or uses the x getter. print bar.x // Sets the public field x, or uses the x setter. bar.x = 42
If we later decide to make the x field private we rename it to have a final underscore. At the same time, we can introduce getters and setters that keep our class compatible with the old version:
class Bar:
x_ /int := ? // Private field.
constructor .x_:
// Getter, returns an int.
x -> int:
return x_
// Setter, takes an int, returns nothing.
x= value/int -> none:
x_ = value
print "x_ was set to $x_"Inheritance
Toit is an object-oriented language. The inheritance and typing mechanism for classes is similar to the one of Java: Classes can extend each other, with subclasses being subtypes in the typing system.
class Point: x /int := 0 y /int := 0 class Point3D extends Point: z /int := 0 main: p /Point? := null // Local variable p has type Point (nullable). p = Point // Create point. // Since a Point3D is a type of point we can // assign p to refer to a Point3D. p = Point3D // Create 3d point. p.x = 42 // OK because Point has a field called x. p.z = 103 // Compile-time error: Point has no z field! p3 := p as Point3D // Cast p to a Point3D (with run-time check). p3.z = 103 // OK because p3 has the right static type.
Interfaces
A class can only extend one class, but can implement several interfaces. Like classes, interfaces act as types in Toit's type system, but unlike classes, they do not include implementations for the non-static methods they declare. Since a method signature declared in an interface has no implementation, the colon at the end of the first line is omitted:
// To implement the Turtle interface, a class must have
// these two methods.
interface Turtle:
forwards mm/int -> none
turn degrees/int -> none
// To be used as a Drawable, a class must have a method
// called draw, which takes a Turtle and returns no value.
interface Drawable:
// No colon on the method declaration
draw turtle/Turtle -> none
// To have the type `Drawable` the Square class must both
// declare that it `implements Drawable`, and contain the
// `draw` method.
class Square implements Drawable:
draw turtle/Turtle -> none:
4.repeat:
turtle.forwards 10
turtle.turn 90
main:
// Variables can have interface types. Here `d` has the
// type `Drawable`.
d /Drawable := SquareIn Toit an interface can have static methods and constructors, but the constructors must be factory constructors.
An abstract class has some similarities to a class and to an interface. Like a class, it can have fields and methods that its extending classes will inherit. Like an interface, it can have methods without implementations and constructors. The class and the non-static methods must be marked explicitly as abstract. A class cannot be instantiated (objects of that class cannot be created) unless it implements all the required methods from the abstract classes it extends:
// Classes that extend `TurtleBase` must implement two
// methods.
abstract class TurtleBase:
mileage := 0
// Classes that extend `TurtleBase` should call this after
// moving a distance to register the distance the Turtle
// has moved.
register-mileage mm/int -> none:
mileage += mm
// Concrete (non-abstract) classes that extend TurtleBase
// must implement these two methods.
abstract forwards mm/int -> none
abstract turn degrees/int -> none
// The LogTurtle doesn't actually perform the drawing moves,
// it just logs the moves a real turtle would make.
class LogTurtle extends TurtleBase:
forwards mm/int -> none:
print "I moved forwards $(mm)mm."
register-mileage mm
turn degrees/int -> none:
print "I turned $degrees degrees"
main:
turtle := LogTurtle
4.repeat:
turtle.forwards 10 // >> I moved forwards 10mm.
turtle.turn 90 // >> I turned 90 degrees.
print "The turtle has travelled $(turtle.mileage)mm."Constructors
Sometimes it is not enough to specify the initial values for fields. When there
are more complex ways to initialize an object we make use of special functions
called constructors. In Toit the constructor has the name constructor rather
than having the same name as the class:
import math
class Vector:
x /float
y /float
length /float
constructor x-arg/float y-arg/float:
x = x-arg
y = y-arg
length = math.sqrt x * x + y * yIt is rather common that parameters of constructors are copied directly into
fields of the new object, so there is a shorthand. By putting a dot (.) in
front of the parameter name, it is copied directly to the field of the same
name:
import math
class Vector:
x /float
y /float
length /float
// The dots initialize fields x and y. No need to restate
// the types of x and y here.
constructor .x .y:
length = math.sqrt x * x + y * yThe shorthand .field parameter has the same type as the field it assigns to.
Named Constructors
By default, constructors have no name and are invoked with
the name of the class. For example, the above unnamed constructor for the
Vector class is invoked using the name Vector:
main: // Create a new Vector using the unnamed constructor. // Note that there is no `new` keyword in Toit. v := Vector 10.0 7.5
We can have several unnamed constructors with different numbers of arguments. This is a natural result of the fact that Toit has argument-count overloading and argument-name overloading.
However, sometimes it is more helpful to have constructors with different
names. For example, the Vector class might have a polar constructor:
import math
class Vector:
x /float
y /float
length /float
constructor .x .y:
length = math.sqrt x * x + y * y
constructor.polar angle/float .length:
// The dot above initializes the length field.
x = length * (math.cos angle)
y = length * (math.sin angle)
main:
// Use the unnamed Cartesian constructor.
v := Vector 10.0 7.5
// Use the polar constructor.
v2 := Vector.polar math.PI/6 8.0Super-constructors
In a class with inheritance you may want to invoke a constructor
for the class you are extending. This is done with the super keyword:
class Color:
r /int
g /int
b /int
constructor .r .g .b:
class ColoredVector extends Vector:
color /Color
constructor x/float y/float .color:
super x y // Calls the constructor of `Vector`.Factory constructors
A factory constructor is rather like a static method that returns an instance of the class. Simply placing a return statement in a constructor makes it into a factory constructor.
For example, it may be very common to create a special
instance of an immutable class. Since it is immutable,
the constructor can save memory by returning the same
object every time. Here we have a factory constructor
named origin.
class Pair:
x /int
y /int
// Regular constructor.
constructor .x .y:
// Private singleton object.
static SINGLETON-ORIGIN_ ::= Pair 0 0
// Named factory constructor.
constructor.origin:
return SINGLETON-ORIGIN_Factory constructors are the only kinds of constructors that interfaces can have. You cannot instantiate an interface, so regular constructors are not possible. An example of this is ByteArray, which is actually an interface because there are several different implementations. The constructors return a "regular" ByteArray:
ba1 := ByteArray 10 // Constructs a ByteArray with 10 zeros. ba2 := ByteArray 5: it * 2 // Constructs #[0, 2, 4, 6, 8]
Advanced constructor topics
Some languages have a different syntax for initializing the field variables. For example, C++ has the initializer list which can precede the constructor body. In Dart a similar feature is also called initializer lists. Toit uses the same syntax for member initialization as for the rest of the constructor, so you don't have to learn two different syntaxes.
Usually you don't have to worry about where the boundary is between
field initialization and the rest of the constructor, but there can
be situations where it matters. Implicitly, a constructor is split into two
parts: the initialization, and the instance part. They are separated by the
super call.
If no super call is present, then Toit implicitly adds
one as late as possible. This is either at the end of the constructor body, or
as soon the code uses this, a reference to the newly constructed object.
Implicit uses of this also count, for example by invoking a method, or by
creating a lambda that captures this.
class Point:
x/float // This must be set during field initialization...
y/float // ...of the constructor.
// A method on the Point class
stringify -> string:
return "$x, $y"
constructor x-arg/float y-arg/float:
// Calling the stringify method uses "this".
print "Created " + stringify
x = x-arg // Error - this happens in the instance...
y = y-arg // ...part of the constructor.Although the compiler will infer the boundary between field
initialization and object creation you can always explicitly specify
it by calling the super constructor. Even classes that don't
explicitly extend another class extend the Object class so
you can call the super constructor with no arguments:
class Foo:
x /int
constructor:
x = 12
// Explicit 'super' calls are possible but rare.
super
// Explicit `this` is also not normally needed.
print this.xBefore the object is created you can already refer to fields that
have been assigned. These field accesses do not count as instance accesses.
Immutable fields do not become immutable before the call to super
in the constructor..
This still means the fields are immutable all other places than in the
constructor, since no other parts of the program can obtain a reference
to the newly constructed object before the super call.
import math
class Vector:
x/float // These must be initialized in the initializer...
y/float // ...part of the constructor, not later.
length /float
// A method on the Vector class
stringify -> string:
return "$x, $y"
constructor.unit:
x = 1.0
y = 1.0
// It is OK to access fields in the initializer part of
// the constructor.
length = math.sqrt x * x + y * y
super
// Implicit use of this when calling stringify.
print stringify