Objects in Toit

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:

class Point:
  x /int := 42
  y /int := 103

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:

main:
  p := Point  // Creates a new Point object
  print p.x  // >> 42
  print p.y  // >> 103

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.

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 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 := Square

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. The class and the 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 * y

It 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 * y

The 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.0

Super-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`.

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

Before 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